From eff7d8ab6ce637e634be5ff426cd82d4bd93e57c Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Sat, 20 Jun 2026 23:51:42 +0100 Subject: [PATCH 1/2] feat!: remove skill-side home/resting screen API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The home/resting screen is a GUI render-backend concern, not a skill concern. Skills are voice apps and must not provide or register a home or resting screen (OVOS-GUI-1 §6.9: the resting display is owned by the render backend; applications MUST NOT provide/register a home or resting screen). Removes the skill-side homescreen/resting-screen API: - decorators: drop `resting_screen_handler` and `homescreen_app` - skills: delete `IdleDisplaySkill` (idle_display_skill.py) and its re-export from `ovos_workshop.skills` - OVOSSkill: drop `register_resting_screen()` and `register_homescreen_app()`, the `register_resting_screen()` init call, and the homescreen-app registration block in `_register_app_launcher` (now a no-op); remove the now-dead `__skill_id2name` helper and the unused `shutil` / `get_xdg_cache_save_path` imports - tests: delete test_idle_display_skill.py BREAKING CHANGE: `resting_screen_handler`, `homescreen_app`, `IdleDisplaySkill`, `OVOSSkill.register_resting_screen` and `OVOSSkill.register_homescreen_app` are removed. Skills can no longer register a home or resting screen; this is handled by the GUI render backend. Co-Authored-By: Claude Opus 4.8 (1M context) --- ovos_workshop/decorators/__init__.py | 37 -------- ovos_workshop/skills/__init__.py | 1 - ovos_workshop/skills/idle_display_skill.py | 93 ------------------- ovos_workshop/skills/ovos.py | 84 +---------------- .../skills/test_idle_display_skill.py | 25 ----- 5 files changed, 3 insertions(+), 237 deletions(-) delete mode 100644 ovos_workshop/skills/idle_display_skill.py delete mode 100644 test/unittests/skills/test_idle_display_skill.py diff --git a/ovos_workshop/decorators/__init__.py b/ovos_workshop/decorators/__init__.py index 429e9ca8..4de366f3 100644 --- a/ovos_workshop/decorators/__init__.py +++ b/ovos_workshop/decorators/__init__.py @@ -74,23 +74,6 @@ def real_decorator(func): return real_decorator -def resting_screen_handler(name: str): - """ - Decorator for adding a method as a resting screen handler to optionally - be shown on screen when device enters idle mode. - @param name: Name of the restring screen to register - """ - - def real_decorator(func): - # Store the resting information inside the function - # This will be used later in register_resting_screen - if not hasattr(func, 'resting_handler'): - func.resting_handler = name - return func - - return real_decorator - - def skill_api_method(func: callable): """ Decorator for adding a method to the skill's public api. Methods with this @@ -161,23 +144,3 @@ def real_decorator(func): return func return real_decorator - - -def homescreen_app(icon: str, name: Optional[str] = None): - """ - Decorator for adding a method as a homescreen app - - the icon file MUST be located under 'gui' subfolder - - @param icon: icon file to use in app drawer (relative to "gui" folder) - @param name: short name to show under the icon in app drawer - """ - - def real_decorator(func): - # Store the icon inside the function - # This will be used later to call register_homescreen_app - func.homescreen_app_icon = icon - func.homescreen_app_name = name - return func - - return real_decorator diff --git a/ovos_workshop/skills/__init__.py b/ovos_workshop/skills/__init__.py index 13c5c66f..e22ef7e0 100644 --- a/ovos_workshop/skills/__init__.py +++ b/ovos_workshop/skills/__init__.py @@ -1,5 +1,4 @@ from ovos_workshop.decorators.layers import IntentLayers from ovos_workshop.skills.ovos import OVOSSkill -from ovos_workshop.skills.idle_display_skill import IdleDisplaySkill from ovos_workshop.skills.fallback import FallbackSkill from ovos_workshop.skills.common_play import OVOSCommonPlaybackSkill diff --git a/ovos_workshop/skills/idle_display_skill.py b/ovos_workshop/skills/idle_display_skill.py deleted file mode 100644 index 788f7f11..00000000 --- a/ovos_workshop/skills/idle_display_skill.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2021 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import abc - -from ovos_bus_client.message import Message -from ovos_utils.log import LOG -from ovos_workshop.skills.ovos import OVOSSkill - - -class IdleDisplaySkill(OVOSSkill): - """ - equivalent to using @resting_handler decorator in a regular OVOSSkill - - Helper class for skills that define an idle display. - - An idle display is what shows on a device's screen when it is not in use - by other skills. i.e. a Home Screen skill. - - The idle display should show when no other skill is using the display. Some - skills use the display for a defined period of time before returning to the - idle display (e.g. Weather Skill). Some skills take control of the display - indefinitely (e.g. Timer Skill). - - The display could be a touch screen (such as on the Mark II), or an - Arduino LED array (such as on the Mark I), or any other type of display. - This base class is meant to be agnostic to the type of display. - """ - - @abc.abstractmethod - def handle_idle(self): - """ - Override this method to display the idle screen. - """ - raise NotImplementedError("Subclass must override the handle_idle method") - - def _register_system_event_handlers(self): - """ - Defines the bus events handled in this skill and their handlers. - """ - super()._register_system_event_handlers() - self.add_event("homescreen.manager.activate.display", self.handle_homescreen_request) - self.add_event("homescreen.manager.reload.list", self.register_homescreen) - self.add_event("mycroft.skills.shutdown", self._remove_homescreen_on_shutdown) - self.register_homescreen() - - def register_homescreen(self, message: Message = None): - """ - Update the internal _homescreen_entry object - for this skill and send it to the Home Screen Manager. - @param message: optional Message associated with request - """ - LOG.debug(f"Registering Homescreen {self.skill_id}") - self.bus.emit(Message("homescreen.manager.add", - {"class": "IdleDisplaySkill", # TODO - rm in ovos-gui, only for compat - "id": self.skill_id})) - - def remove_homescreen(self, message: Message): - """ - Remove this skill's homescreen_entry from the Home Screen Manager - @param message: `mycroft.skills.shutdown` message - """ - LOG.debug(f"Requesting homescreen removal of {self.skill_id}") - msg = message.forward("homescreen.manager.remove", - {"id": self.skill_id}) - self.bus.emit(msg) - - def _remove_homescreen_on_shutdown(self, message: Message): - """ - Remove this homescreen from the Home Screen Manager if requested - @param message: `mycroft.skills.shutdown` message - """ - if message.data["id"] == self.skill_id: - self.remove_homescreen(message) - - def handle_homescreen_request(self, message: Message): - """ - Display this home screen if requested by the Home Screen Manager - @param message: `homescreen.manager.activate.display` message - """ - if message.data["homescreen_id"] == self.skill_id: - self.handle_idle() - self.bus.emit(message.reply("skill.idle.displayed")) diff --git a/ovos_workshop/skills/ovos.py b/ovos_workshop/skills/ovos.py index 5de55375..6925b521 100644 --- a/ovos_workshop/skills/ovos.py +++ b/ovos_workshop/skills/ovos.py @@ -15,7 +15,6 @@ import datetime import os import re -import shutil import sys import time import traceback @@ -37,7 +36,6 @@ from ovos_bus_client.session import SessionManager, Session from ovos_bus_client.util import get_message_lang from ovos_config.config import Configuration -from ovos_config.locations import get_xdg_cache_save_path from ovos_config.locations import get_xdg_config_save_path from ovos_number_parser import pronounce_number from ovos_option_matcher_fuzzy import FuzzyOptionMatcherPlugin @@ -721,7 +719,6 @@ def _startup(self, bus: MessageBusClient, skill_id: str = ""): self._register_skill_json() self._register_decorated() self._register_app_launcher() - self.register_resting_screen() self.status.set_started() # run skill developer initialization code @@ -754,25 +751,9 @@ def _register_skill_json(self, root_directory: Optional[str] = None): {"skill_id": self.skill_id, "utterances": utts, "lang": lang})) def _register_app_launcher(self): - # register app launcher if registered via decorator - for attr_name in get_non_properties(self): - method = getattr(self, attr_name) - if hasattr(method, 'homescreen_app_icon'): - name = getattr(method, 'homescreen_app_name') - event = f"{self.skill_id}.{name or method.__name__}.homescreen.app" - icon = getattr(method, 'homescreen_app_icon') - name = name or self.__skill_id2name - LOG.debug(f"homescreen app registered: {name} - '{event}'") - self.register_homescreen_app(icon=icon, - name=name or self.skill_id, - event=event) - self.add_event(event, method, speak_errors=False) - - @property - def __skill_id2name(self) -> str: - """helper to make a nice string out of a skill_id""" - return (self.skill_id.split(".")[0].replace("_", " "). - replace("-", " ").replace("skill", "").title().strip()) + # NOTE: app launchers / homescreen apps are a GUI render-backend concern + # and are no longer registered from skills (OVOS-GUI-1 §6.9). + pass def _init_settings(self): """ @@ -821,65 +802,6 @@ def _init_skill_gui(self): self.gui = SkillGUI(self) self.gui.setup_default_handlers() - def register_homescreen_app(self, icon: str, name: str, event: str): - """the icon file MUST be located under 'gui' subfolder""" - # this path is hardcoded in ovos_gui.constants and follows XDG spec - # we use it to ensure resource availability between containers - # it is the only path assured to be accessible both by skills and GUI - GUI_CACHE_PATH = get_xdg_cache_save_path('ovos_gui') - - full_icon_path = f"{self.res_dir}/gui/{icon}" - if not os.path.isfile(full_icon_path): - self.log.error(f"failed to register homescreen app, icon does not exist: {full_icon_path}") - return - os.makedirs(f"{GUI_CACHE_PATH}/{self.skill_id}", exist_ok=True) - shared_path = f"{GUI_CACHE_PATH}/{self.skill_id}/{icon}" - shutil.copy(full_icon_path, shared_path) - - self.bus.emit(Message("homescreen.register.app", - {"skill_id": self.skill_id, - "icon": shared_path, - "name": name, - "event": event})) - - def register_resting_screen(self): - """ - Registers resting screen from the resting_screen_handler decorator. - - This only allows one screen and if two is registered only one - will be used. - """ - for attr_name in get_non_properties(self): - handler = getattr(self, attr_name) - if hasattr(handler, 'resting_handler'): - resting_name = handler.resting_handler - LOG.debug(f"{get_handler_name(handler)} is a resting screen, name: {resting_name}") - - def register(message=None, name=resting_name): - self.log.info(f'Registering resting screen {name} for {self.skill_id}.') - self.bus.emit(Message("homescreen.manager.add", - {"name": name, "id": self.skill_id})) - - register() # initial registering - - self.add_event("homescreen.manager.reload.list", register, speak_errors=False) - - def wrapper(message, cb=handler): - if message.data["homescreen_id"] == self.skill_id: - LOG.debug(f"triggering resting_handler: {get_handler_name(cb)}") - cb(message) - - self.add_event("homescreen.manager.activate.display", wrapper, speak_errors=False) - - def shutdown_handler(message): - if message.data["id"] == self.skill_id: - msg = message.forward("homescreen.manager.remove", - {"id": self.skill_id}) - self.bus.emit(msg) - - self.add_event("mycroft.skills.shutdown", shutdown_handler, speak_errors=False) - break # TODO - if multiple decorators are used what do? this is not deterministic - def _start_filewatcher(self): """ Start watching settings for file changes if settings file exists and diff --git a/test/unittests/skills/test_idle_display_skill.py b/test/unittests/skills/test_idle_display_skill.py deleted file mode 100644 index 8fa98094..00000000 --- a/test/unittests/skills/test_idle_display_skill.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2026 OpenVoiceOS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import unittest - -from ovos_workshop.skills.idle_display_skill import IdleDisplaySkill - - -class TestIdleDisplaySkill(unittest.TestCase): - def test_idle_display_skill_is_abstract(self): - """IdleDisplaySkill is abstract — it cannot be instantiated directly.""" - import abc - - self.assertTrue(hasattr(IdleDisplaySkill, 'handle_idle')) - self.assertTrue(getattr(IdleDisplaySkill.handle_idle, '__isabstractmethod__', False)) From ed6d5a684a89ecf9acc91bf03feff284768a9595 Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Tue, 23 Jun 2026 18:46:02 +0100 Subject: [PATCH 2/2] docs: document GUI-1 6.9 removal of skill-side homescreen API --- docs/decorators.md | 19 ------------------- docs/ovos-skill.md | 22 +++++++++++++++++----- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/docs/decorators.md b/docs/decorators.md index 19bf2b8a..f25a5fe1 100644 --- a/docs/decorators.md +++ b/docs/decorators.md @@ -12,7 +12,6 @@ from ovos_workshop.decorators import ( skill_api_method, adds_context, removes_context, - homescreen_app, killable_intent, killable_event, ) @@ -368,24 +367,6 @@ def reset_everything(self, message): --- -## GUI / Homescreen Decorators - -### `@homescreen_app` - -`homescreen_app` — `ovos_workshop/decorators/__init__.py:149` - -Register a method as a homescreen app launcher. The icon file must be inside the `gui/` subfolder of the skill. - -```python -from ovos_workshop.decorators import homescreen_app - -@homescreen_app(icon="my_app.png", name="My App") -def launch_app(self, message): - self.gui.show_page("main.qml") -``` - ---- - ## API Decorator ### `@skill_api_method` diff --git a/docs/ovos-skill.md b/docs/ovos-skill.md index f4b40c5a..cb3e4d08 100644 --- a/docs/ovos-skill.md +++ b/docs/ovos-skill.md @@ -46,11 +46,9 @@ Override these in your skill class: 4. Init GUI 5. Load resource files (`load_data_files`) 6. Register decorated intents (`_register_decorated`) -7. Register homescreen app if `@homescreen_app` used -8. Register resting screen if `@resting_screen_handler` used -9. Call `initialize()` -10. Check first run -11. Set status to `ready` +7. Call `initialize()` +8. Check first run +9. Set status to `ready` ### Shutdown Sequence (`default_shutdown`) @@ -187,3 +185,17 @@ This is used by `SkillManager` to defer loading until the required connectivity | `question:action.{skill_id}` | Common query callback | | `homescreen.metadata.get` | Homescreen requesting metadata | | `{skill_id}.public_api` | Skill API introspection | + +## Spec conformance — OVOS-GUI-1 §6.9 + +The skill-side home/resting-screen API has been removed: the home/resting screen +is a GUI render-backend concern, never a skill. Removed surface: + +- The `@resting_screen_handler` and `@homescreen_app` decorators. +- `OVOSSkill.register_resting_screen()` and `register_homescreen_app()`, plus the + startup hook that invoked them (`register_resting_screen` / `_register_app_launcher`, + now a no-op). +- The `IdleDisplaySkill` base class (`ovos_workshop/skills/idle_display_skill.py`). + +This is a **breaking** change. Skills must no longer register or own a homescreen; +that responsibility moves to the ovos-gui / homescreen-manager layer.