Skip to content

Commit

Permalink
feat/qml_server
Browse files Browse the repository at this point in the history
comments

enclosure everywhere, clarity nowhere

Update GUI page handling to always write gathered pages locally
Support configured GUI directory to write files to (i.e. for a mounted docker volume)

Start on URI determination with annotated TODO

Add handler to request GUI pages on `ready`

not bytes

bus upload qml

bus upload qml

better logs

better logs

feat/qml_server

setting to translate qml paths to urls

Increment Version
  • Loading branch information
JarbasAl committed Jun 28, 2023
1 parent f446173 commit c0a5b08
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 58 deletions.
84 changes: 64 additions & 20 deletions ovos_gui/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@
"""
import asyncio
import json
import os.path
from threading import Lock

from ovos_bus_client import Message, GUIMessage
from ovos_config.config import Configuration
# from ovos_gui.namespace import NamespaceManager
from ovos_utils import create_daemon
from ovos_utils.log import LOG
from tornado import ioloop
from tornado.options import parse_command_line
from tornado.web import Application
from tornado.websocket import WebSocketHandler
from ovos_gui.namespace import NamespaceManager

_write_lock = Lock()

Expand All @@ -50,10 +51,10 @@ def get_gui_websocket_config() -> dict:
return websocket_config


def create_gui_service(enclosure) -> Application:
def create_gui_service(nsmanager: NamespaceManager) -> Application:
"""
Initiate a websocket for communicating with the GUI service.
@param enclosure: NamespaceManager instance
@param nsmanager: NamespaceManager instance
"""
LOG.info('Starting message bus for GUI...')
websocket_config = get_gui_websocket_config()
Expand All @@ -62,9 +63,7 @@ def create_gui_service(enclosure) -> Application:

routes = [(websocket_config['route'], GUIWebsocketHandler)]
application = Application(routes)
# TODO: Is the NamespaceManager used by `application`, or can it be a
# GUIWebsocketHandler class variable
application.enclosure = enclosure
application.nsmanager = nsmanager
application.listen(
websocket_config['base_port'], websocket_config['host']
)
Expand Down Expand Up @@ -112,42 +111,73 @@ def on_close(self):
LOG.info('Closing {}'.format(id(self)))
GUIWebsocketHandler.clients.remove(self)

def get_client_pages(self, namespace):
nsmanager = self.application.nsmanager
skill_id = namespace.skill_id

client_pages = []

for page in namespace.pages:

if not page.url.startswith('http') and nsmanager.qml_server:
p = os.path.join(nsmanager.gui_file_path, skill_id, self.framework, page)
if os.path.isfile(p):
LOG.info(f"serving qml file {page.url} via {p}")
client_pages.append(p)
continue
p = os.path.join(nsmanager.gui_file_path, skill_id, self.framework, page)
if os.path.isfile(p):
LOG.info(f"serving qml file {page.url} via {p}")
client_pages.append(p)
continue

client_pages.append(page.url)

return client_pages

def synchronize(self):
"""
Upload namespaces, pages and data to the last connected client.
"""
namespace_pos = 0
enclosure = self.application.enclosure
nsmanager = self.application.nsmanager

for namespace in enclosure.active_namespaces:
LOG.info(f'Sync {namespace.name}')
for namespace in nsmanager.active_namespaces:
LOG.info(f'Sync {namespace.skill_id}')
# Insert namespace
self.send({"type": "mycroft.session.list.insert",
"namespace": "mycroft.system.active_skills",
"position": namespace_pos,
"data": [{"skill_id": namespace.name}]
"data": [{"skill_id": namespace.skill_id}]
})
# Insert pages
self.send({"type": "mycroft.gui.list.insert",
"namespace": namespace.name,
"namespace": namespace.skill_id,
"position": 0,
"data": [{"url": p.url} for p in namespace.pages]
"data": [{"url": url} for url in self.get_client_pages(namespace)]
})
# Insert data
for key, value in namespace.data.items():
self.send({"type": "mycroft.session.set",
"namespace": namespace.name,
"namespace": namespace.skill_id,
"data": {key: value}
})
namespace_pos += 1

@property
def framework(self):
if self._framework:
return self._framework
return "qt5"

def on_message(self, message: str):
"""
Handle a message on the GUI websocket. Deserialize the message, map
message types to valid equivalents for the core messagebus and emit
on the core messagebus.
@param message: Serialized Message
"""
LOG.info(f"Received: {message}")
parsed_message = GUIMessage.deserialize(message)
LOG.debug(f"Received: {parsed_message.msg_type}|{parsed_message.data}")

Expand Down Expand Up @@ -179,18 +209,35 @@ def on_message(self, message: str):
msg_data = parsed_message.data['data']
elif parsed_message.msg_type == 'mycroft.gui.connected':
# new client connected to GUI

# NOTE: mycroft-gui clients do this directly in core bus, dont send it to gui bus
# in those cases framework is always QT5 (backwards compat)
# new GUIs MUST send this message via gui websocket
# this means QT6 version of mycroft-gui WILL NOT WORK for now
msg_type = parsed_message.msg_type
msg_data = parsed_message.data

framework = msg_data.get("framework") # new api
if framework is None:
qt = msg_data.get("qt_version", 5) # mycroft-gui api
if int(qt) == 6:
framework = "qt6"
else:
framework = "qt5"

self._framework = framework
else:
# message not in spec
# https://github.com/MycroftAI/mycroft-gui/blob/master/transportProtocol.md
LOG.error(f"unknown GUI protocol message type, ignoring: "
f"{parsed_message}")
f"{parsed_message.msg_type}")
return

parsed_message.context["gui_framework"] = self.framework
message = Message(msg_type, msg_data, parsed_message.context)
self.application.enclosure.core_bus.emit(message)
LOG.debug('Forwarded to core bus')
LOG.info('Forwarding to core bus...')
self.application.nsmanager.core_bus.emit(message)
LOG.info('Done!')

def write_message(self, *arg, **kwarg):
"""
Expand All @@ -214,8 +261,5 @@ def send(self, data: dict):
self.write_message(s)

def check_origin(self, origin):
"""
Disable origin check to make js connections work.
"""
# TODO: Should this be implemented or deprecated
"""Disable origin check to make js connections work."""
return True
4 changes: 2 additions & 2 deletions ovos_gui/homescreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ def get_active_homescreen(self) -> Optional[dict]:
Get the active homescreen according to configuration if it is loaded
@return: Loaded homescreen with an ID matching configuration
"""
enclosure_config = Configuration().get("gui") or {}
active_homescreen = enclosure_config.get("idle_display_skill")
gui_config = Configuration().get("gui") or {}
active_homescreen = gui_config.get("idle_display_skill")
LOG.debug(f"Homescreen Manager: Active Homescreen {active_homescreen}")
for h in self.homescreens:
if h["id"] == active_homescreen:
Expand Down
Loading

0 comments on commit c0a5b08

Please sign in to comment.