GUI Adapter Plugins¶
In a nutshell
For developers: the unfinished design for how OVOS draws things on a screen, the planned replacement for the 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. It is work in progress, not the everyday path. See the GUI Protocol for the legacy screen and the Glossary for terms.
The adapter router and base class are not released
The adapter design is the in-progress replacement for the deprecated
legacy GUI. None of it is usable on any install. 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-basedGUIInterfaceexists and works.ovos-legacy-mycroft-gui-pluginandovos-gui-plugin-ag-uiimplement the adapter-side contract described below (anAbstractGUIPluginsubclass registered underopm.gui_adapter). But that base class does not exist in any releasedovos-plugin-manager, andovos-guidoes not contain the router that would dispatch events to these adapters. These plugins are therefore built ahead of their own dependency. The router described below (connection status,handle_show_*dispatch) is not 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 standardized on. That is why the examples below use 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 follows
this convention (every handle_show_* / lifecycle signature takes session_id: str = "default").
The legacy adapter routes on site_id (in flux)
The shipped ovos-legacy-mycroft-gui-plugin does not follow 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 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 remains, even though the OVOS-GUI-1 adapter contract
collapses routing to session_id.
In the rework, ovos-gui does not render or talk 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 — AbstractGUIPlugin does not exist in any released
# ovos-plugin-manager (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
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 router (not built) is expected to map each SYSTEM_* template name to the matching
handle_show_* method. The naming convention handle_show_<template suffix> is fixed
by the adapter plugins that implement this contract. 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 exists 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 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 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__. No released code
fixes this exact key path. Treat it as illustrative:
The shipped legacy adapter, however, reads concrete config 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