Skip to content

Commit

Permalink
feat:skilljson and homescreen
Browse files Browse the repository at this point in the history
register utterance examples from skill.json with homescreen

register handler with homescreen app drawer

companion to OpenVoiceOS/ovos-skill-homescreen#130
  • Loading branch information
JarbasAl committed Nov 14, 2024
1 parent 5abc2ad commit 09165cf
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 9 deletions.
18 changes: 18 additions & 0 deletions ovos_workshop/decorators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,21 @@ def real_decorator(func):
return func

return real_decorator


def homescreen_app(icon: str):
"""
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)
"""

def real_decorator(func):
# Store the icon inside the function
# This will be used later to call register_homescreen_app
func.homescreen_icon = icon
return func

return real_decorator
21 changes: 18 additions & 3 deletions ovos_workshop/resource_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
# limitations under the License.
#
"""Handling of skill data such as intents and regular expressions."""
import json
import re
from collections import namedtuple
from os import walk
from os.path import dirname
from pathlib import Path
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Dict

from langcodes import tag_distance
from ovos_config.config import Configuration
Expand All @@ -40,7 +41,8 @@
"template",
"vocabulary",
"word",
"qml"
"qml",
"json"
]
)

Expand Down Expand Up @@ -414,6 +416,14 @@ def load(self):
return str(self.file_path)


class JsonFile(ResourceFile):
def load(self) -> Dict[str, str]:
if self.file_path is not None:
with open(self.file_path) as f:
return json.load(f)
return {}


class DialogFile(ResourceFile):
"""Defines a dialog file, which is used instruct TTS what to speak."""

Expand Down Expand Up @@ -646,14 +656,19 @@ def _define_resource_types(self) -> SkillResourceTypes:
template=ResourceType("template", ".template", self.language),
vocabulary=ResourceType("vocab", ".voc", self.language),
word=ResourceType("word", ".word", self.language),
qml=ResourceType("qml", ".qml")
qml=ResourceType("qml", ".qml"),
json=ResourceType("json", ".json")
)
for resource_type in resource_types.values():
if self.skill_id:
resource_type.locate_user_directory(self.skill_id)
resource_type.locate_base_directory(self.skill_directory)
return SkillResourceTypes(**resource_types)

def load_json_file(self, name: str = "skill.json") -> Dict[str, str]:
jsonf = JsonFile(self.types.json, name)
return jsonf.load()

def load_dialog_file(self, name: str,
data: Optional[dict] = None) -> List[str]:
"""
Expand Down
64 changes: 58 additions & 6 deletions ovos_workshop/skills/ovos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import os
import re
import shutil
import sys
import time
import traceback
Expand All @@ -16,8 +17,6 @@
import binascii
from json_database import JsonStorage
from langcodes import closest_match
from ovos_number_parser import pronounce_number, extract_number
from ovos_yes_no_solver import YesNoSolver
from ovos_bus_client import MessageBusClient
from ovos_bus_client.apis.enclosure import EnclosureAPI
from ovos_bus_client.apis.gui import GUIInterface
Expand All @@ -26,7 +25,9 @@
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, extract_number
from ovos_plugin_manager.language import OVOSLangTranslationFactory, OVOSLangDetectionFactory
from ovos_utils import camel_case_split, classproperty
from ovos_utils.dialog import get_dialog, MustacheDialogRenderer
Expand All @@ -38,9 +39,12 @@
from ovos_utils.lang import standardize_lang_tag
from ovos_utils.log import LOG
from ovos_utils.parse import match_one
from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap, ProcessState
from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap
from ovos_utils.process_utils import RuntimeRequirements
from ovos_utils.skills import get_non_properties
from ovos_yes_no_solver import YesNoSolver
from padacioso import IntentContainer

from ovos_workshop.decorators.killable import AbortEvent, killable_event, \
AbortQuestion
from ovos_workshop.decorators.layers import IntentLayers
Expand All @@ -51,7 +55,6 @@
CoreResources, find_resource, SkillResources
from ovos_workshop.settings import PrivateSettings
from ovos_workshop.settings import SkillSettingsManager
from padacioso import IntentContainer


def simple_trace(stack_trace: List[str]) -> str:
Expand Down Expand Up @@ -811,7 +814,9 @@ def _startup(self, bus: MessageBusClient, skill_id: str = ""):
if self._enable_settings_manager:
self._init_settings_manager()
self.load_data_files()
self._register_skill_json()
self._register_decorated()
self._register_app_launcher()
self.register_resting_screen()

self.status.set_started()
Expand All @@ -829,6 +834,34 @@ def _startup(self, bus: MessageBusClient, skill_id: str = ""):
LOG.debug(e2)
raise e

def _register_skill_json(self, root_directory: Optional[str] = None):
"""Load skill.json metadata found under locale folder and register with homescreen"""
root_directory = root_directory or self.res_dir
for lang in self.native_langs:
resources = self.load_lang(root_directory, lang)
if resources.types.json.base_directory is None:
self.log.debug(f'No skill.json loaded for {lang}')
else:
skill_meta = resources.load_json_file("skill.json")
utts = skill_meta.get("examples", [])
if utts:
self.log.info(f"Registering example utterances with homescreen for lang: {lang} - {utts}")
self.bus.emit(Message("homescreen.register.examples",
{"skill_id": self.skill_id, "utterances": utts}))

def _register_app_launcher(self):
# homescreen might load after this skill and miss the original events
self.add_event("homescreen.metadata.get", self.handle_homescreen_loaded)

# register app launcher if registered via decorator
for attr_name in get_non_properties(self):
method = getattr(self, attr_name)
if hasattr(method, 'homescreen_icon'):
event = f"{self.skill_id}.{method.__name__}.homescreen.app"
icon = getattr(method, 'homescreen_icon')
self.register_homescreen_app(icon=icon, event=event)
self.add_event(event, method)

def _init_settings(self):
"""
Set up skill settings. Defines settings in the specified file path,
Expand Down Expand Up @@ -882,6 +915,22 @@ def _init_settings_manager(self):
"""
self.settings_manager = SkillSettingsManager(self)

def register_homescreen_app(self, icon: 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.root_dir}/gui/{icon}"
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,
"event": event}))

def register_resting_screen(self):
"""
Registers resting screen from the resting_screen_handler decorator.
Expand Down Expand Up @@ -927,8 +976,6 @@ def shutdown_handler(message):
self.add_event(f'{self.skill_id}.idle', handler,
speak_errors=False)

return

def _start_filewatcher(self):
"""
Start watching settings for file changes if settings file exists and
Expand Down Expand Up @@ -1464,6 +1511,11 @@ def register_regex(self, regex_str: str, lang: Optional[str] = None):
self.intent_service.register_adapt_regex(regex, lang=standardize_lang_tag(lang or self.lang))

# event/intent registering internal handlers
def handle_homescreen_loaded(self, message: Message):
"""homescreen loaded, we should re-register any metadata we want to provide"""
self._register_skill_json()
self._register_app_launcher()

def handle_enable_intent(self, message: Message):
"""
Listener to enable a registered intent if it belongs to this skill.
Expand Down

0 comments on commit 09165cf

Please sign in to comment.