diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a06ded7..aac68645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - feat(web-ui): add web-ui service - fix(users): avoid setting user as sudoer when it performs a password reset - feat(web-ui): process input demands, dispatched on the bus +- feat(keypad): ability to use key-combinations, set key combinations for screenshot, snapshot and quit ## Version 1.0.0 diff --git a/pyproject.toml b/pyproject.toml index 5988c0c1..8913f00b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,12 @@ version-file = "ubo_app/_version.py" [tool.hatch.version.raw-options] local_scheme = "setup_scm_schemes:local_scheme" +[tool.hatch.build] +packages = ["ubo_app"] + +[tool.hatch.build.targets.sdist] +packages = ["ubo_app"] + [tool.uv] dev-dependencies = [ "headless-kivy [test] >=0.9.8", @@ -182,6 +188,7 @@ profile = "black" [tool.pyright] exclude = ["typings", "ubo_app/rpc/generated", ".venv", "setup_scm_schemes.py"] +disableTaggedHints = true [[tool.pyright.executionEnvironments]] root = "ubo_app/services/000-audio" diff --git a/tests/flows/test_wireless.py b/tests/flows/test_wireless.py index b6a58415..0a3f4985 100644 --- a/tests/flows/test_wireless.py +++ b/tests/flows/test_wireless.py @@ -52,9 +52,12 @@ async def strength() -> int: ) from ubo_app.menu_app.menu import MenuApp - from ubo_app.store.core import ChooseMenuItemByIconEvent, ChooseMenuItemByLabelEvent + from ubo_app.store.core import ( + MenuChooseByIconEvent, + MenuChooseByLabelEvent, + MenuGoBackEvent, + ) from ubo_app.store.main import store - from ubo_app.store.services.keypad import Key, KeypadKeyPressAction def store_snapshot_selector(state: RootState) -> WiFiState: return state.wifi @@ -83,32 +86,32 @@ def check_icon(expected_icon: str) -> None: store_snapshot.take(selector=store_snapshot_selector) # Select the main menu - store.dispatch(ChooseMenuItemByIconEvent(icon='󰍜')) + store.dispatch(MenuChooseByIconEvent(icon='󰍜')) await stability() # Select the settings menu - store.dispatch(ChooseMenuItemByLabelEvent(label='Settings')) + store.dispatch(MenuChooseByLabelEvent(label='Settings')) await stability() # Go to network category - store.dispatch(ChooseMenuItemByLabelEvent(label='Network')) + store.dispatch(MenuChooseByLabelEvent(label='Network')) await stability() # Open the wireless menu - store.dispatch(ChooseMenuItemByLabelEvent(label='WiFi')) + store.dispatch(MenuChooseByLabelEvent(label='WiFi')) await stability() window_snapshot.take() # Select "Select" to open the wireless connection list - store.dispatch(ChooseMenuItemByLabelEvent(label='Select')) + store.dispatch(MenuChooseByLabelEvent(label='Select')) await stability() # Back to the wireless menu - store.dispatch(KeypadKeyPressAction(key=Key.BACK)) + store.dispatch(MenuGoBackEvent()) await stability() # Select "Add" to add a new connection - store.dispatch(ChooseMenuItemByLabelEvent(label='Add')) + store.dispatch(MenuChooseByLabelEvent(label='Add')) await stability() window_snapshot.take() @@ -116,7 +119,7 @@ def check_icon(expected_icon: str) -> None: camera.set_image('qrcode/wifi') # Select "QR code" to scan a QR code for credentials - store.dispatch(ChooseMenuItemByIconEvent(icon='󰄀')) + store.dispatch(MenuChooseByIconEvent(icon='󰄀')) # Success notification should be shown window_snapshot.take() @@ -124,11 +127,11 @@ def check_icon(expected_icon: str) -> None: # Dismiss the notification informing the user that the connection was added await check_icon('󰤨') await wait_for_menu_item(label='', icon='󰆴') - store.dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) + store.dispatch(MenuChooseByIconEvent(icon='󰆴')) await stability() # Select "Select" to open the wireless connection list and see the new connection - store.dispatch(ChooseMenuItemByLabelEvent(label='Select')) + store.dispatch(MenuChooseByLabelEvent(label='Select')) @wait_for(wait=wait_fixed(1), run_async=True) def check_connections() -> None: @@ -143,13 +146,13 @@ def check_connections() -> None: window_snapshot.take() # Select the connection - store.dispatch(ChooseMenuItemByLabelEvent(label='ubo-test-ssid')) + store.dispatch(MenuChooseByLabelEvent(label='ubo-test-ssid')) # Wait for the "Disconnect" item to show up await wait_for_menu_item(label='Disconnect') await stability() window_snapshot.take() - store.dispatch(ChooseMenuItemByLabelEvent(label='Disconnect')) + store.dispatch(MenuChooseByLabelEvent(label='Disconnect')) # Wait for the "Connect" item to show up await wait_for_menu_item(label='Connect') @@ -157,14 +160,14 @@ def check_connections() -> None: await stability() store_snapshot.take(selector=store_snapshot_selector) window_snapshot.take() - store.dispatch(ChooseMenuItemByLabelEvent(label='Connect')) + store.dispatch(MenuChooseByLabelEvent(label='Connect')) await wait_for_menu_item(label='Disconnect') await check_icon('󰤨') await stability() store_snapshot.take(selector=store_snapshot_selector) window_snapshot.take() - store.dispatch(ChooseMenuItemByLabelEvent(label='Delete')) + store.dispatch(MenuChooseByLabelEvent(label='Delete')) @wait_for(wait=wait_fixed(1), run_async=True) def check_no_connections() -> None: @@ -179,7 +182,7 @@ def check_no_connections() -> None: # Dismiss the notification informing the user that the connection was deleted await wait_for_menu_item(label='', icon='󰆴') window_snapshot.take() - store.dispatch(ChooseMenuItemByIconEvent(icon='󰆴')) + store.dispatch(MenuChooseByIconEvent(icon='󰆴')) await wait_for_empty_menu(placeholder='No Wi-Fi connections found') window_snapshot.take() diff --git a/ubo_app/menu_app/menu_central.py b/ubo_app/menu_app/menu_central.py index c85fa73c..c0a87ffc 100644 --- a/ubo_app/menu_app/menu_central.py +++ b/ubo_app/menu_app/menu_central.py @@ -15,15 +15,16 @@ from ubo_app.logging import logger from ubo_app.menu_app.menu_notification_handler import MenuNotificationHandler from ubo_app.store.core import ( - ChooseMenuItemByIconEvent, - ChooseMenuItemByIndexEvent, - ChooseMenuItemByLabelEvent, CloseApplicationEvent, + MenuChooseByIconEvent, + MenuChooseByIndexEvent, + MenuChooseByLabelEvent, + MenuGoBackEvent, + MenuGoHomeEvent, OpenApplicationEvent, SetMenuPathAction, ) from ubo_app.store.main import store -from ubo_app.store.services.keypad import Key, KeypadKeyPressEvent from ubo_app.store.services.notifications import NotificationsDisplayEvent from ubo_app.store.update_manager import UpdateManagerSetUpdateServiceStatusAction from ubo_app.utils.async_ import create_task @@ -129,12 +130,6 @@ def central(self: MenuAppCentral) -> Widget | None: menu_representation = 'Menu:\n' + repr(self.menu_widget) self.menu_widget.bind(stack=lambda *_: logger.info(menu_representation)) - store.subscribe_event( - KeypadKeyPressEvent, - self.handle_key_press_event, - keep_ref=False, - ) - store.subscribe_event( NotificationsDisplayEvent, self.display_notification, @@ -152,43 +147,33 @@ def central(self: MenuAppCentral) -> Widget | None: keep_ref=False, ) store.subscribe_event( - ChooseMenuItemByIconEvent, + MenuGoHomeEvent, + self.go_home, + keep_ref=False, + ) + store.subscribe_event( + MenuGoBackEvent, + self.go_back, + keep_ref=False, + ) + store.subscribe_event( + MenuChooseByIconEvent, self.select_by_icon, keep_ref=False, ) store.subscribe_event( - ChooseMenuItemByLabelEvent, + MenuChooseByLabelEvent, self.select_by_label, keep_ref=False, ) store.subscribe_event( - ChooseMenuItemByIndexEvent, + MenuChooseByIndexEvent, self.select_by_index, keep_ref=False, ) return self.menu_widget - @mainthread - def handle_key_press_event( - self: MenuAppCentral, - key_press_event: KeypadKeyPressEvent, - ) -> None: - if key_press_event.key == Key.L1: - self.menu_widget.select(0) - if key_press_event.key == Key.L2: - self.menu_widget.select(1) - if key_press_event.key == Key.L3: - self.menu_widget.select(2) - if key_press_event.key == Key.UP: - self.menu_widget.go_up() - if key_press_event.key == Key.DOWN: - self.menu_widget.go_down() - if key_press_event.key == Key.BACK: - self.menu_widget.go_back() - if key_press_event.key == Key.HOME: - self.menu_widget.go_home() - @mainthread def open_application(self: MenuAppCentral, event: OpenApplicationEvent) -> None: self.menu_widget.open_application(event.application) @@ -198,7 +183,15 @@ def close_application(self: MenuAppCentral, event: CloseApplicationEvent) -> Non self.menu_widget.close_application(event.application) @mainthread - def select_by_icon(self: MenuAppCentral, event: ChooseMenuItemByIconEvent) -> None: + def go_home(self: MenuAppCentral, _: MenuGoHomeEvent) -> None: + self.menu_widget.go_home() + + @mainthread + def go_back(self: MenuAppCentral, _: MenuGoBackEvent) -> None: + self.menu_widget.go_back() + + @mainthread + def select_by_icon(self: MenuAppCentral, event: MenuChooseByIconEvent) -> None: current_page = self.menu_widget.current_screen if current_page is None: msg = 'No current page' @@ -219,7 +212,7 @@ def select_by_icon(self: MenuAppCentral, event: ChooseMenuItemByIconEvent) -> No @mainthread def select_by_label( self: MenuAppCentral, - event: ChooseMenuItemByLabelEvent, + event: MenuChooseByLabelEvent, ) -> None: current_page = self.menu_widget.current_screen if current_page is None: @@ -241,6 +234,6 @@ def select_by_label( @mainthread def select_by_index( self: MenuAppCentral, - event: ChooseMenuItemByIndexEvent, + event: MenuChooseByIndexEvent, ) -> None: self.menu_widget.select(event.index) diff --git a/ubo_app/services/000-keypad/setup.py b/ubo_app/services/000-keypad/setup.py index 89faff6f..e1f45055 100644 --- a/ubo_app/services/000-keypad/setup.py +++ b/ubo_app/services/000-keypad/setup.py @@ -143,6 +143,7 @@ def init_i2c(self: Keypad) -> None: self.on_button_event( index=MIC_INDEX, status='released' if is_mic_active else 'pressed', + pressed_buttons=[], ) # This should always be the last line of this method @@ -188,31 +189,66 @@ def key_press_cb(self: Keypad, _: object) -> None: # or risign (0->1) indicating a release action index = (int)(math.log2(change_mask)) self.logger.info('button index', extra={'button_index': index}) + + # Check for multiple button presses + pressed_buttons = [ + i for i in range(8) if i in KEY_INDEX and inputs & 1 << i == 0 + ] + # Check for rising edge or falling edge action (press or release) if (self.previous_inputs & change_mask) == 0: self.logger.info( 'Button pressed', - extra={'button': str(index)}, + extra={ + 'button': str(index), + 'pressed_buttons': pressed_buttons, + }, + ) + self.on_button_event( + index=index, + status='released', + pressed_buttons=pressed_buttons, ) - self.on_button_event(index=index, status='released') else: self.logger.info( 'Button released', - extra={'button': str(index)}, + extra={ + 'button': str(index), + 'pressed_buttons': pressed_buttons, + }, + ) + self.on_button_event( + index=index, + status='pressed', + pressed_buttons=pressed_buttons, ) - self.on_button_event(index=index, status='pressed') self.previous_inputs = inputs @staticmethod - def on_button_event(*, index: int, status: ButtonStatus) -> None: + def on_button_event( + *, + index: int, + status: ButtonStatus, + pressed_buttons: list[int], + ) -> None: from ubo_app.store.main import store if index in KEY_INDEX: if status == 'pressed': - store.dispatch(KeypadKeyPressAction(key=KEY_INDEX[index])) + store.dispatch( + KeypadKeyPressAction( + key=KEY_INDEX[index], + pressed_keys={KEY_INDEX[i] for i in pressed_buttons}, + ), + ) elif status == 'released': - store.dispatch(KeypadKeyReleaseAction(key=KEY_INDEX[index])) + store.dispatch( + KeypadKeyReleaseAction( + key=KEY_INDEX[index], + pressed_keys={KEY_INDEX[i] for i in pressed_buttons}, + ), + ) if index == MIC_INDEX: store.dispatch( AudioSetMuteStatusAction( diff --git a/ubo_app/services/020-keyboard/setup.py b/ubo_app/services/020-keyboard/setup.py index d01bca6f..826d99ba 100644 --- a/ubo_app/services/020-keyboard/setup.py +++ b/ubo_app/services/020-keyboard/setup.py @@ -4,10 +4,9 @@ from typing import TYPE_CHECKING, Literal from kivy.core.window import Keyboard, Window, WindowBase -from redux import FinishAction, FinishEvent +from redux import FinishEvent from ubo_app.store.main import store -from ubo_app.store.operations import ScreenshotEvent, SnapshotEvent from ubo_app.store.services.audio import AudioDevice, AudioToggleMuteStatusAction from ubo_app.store.services.keypad import Key, KeypadKeyPressAction @@ -28,23 +27,23 @@ def on_keyboard( # noqa: C901 if modifier == []: if key in (Keyboard.keycodes['up'], Keyboard.keycodes['k']): - store.dispatch(KeypadKeyPressAction(key=Key.UP)) + store.dispatch(KeypadKeyPressAction(key=Key.UP, pressed_keys=set())) elif key in (Keyboard.keycodes['down'], Keyboard.keycodes['j']): - store.dispatch(KeypadKeyPressAction(key=Key.DOWN)) + store.dispatch(KeypadKeyPressAction(key=Key.DOWN, pressed_keys=set())) elif key == Keyboard.keycodes['1']: - store.dispatch(KeypadKeyPressAction(key=Key.L1)) + store.dispatch(KeypadKeyPressAction(key=Key.L1, pressed_keys=set())) elif key == Keyboard.keycodes['2']: - store.dispatch(KeypadKeyPressAction(key=Key.L2)) + store.dispatch(KeypadKeyPressAction(key=Key.L2, pressed_keys=set())) elif key == Keyboard.keycodes['3']: - store.dispatch(KeypadKeyPressAction(key=Key.L3)) + store.dispatch(KeypadKeyPressAction(key=Key.L3, pressed_keys=set())) elif key in ( Keyboard.keycodes['left'], Keyboard.keycodes['escape'], Keyboard.keycodes['h'], ): - store.dispatch(KeypadKeyPressAction(key=Key.BACK)) + store.dispatch(KeypadKeyPressAction(key=Key.BACK, pressed_keys=set())) elif key == Keyboard.keycodes['backspace']: - store.dispatch(KeypadKeyPressAction(key=Key.HOME)) + store.dispatch(KeypadKeyPressAction(key=Key.HOME, pressed_keys=set())) elif key == Keyboard.keycodes['m']: from ubo_app.store.main import store @@ -54,11 +53,17 @@ def on_keyboard( # noqa: C901 ), ) elif key == Keyboard.keycodes['p']: - store.dispatch(ScreenshotEvent()) + store.dispatch( + KeypadKeyPressAction(key=Key.L1, pressed_keys={Key.HOME, Key.L1}), + ) elif key == Keyboard.keycodes['s']: - store.dispatch(SnapshotEvent()) + store.dispatch( + KeypadKeyPressAction(key=Key.L2, pressed_keys={Key.HOME, Key.L2}), + ) elif key == Keyboard.keycodes['q']: - store.dispatch(FinishAction()) + store.dispatch( + KeypadKeyPressAction(key=Key.BACK, pressed_keys={Key.HOME, Key.BACK}), + ) def init_service() -> None: diff --git a/ubo_app/services/090-web-ui/setup.py b/ubo_app/services/090-web-ui/setup.py index 292778bf..3a6c5d43 100644 --- a/ubo_app/services/090-web-ui/setup.py +++ b/ubo_app/services/090-web-ui/setup.py @@ -18,6 +18,7 @@ async def init_service() -> None: """Initialize the web-ui service.""" + _ = [] app = Quart( 'ubo-app', template_folder=(Path(__file__).parent / 'templates').absolute().as_posix(), @@ -48,6 +49,8 @@ async def inputs_form() -> str: await asyncio.sleep(0.2) return await render_template('index.jinja2', inputs=inputs(), re=re) + _.append(inputs_form) + if WEB_UI_DEBUG_MODE: @app.errorhandler(Exception) @@ -56,6 +59,8 @@ async def handle_error(_: Exception) -> str: return f'
{traceback.format_exc()}
' + _.append(handle_error) + store.subscribe_event(FinishEvent, shutdown_event.set) async def wait_for_shutdown() -> None: diff --git a/ubo_app/side_effects.py b/ubo_app/side_effects.py index 77cfe6bb..e73ffbed 100644 --- a/ubo_app/side_effects.py +++ b/ubo_app/side_effects.py @@ -66,13 +66,14 @@ def write_image(image_path: Path, array: NDArray) -> None: import png png.Writer( + alpha=True, width=array.shape[0], height=array.shape[1], greyscale=False, # pyright: ignore [reportArgumentType] bitdepth=8, ).write( image_path.open('wb'), - array.reshape(-1, array.shape[0] * 3).tolist(), + array.reshape(-1, array.shape[1] * 4).tolist(), ) @@ -90,7 +91,11 @@ def take_screenshot() -> None: def take_snapshot() -> None: """Take a snapshot of the store.""" - path = Path('snapshot.json') + counter = 0 + while (path := Path(f'snapshots/ubo-screenshot-{counter:03d}.png')).exists(): + counter += 1 + + path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(store.snapshot, indent=2)) diff --git a/ubo_app/store/core/__init__.py b/ubo_app/store/core/__init__.py index b413637e..356276b2 100644 --- a/ubo_app/store/core/__init__.py +++ b/ubo_app/store/core/__init__.py @@ -17,6 +17,11 @@ class SettingsCategory(StrEnum): DOCKER = 'Docker' +class MenuScrollDirection(StrEnum): + UP = 'up' + DOWN = 'down' + + SETTINGS_ICONS = { SettingsCategory.NETWORK: '󰛳', SettingsCategory.REMOTE: '󰑔', @@ -81,21 +86,29 @@ class MainEvent(BaseEvent): ... class InitEvent(MainEvent): ... -class ChooseMenuItemByIconEvent(MainEvent): +class MenuEvent(MainEvent): ... + + +class MenuGoBackEvent(MenuEvent): ... + + +class MenuGoHomeEvent(MenuEvent): ... + + +class MenuChooseByIconEvent(MenuEvent): icon: str -class ChooseMenuItemByLabelEvent(MainEvent): +class MenuChooseByLabelEvent(MenuEvent): label: str -class ChooseMenuItemByIndexEvent(MainEvent): +class MenuChooseByIndexEvent(MenuEvent): index: int -ChooseMenuItemEvent = ( - ChooseMenuItemByIconEvent | ChooseMenuItemByLabelEvent | ChooseMenuItemByIndexEvent -) +class MenuScrollEvent(MenuEvent): + direction: MenuScrollDirection class OpenApplicationEvent(MainEvent): diff --git a/ubo_app/store/core/reducer.py b/ubo_app/store/core/reducer.py index a1dfc977..cce5464f 100644 --- a/ubo_app/store/core/reducer.py +++ b/ubo_app/store/core/reducer.py @@ -7,6 +7,7 @@ from redux import ( CompleteReducerResult, + FinishEvent, InitAction, InitializationActionError, ReducerResult, @@ -16,6 +17,12 @@ InitEvent, MainAction, MainState, + MenuChooseByIndexEvent, + MenuEvent, + MenuGoBackEvent, + MenuGoHomeEvent, + MenuScrollDirection, + MenuScrollEvent, PowerEvent, PowerOffAction, PowerOffEvent, @@ -25,14 +32,12 @@ RegisterSettingAppAction, SetMenuPathAction, ) +from ubo_app.store.operations import ScreenshotEvent, SnapshotEvent from ubo_app.store.services.audio import AudioChangeVolumeAction, AudioDevice from ubo_app.store.services.keypad import ( Key, - KeypadEvent, KeypadKeyPressAction, - KeypadKeyPressEvent, KeypadKeyReleaseAction, - KeypadKeyReleaseEvent, ) @@ -42,7 +47,7 @@ def reducer( ) -> ReducerResult[ MainState, AudioChangeVolumeAction, - KeypadEvent | InitEvent | PowerEvent, + InitEvent | MenuEvent | ScreenshotEvent | SnapshotEvent | FinishEvent | PowerEvent, ]: from ubo_gui.menu.types import Item, Menu, SubMenuItem, menu_items @@ -57,30 +62,85 @@ def reducer( raise InitializationActionError(action) if isinstance(action, KeypadKeyPressAction): - actions: list[AudioChangeVolumeAction] = [] - events: list[KeypadKeyPressEvent] = [] - if action.key == Key.UP and state.depth == 1: - actions = [ - AudioChangeVolumeAction( - amount=0.05, - device=AudioDevice.OUTPUT, - ), - ] - elif action.key == Key.DOWN and state.depth == 1: - actions = [ - AudioChangeVolumeAction( - amount=-0.05, - device=AudioDevice.OUTPUT, - ), - ] + if action.pressed_keys == {action.key}: + if action.key == Key.UP and state.depth == 1: + return CompleteReducerResult( + state=state, + actions=[ + AudioChangeVolumeAction( + amount=0.05, + device=AudioDevice.OUTPUT, + ), + ], + ) + if action.key == Key.DOWN and state.depth == 1: + return CompleteReducerResult( + state=state, + actions=[ + AudioChangeVolumeAction( + amount=-0.05, + device=AudioDevice.OUTPUT, + ), + ], + ) + + if action.key == Key.L1: + return CompleteReducerResult( + state=state, + events=[MenuChooseByIndexEvent(index=0)], + ) + if action.key == Key.L2: + return CompleteReducerResult( + state=state, + events=[MenuChooseByIndexEvent(index=1)], + ) + if action.key == Key.L3: + return CompleteReducerResult( + state=state, + events=[MenuChooseByIndexEvent(index=2)], + ) + if action.key == Key.UP: + return CompleteReducerResult( + state=state, + events=[MenuScrollEvent(direction=MenuScrollDirection.UP)], + ) + if action.key == Key.DOWN: + return CompleteReducerResult( + state=state, + events=[MenuScrollEvent(direction=MenuScrollDirection.DOWN)], + ) else: - events = [KeypadKeyPressEvent(key=action.key, time=action.time)] - return CompleteReducerResult(state=state, actions=actions, events=events) + if action.pressed_keys == {Key.HOME, Key.L1} and action.key == Key.L1: + return CompleteReducerResult( + state=state, + events=[ScreenshotEvent()], + ) + if action.pressed_keys == {Key.HOME, Key.L2} and action.key == Key.L2: + return CompleteReducerResult( + state=state, + events=[SnapshotEvent()], + ) + if action.pressed_keys == {Key.HOME, Key.BACK} and action.key == Key.BACK: + return CompleteReducerResult( + state=state, + events=[FinishEvent()], + ) + return state + if isinstance(action, KeypadKeyReleaseAction): - return CompleteReducerResult( - state=state, - events=[KeypadKeyReleaseEvent(key=action.key, time=action.time)], - ) + if len(action.pressed_keys) == 0: + if action.key == Key.BACK: + return CompleteReducerResult( + state=state, + events=[MenuGoBackEvent()], + ) + if action.key == Key.HOME: + return CompleteReducerResult( + state=state, + events=[MenuGoHomeEvent()], + ) + + return state if isinstance(action, RegisterSettingAppAction): parent_index = 1 diff --git a/ubo_app/store/main.py b/ubo_app/store/main.py index 03ff3de6..81bcb8c7 100644 --- a/ubo_app/store/main.py +++ b/ubo_app/store/main.py @@ -41,7 +41,7 @@ from ubo_app.store.services.display import DisplayAction, DisplayEvent from ubo_app.store.services.docker import DockerAction from ubo_app.store.services.ip import IpAction, IpEvent -from ubo_app.store.services.keypad import KeypadAction, KeypadEvent +from ubo_app.store.services.keypad import KeypadAction from ubo_app.store.services.lightdm import LightDMAction from ubo_app.store.services.notifications import ( NotificationsAction, @@ -123,7 +123,6 @@ | CameraEvent | DisplayEvent | IpEvent - | KeypadEvent | NotificationsEvent | SnapshotEvent | UsersEvent diff --git a/ubo_app/store/services/keypad.py b/ubo_app/store/services/keypad.py index aa90bdf2..27f04574 100644 --- a/ubo_app/store/services/keypad.py +++ b/ubo_app/store/services/keypad.py @@ -5,7 +5,7 @@ from dataclasses import field from enum import StrEnum -from redux import BaseAction, BaseEvent +from redux import BaseAction class Key(StrEnum): @@ -20,6 +20,7 @@ class Key(StrEnum): class KeypadAction(BaseAction): key: Key + pressed_keys: set[Key] time: float = field(default_factory=time.time) @@ -33,14 +34,3 @@ class KeypadKeyPressAction(KeypadAction): ... class KeypadKeyReleaseAction(KeypadAction): ... - - -class KeypadEvent(BaseEvent): - key: Key - time: float - - -class KeypadKeyPressEvent(KeypadEvent): ... - - -class KeypadKeyReleaseEvent(KeypadEvent): ... diff --git a/ubo_app/utils/persistent_store.py b/ubo_app/utils/persistent_store.py index 5b9330ef..a104fef6 100644 --- a/ubo_app/utils/persistent_store.py +++ b/ubo_app/utils/persistent_store.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import json.decoder from pathlib import Path from typing import TYPE_CHECKING, TypeVar, cast, overload @@ -35,7 +36,7 @@ async def write(value: T) -> None: with persistent_store_lock.write_lock(): try: current_state = json.loads(Path(PERSISTENT_STORE_PATH).read_text()) - except FileNotFoundError: + except (FileNotFoundError, json.decoder.JSONDecodeError): current_state = {} serialized_value = store.serialize_value(value) current_state[key] = serialized_value @@ -84,7 +85,7 @@ def read_from_persistent_store( with persistent_store_lock.read_lock(): file_content = Path(PERSISTENT_STORE_PATH).read_text() current_state = json.loads(file_content) - except FileNotFoundError: + except (FileNotFoundError, json.decoder.JSONDecodeError): current_state = {} value = current_state.get(key) if value is None: