GUI Adapter Plugins¶
In a nutshell
This page is for developers. It describes the new, not-yet-finished way OVOS will draw things on a screen: the planned replacement for the old, deprecated GUI. The idea: instead of OVOS talking to one kind of screen directly, it sends a generic "show this weather card" message. Small plugins called adapters translate that into whatever the actual display is (a touchscreen, a web browser, even a terminal). Several adapters can run at once, so the same content shows on multiple screens. This is upcoming work, not the everyday path today. See the GUI Protocol for the current legacy screen and the Glossary for terms.
Nothing on this page is built or released yet
This page documents the in-progress replacement for the deprecated
legacy GUI. None of it is usable on a stable install today. Until it
ships, there is no generally usable OVOS GUI. Mark 2 devices keep a screen via the
legacy stack that the ovos-installer sets up. The pieces are at
different stages:
ovos-gui-api-client: a template-basedGUIInterfacealready exists and works today.ovos-legacy-mycroft-gui-pluginandovos-gui-plugin-ag-uialready implement the adapter-side contract described below (anAbstractGUIPluginsubclass registered underopm.gui_adapter). But that base class does not exist yet in any releasedovos-plugin-manager, andovos-guidoes not yet contain the router that would dispatch events to these adapters. These plugins are therefore built ahead of their own dependency. The router mentioned elsewhere on this page (connection status,handle_show_*dispatch) is not yet built either.- The formal contract is specified by
OVOS-GUI-1, an
architecture spec. The spec deliberately leaves the exact
entry-point group name and method signatures non-normative. It gives them only as an
illustrative example. The names below (
AbstractGUIPlugin,handle_show_*,opm.gui_adapter) are what the real adapter plugins linked above have already standardized on. That is why this page uses them.
Per OVOS-GUI-1, the sole per-event routing key for the adapter contract is session_id.
There is no separate site/room/location dimension. A shared/multi-room screen is expressed
by its clients sharing one session_id. The ovos-gui-plugin-ag-ui adapter already follows
this convention (every handle_show_* / lifecycle signature takes session_id: str = "default").
The legacy adapter still routes on site_id (in flux)
The shipped ovos-legacy-mycroft-gui-plugin has not yet adopted the session_id
contract. Every handle_show_* and lifecycle method takes site_id: str = "default"
instead (ovos_legacy_mycroft_gui/__init__.py), and its websocket layer routes on
site_id (send_to_clients_for_site(site_id)). The Qt6 client rework branch
(mycroft-gui-qt6) likewise still ships a separate site_id dimension (--site-id
flag, MYCROFT_SITE_ID env var) for multi-site setups. So at both the legacy adapter
and client layers, site_id is not yet gone, even though the OVOS-GUI-1 adapter contract
collapses routing to session_id. The two layers may still be reconciled.
In the rework, ovos-gui no longer renders or talks to Qt clients directly. It becomes a
router that dispatches each display event to every installed GUI adapter plugin. Each
adapter translates template events into whatever protocol it needs (Qt WebSocket,
HTTP+SSE, curses, and more). Multiple adapters can run at once, so output can reach several
display types at the same time.
Adapters can add their own extra functionality (optional)
Beyond rendering the standard display templates, a GUI adapter plugin may define its own extra bus event listeners. This exposes additional, optional capabilities that skills can choose to use. Examples include Mark 1 events / faceplate control (the Mark 1 faceplate becomes such an adapter), home screens, and custom QML. These are opt-in. A skill works without them, but can adopt the extra functionality offered by whichever adapter(s) are installed.
Entry point¶
Adapters are discovered via the opm.gui_adapter entry-point group:
The class extends AbstractGUIPlugin from ovos_plugin_manager.templates.gui.
AbstractGUIPlugin¶
# NOTE: this import fails today — AbstractGUIPlugin does not exist in any released
# ovos-plugin-manager yet (see the warning above). This is a forward-looking example.
from ovos_plugin_manager.templates.gui import AbstractGUIPlugin
class MyGUIPlugin(AbstractGUIPlugin):
def __init__(self, config: dict, bus=None):
super().__init__(config, bus)
# start your server / rendering pipeline here
Template handlers¶
Override any of the following methods to render a template. Each receives skill_id: str
(the namespace), data: dict (current session data), and session_id: str (the routing
id, default "default"). All default to no-ops, so partial implementations are valid.
| Method | Template | Key data keys |
|---|---|---|
handle_show_idle |
SYSTEM_idle |
none |
handle_show_loading |
SYSTEM_loading |
label |
handle_show_status |
SYSTEM_status |
label, success |
handle_show_error |
SYSTEM_error |
label, detail |
handle_show_text |
SYSTEM_text |
text, title |
handle_show_image |
SYSTEM_image |
image, title, caption, fill |
handle_show_animated_image |
SYSTEM_animated_image |
same as image |
handle_show_list |
SYSTEM_list |
title, items |
handle_show_grid |
SYSTEM_grid |
title, items |
handle_show_table |
SYSTEM_table |
title, columns, rows |
handle_show_html |
SYSTEM_html |
html |
handle_show_url |
SYSTEM_url |
url |
handle_show_audio_player |
SYSTEM_audio_player |
title, artist, album, image, playing, position, duration |
handle_show_video_player |
SYSTEM_video_player |
uri, title, playing |
handle_show_media_player |
SYSTEM_media_player |
media fields |
handle_show_clock |
SYSTEM_clock |
none |
handle_show_timer |
SYSTEM_timer |
end_time, label, count_up |
handle_show_weather |
SYSTEM_weather |
current_temp, min_temp, max_temp, condition, icon, location |
handle_show_map |
SYSTEM_map |
latitude, longitude, zoom, label |
handle_show_confirm |
SYSTEM_confirm |
question |
handle_show_select |
SYSTEM_select |
prompt, items |
handle_show_face |
SYSTEM_face |
sleeping |
handle_show_ocp_now_playing |
SYSTEM_ocp_now_playing |
media fields |
handle_show_ocp_search |
SYSTEM_ocp_search |
search-result fields |
handle_show_ocp_playlist |
SYSTEM_ocp_playlist |
playlist fields |
The three SYSTEM_ocp_* rows are a legacy-adapter extension, not part of OVOS-GUI-1's
closed vocabulary of 22 templates (see GUI Service): the legacy
mycroft-gui adapter implements them, the ag-ui adapter implements only the spec's 22, and a
new spec-conformant adapter is not required to handle them.
Lifecycle hooks¶
def on_namespace_activated(self, skill_id: str, session_id: str = "default") -> None:
"""Called when a namespace moves to the top of the display stack."""
def on_namespace_deactivated(self, skill_id: str, session_id: str = "default") -> None:
"""Called when a namespace is removed from the display stack."""
def on_idle(self) -> None:
"""Called when the display returns to the idle/resting state."""
def on_session_update(self, skill_id: str, data: dict, session_id: str = "default") -> None:
"""Called whenever skill session data changes (e.g. gui['key'] = value)."""
def on_status_event(self, event_name: str, data: dict, session_id: str = "default") -> None:
"""Called for system status events (wakeword, utterance handled, etc.)."""
Dispatch¶
The (not-yet-built) router is expected to map each SYSTEM_* template name to the matching
handle_show_* method. The naming convention handle_show_<template suffix> is already fixed
by the adapter plugins that implement this contract today. An adapter overrides only the
individual handle_show_* methods it cares about. Unimplemented ones default to no-ops.
Connection status¶
The legacy gui.status.request bus message already exists today and answers
whether any display is connected. An adapter is expected to optionally provide an
any_client_connected() -> bool method, so a future router can fold it into that response. The
legacy adapter already ships a concrete module-level any_client_connected()
(ovos_legacy_mycroft_gui/websocket.py) as a reference implementation. Since the base class and
router are not yet built, treat the exact hook-up (duck-typing or otherwise) as illustrative:
Built-in adapters¶
| Repo | PyPI package | Entry-point name | Class | Description |
|---|---|---|---|---|
ovos-legacy-mycroft-gui-plugin |
ovos-legacy-mycroft-gui-plugin |
ovos-legacy-mycroft-gui |
LegacyMycoftGuiPlugin |
Tornado WebSocket to Qt / mycroft-gui clients, also runs HomescreenManager |
ovos-gui-plugin-ag-ui |
ovos-gui-plugin-ag-ui |
ovos-gui-plugin-ag-ui |
AgUiGuiPlugin |
ag-ui protocol adapter: renders GUI state/template events as ag-ui SSE events for ag-ui / CopilotKit frontends |
pyhtmx-gui-client is a client, not an adapter
The pyhtmx-gui-client repo (PyPI ovos-pyhtmx-gui-client, console script pyhtmx-gui)
is not a GUI adapter plugin. It ships no AbstractGUIPlugin subclass and no
opm.gui_adapter entry point. It is a standalone FastAPI/uvicorn GUI client whose
GUIClient connects as a WebSocket client to the legacy ovos-gui protocol
(ws://localhost:18181/gui, config keys ovos-server-url / client-id), similar to a
mycroft-gui Qt client. It belongs to the legacy websocket-client path, not the adapter
architecture described here.
Writing a custom adapter¶
from ovos_plugin_manager.templates.gui import AbstractGUIPlugin
class TerminalGUIPlugin(AbstractGUIPlugin):
"""Render OVOS GUI templates in the terminal."""
def __init__(self, config, bus=None):
super().__init__(config, bus)
def handle_show_text(self, skill_id, data, session_id="default"):
print(data.get("title", ""))
print(data.get("text", ""))
def handle_show_weather(self, skill_id, data, session_id="default"):
print(data.get("location", ""),
f"{data['current_temp']}° — {data['condition']}")
Register in pyproject.toml:
Configuration¶
Adapter configuration is expected to live under a gui.adapters.<entry-point-name> key in
mycroft.conf, following the existing get_plugin_config() convention used by
other OPM plugin types. It is expected to be passed as the config dict to the adapter's __init__. This
exact key path is not yet fixed by any released code. Treat it as illustrative:
The shipped legacy adapter, however, reads concrete config today rather than the
gui.adapters.* path. It consumes the top-level [gui_websocket] section via ovos_config
(host default 127.0.0.1, base_port default 18181, route default /gui) and
gui.default_qt_version (default 5):
{
"gui_websocket": {
"host": "127.0.0.1",
"base_port": 18181,
"route": "/gui"
},
"gui": {
"default_qt_version": 5
}
}
Multi-modal rendering¶
With two adapters installed (e.g. legacy Qt and ag-ui), every display event is dispatched to both:
self.gui.show_weather(…)
↓
ovos-gui router, fanned out to every installed opm.gui_adapter plugin
├──→ LegacyMycoftGuiPlugin.handle_show_weather(…) → Qt client
└──→ AgUiGuiPlugin.handle_show_weather(…) → ag-ui / CopilotKit frontend
Both displays update simultaneously and independently.
Read next: Agents Overview Related: GUI Protocol · Home Screen · Plugin Manager