diff --git a/CHANGELOG.md b/CHANGELOG.md index ab7fe2a3..172a974c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - refactor(core): use `dpkg-query` instead of `apt` python api as loading `Cache` in `apt` is slow and use it in docker service - refactor(system): add response for docker commands and service commands +- feat(lightdm): add installation options for lightdm package ## Version 0.15.9 diff --git a/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc b/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc index f9a86564..5f1b052a 100644 --- a/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc +++ b/tests/integration/results/test_services/all_services_register/store-desktop-000.jsonc @@ -81,7 +81,9 @@ }, "lightdm": { "is_active": true, - "is_enabled": true + "is_enabled": true, + "is_installed": true, + "is_installing": false }, "main": { "depth": 1, diff --git a/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc b/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc index b08e9a36..a148e7c4 100644 --- a/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc +++ b/tests/integration/results/test_services/all_services_register/store-rpi-000.jsonc @@ -81,7 +81,9 @@ }, "lightdm": { "is_active": false, - "is_enabled": true + "is_enabled": true, + "is_installed": true, + "is_installing": false }, "main": { "depth": 1, diff --git a/ubo_app/services/050-lightdm/reducer.py b/ubo_app/services/050-lightdm/reducer.py index b5746ad5..f6c3365d 100644 --- a/ubo_app/services/050-lightdm/reducer.py +++ b/ubo_app/services/050-lightdm/reducer.py @@ -19,7 +19,7 @@ def reducer( ) -> LightDMState: if state is None: if isinstance(action, InitAction): - return LightDMState(is_active=False, is_enabled=False) + return LightDMState() raise InitializationActionError(action) if isinstance(action, LightDMClearEnabledStateAction): @@ -30,5 +30,9 @@ def reducer( state = replace(state, is_active=action.is_active) if action.is_enabled is not None: state = replace(state, is_enabled=action.is_enabled) + if action.is_installed is not None: + state = replace(state, is_installed=action.is_installed) + if action.is_installing is not None: + state = replace(state, is_installing=action.is_installing) return state return state diff --git a/ubo_app/services/050-lightdm/setup.py b/ubo_app/services/050-lightdm/setup.py index fcaa4a69..a6aceb61 100644 --- a/ubo_app/services/050-lightdm/setup.py +++ b/ubo_app/services/050-lightdm/setup.py @@ -5,7 +5,8 @@ import asyncio from typing import TYPE_CHECKING -from ubo_gui.menu.types import ActionItem, HeadlessMenu, Item, Menu +from ubo_gui.constants import DANGER_COLOR +from ubo_gui.menu.types import ActionItem, HeadedMenu, HeadlessMenu, Item, Menu from ubo_app.store.core import RegisterSettingAppAction, SettingsCategory from ubo_app.store.main import autorun, dispatch @@ -13,16 +14,52 @@ LightDMClearEnabledStateAction, LightDMUpdateStateAction, ) +from ubo_app.store.services.notifications import ( + Chime, + Notification, + NotificationDisplayType, + NotificationsAddAction, +) +from ubo_app.utils.apt import is_package_installed from ubo_app.utils.async_ import create_task from ubo_app.utils.monitor_unit import is_unit_active, is_unit_enabled, monitor_unit from ubo_app.utils.server import send_command if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Callable from ubo_app.store.services.lightdm import LightDMState +def install_lightdm() -> None: + """Install LightDM.""" + + async def act() -> None: + dispatch(LightDMUpdateStateAction(is_installing=True)) + result = await send_command( + 'package', + 'install', + 'lightdm', + has_output=True, + ) + if result != 'installed': + dispatch( + NotificationsAddAction( + notification=Notification( + title='LightDM', + content='Failed to install', + display_type=NotificationDisplayType.STICKY, + color=DANGER_COLOR, + icon='󰜺', + chime=Chime.FAILURE, + ), + ), + ) + await check_lightdm() + + create_task(act()) + + def start_lightdm_service() -> None: """Start the LightDM service.""" create_task(send_command('service', 'lightdm', 'start')) @@ -40,7 +77,7 @@ async def act() -> None: dispatch(LightDMClearEnabledStateAction()) await send_command('service', 'lightdm', 'enable') await asyncio.sleep(5) - await check_is_lightdm_enabled() + await check_lightdm() create_task(act()) @@ -52,37 +89,62 @@ async def act() -> None: dispatch(LightDMClearEnabledStateAction()) await send_command('service', 'lightdm', 'disable') await asyncio.sleep(5) - await check_is_lightdm_enabled() + await check_lightdm() create_task(act()) @autorun(lambda state: state.lightdm) -def lightdm_items(state: LightDMState) -> Sequence[Item]: +def lightdm_menu(state: LightDMState) -> Menu: """Get the LightDM menu items.""" - return [ - ActionItem( - label='Stop' if state.is_active else 'Start', - icon='󰓛' if state.is_active else '󰐊', - action=stop_lightdm_service if state.is_active else start_lightdm_service, - ), - Item( - label='...', - icon='', + if state.is_installing: + return HeadedMenu( + title=lightdm_title, + heading='Installing LightDM', + sub_heading='This may take a few minutes', + items=[], ) - if state.is_enabled is None - else ActionItem( - label='Disable', - icon='[color=#008000]󰯄[/color]', - action=disable_lightdm_service, + if not state.is_installed: + return HeadedMenu( + title=lightdm_title, + heading='LightDM is not Installed', + sub_heading='Install it to enable desktop access on your Ubo pod', + items=[ + ActionItem( + label='Install LightDM', + icon='󰶮', + action=install_lightdm, + ), + ], ) - if state.is_enabled - else ActionItem( - label='Enable', - icon='[color=#ffff00]󰯅[/color]', - action=enable_lightdm_service, - ), - ] + return HeadlessMenu( + title=lightdm_title, + items=[ + ActionItem( + label='Stop' if state.is_active else 'Start', + icon='󰓛' if state.is_active else '󰐊', + action=stop_lightdm_service + if state.is_active + else start_lightdm_service, + ), + Item( + label='...', + icon='', + ) + if state.is_enabled is None + else ActionItem( + label='Disable', + icon='[color=#008000]󰯄[/color]', + action=disable_lightdm_service, + ) + if state.is_enabled + else ActionItem( + label='Enable', + icon='[color=#ffff00]󰯅[/color]', + action=enable_lightdm_service, + ), + ], + ) @autorun(lambda state: state.lightdm) @@ -97,30 +159,28 @@ def lightdm_title(_: LightDMState) -> str: return lightdm_icon() + ' LightDM' -async def check_is_lightdm_active() -> None: - """Check if the LightDM service is active.""" - if await is_unit_active('lightdm'): - dispatch(LightDMUpdateStateAction(is_active=True)) - else: - dispatch(LightDMUpdateStateAction(is_active=False)) - - -async def check_is_lightdm_enabled() -> None: +async def check_lightdm() -> None: """Check if the LightDM service is enabled.""" - if await is_unit_enabled('lightdm'): - dispatch(LightDMUpdateStateAction(is_enabled=True)) - else: - dispatch(LightDMUpdateStateAction(is_enabled=False)) + is_active, is_enabled, is_installed = await asyncio.gather( + is_unit_active('lightdm'), + is_unit_enabled('lightdm'), + is_package_installed('lightdm'), + ) + + dispatch( + LightDMUpdateStateAction( + is_active=is_installed and is_active, + is_enabled=is_installed and is_enabled, + is_installed=is_installed, + ), + ) -def open_lightdm_menu() -> Menu: +def open_lightdm_menu() -> Callable[[], Menu]: """Open the LightDM menu.""" - create_task(check_is_lightdm_enabled()) + create_task(check_lightdm()) - return HeadlessMenu( - title=lightdm_title, - items=lightdm_items, - ) + return lightdm_menu def init_service() -> None: @@ -137,7 +197,7 @@ def init_service() -> None: ), ) - create_task(check_is_lightdm_enabled()) + create_task(check_lightdm()) create_task( monitor_unit( 'lightdm.service', diff --git a/ubo_app/store/services/lightdm.py b/ubo_app/store/services/lightdm.py index d198cca3..5bae77a3 100644 --- a/ubo_app/store/services/lightdm.py +++ b/ubo_app/store/services/lightdm.py @@ -11,6 +11,8 @@ class LightDMAction(BaseAction): ... class LightDMUpdateStateAction(LightDMAction): is_active: bool | None = None is_enabled: bool | None = None + is_installed: bool | None = None + is_installing: bool | None = None class LightDMClearEnabledStateAction(LightDMAction): ... @@ -18,4 +20,6 @@ class LightDMClearEnabledStateAction(LightDMAction): ... class LightDMState(Immutable): is_active: bool = False - is_enabled: bool | None = None + is_enabled: bool = False + is_installed: bool = False + is_installing: bool = False