The MixlarPlugin API
Every Mixlar plugin is a single Python class that subclasses MixlarPlugin. You
set a handful of metadata attributes, then override only the hooks you
actually use — everything else has a safe, do-nothing default.
from mixlar import MixlarPlugin
class MyPlugin(MixlarPlugin): plugin_id = "my_plugin" plugin_name = "My Plugin" plugin_version = "1.0.0" plugin_author = "You" plugin_description = "What it does." plugin_icon = "fa5s.puzzle-piece" # qtawesome name plugin_icon_color = "#FF4D14" widget_id = "my_widget" # pairs with widgets/my_widget/Metadata attributes
Section titled “Metadata attributes”| Attribute | Default | Meaning |
|---|---|---|
plugin_id |
"" |
Unique id; should match plugin.json’s "id" |
plugin_name |
"Unnamed Plugin" |
Display name (manifest "name" wins if both are set) |
plugin_version |
"0.0.0" |
Manifest "version" wins |
plugin_author |
"" |
Shown on the plugin card |
plugin_description |
"" |
Shown on the plugin card |
plugin_icon |
"fa5s.puzzle-piece" |
qtawesome icon name |
plugin_icon_color |
"" |
Hex color for the icon |
plugin_icons_dir |
"" |
Folder of PNGs installed into the app’s icon set |
widget_id |
"" |
Pairs with widgets/<widget_id>/widget.json; namespaces every push_widget_data key |
See Plugin Packages for how these interact with plugin.json.
Lifecycle
Section titled “Lifecycle”| Method | Signature | Purpose |
|---|---|---|
on_load |
(self, settings) -> None |
Called once at startup with the host settings object. The base implementation stashes it as self._settings for _pget/_pset |
on_unload |
(self) -> None |
Called on app shutdown / warm-reload |
Macro hooks
Section titled “Macro hooks”Four methods the app’s macro editor and executor call. You can override them
directly, or declare actions once with MacroActions and @action (below) and
skip writing them by hand.
| Method | Signature | Purpose |
|---|---|---|
get_macro_actions |
(self) -> list[(id, name)] |
Actions offered in the macro step picker |
get_macro_action_groups |
(self) -> list[(group, [(id, name), ...])] |
Same, bucketed into tabs |
get_macro_action_icons |
(self) -> dict[id, "fa5s.icon"] |
Per-action icons |
get_macro_step_defaults |
(self) -> dict |
Default fields for a newly-added step |
execute_macro_step |
(self, step: dict) -> None |
Run the action (called off the GUI thread) |
describe_macro_step |
(self, step: dict) -> str |
One-line summary shown in the step list |
build_macro_editor |
(self, step, row, editor_lay, upd, upd_refresh, lbl) -> None |
Add custom Qt editor widgets under the action selector |
prime_client |
(self, settings) -> None |
Pre-init a client (e.g. open a socket) on the main thread before execution |
MacroActions + @action
Section titled “MacroActions + @action”from mixlar import MixlarPluginfrom mixlar.macros import MacroActions, action
class MyPlugin(MacroActions, MixlarPlugin): plugin_id = "my_plugin"
@action("mute", "Mute Mic", icon="fa5s.microphone-slash", group="Audio") def _mute(self, step): ...Put MacroActions ahead of MixlarPlugin in the base list so its methods
win the MRO. It scans the class once (cached) for @action-decorated methods
and derives get_macro_actions, get_macro_action_groups,
get_macro_action_icons, and execute_macro_step from them — dispatch reads
step["<plugin_id>_action"] (the key convention the app’s built-in editor
writes), falling back to a plain "action" key. Override any of the four
methods directly on your subclass and that override wins — nothing here is
mandatory, it’s purely a shortcut for the common case.
See Macros for the full walkthrough.
Slider hooks
Section titled “Slider hooks”| Method | Signature | Purpose |
|---|---|---|
get_slider_mode |
(self) -> (mode_id, name) | None |
Opt a fader into this plugin’s mode |
build_slider_config_ui |
(self, idx, parent_layout, settings) -> None |
Build the fader’s config panel |
save_slider_config |
(self, idx) -> dict |
Settings to persist for that fader |
handle_slider_value |
(self, idx: int, value: int, pct: float) -> None |
Fader moved (value 0-100, pct 0.0-1.0) |
get_slider_color |
(self, idx) -> str | None |
Fixed hex color for the fader, or default |
SliderMode
Section titled “SliderMode”from mixlar import MixlarPluginfrom mixlar.sliders import SliderMode
class MyPlugin(SliderMode, MixlarPlugin): plugin_id = "my_plugin" slider_mode_id = "my_plugin_slider" slider_mode_name = "My Plugin" slider_color = "#0a84ff"
def handle_slider_value(self, idx, value, pct): ... # you still implement this — it's the one hook with real side effectsSliderMode derives get_slider_mode/get_slider_color from plain class
attributes. Leave slider_mode_id blank (the default) to opt out entirely —
get_slider_mode() then returns None, same as the base class.
See Sliders for the full walkthrough.
Settings UI
Section titled “Settings UI”| Method | Signature | Purpose |
|---|---|---|
build_settings_ui |
(self, layout, settings) -> None |
Build a Qt settings panel (imperative path) |
has_settings_ui |
(self) -> bool |
True if build_settings_ui is overridden |
For a declarative alternative that needs no Qt code, ship a
settings.schema.json instead — see Settings.
Device widget hooks
Section titled “Device widget hooks”| Method | Signature | Purpose |
|---|---|---|
on_widget_event |
(self, widget_id, element_id, action) -> None |
An interactive element fired ("press"/"hold" for buttons, "on"/"off" for toggles, a value-carrying action for sliders). Broadcast to every plugin; filter by the ids you own |
on_widget_shown |
(self, widget_id) -> None |
Your paired widget came on screen — push full state, start timers |
on_widget_hidden |
(self, widget_id) -> None |
Your paired widget left the screen — go quiet, stop timers |
widget_bundle_dir |
(self) -> str | None |
Path to <plugin dir>/widgets/<widget_id>/, if it exists |
widget_bundle_dirs |
(self) -> list[(widget_id, dir)] |
Every bundled widget declared in the manifest |
push_widget_data |
(self, key, value, widget=None) -> bool |
Push a live value; namespaced <widget_id>.<key> automatically; False no-op with no device attached |
push_widget_image |
(self, key, source, w, h, widget=None) -> bool |
Stream an image to an img element; blocks — call from a worker thread |
Full widget-authoring detail, including the element reference and the wire protocol, lives under Widgets — see in particular Elements and Data & Events.
SystemEventsMixin
Section titled “SystemEventsMixin”from mixlar import MixlarPluginfrom mixlar.macros import MacroActionsfrom mixlar.events import SystemEventsMixin
class MyPlugin(SystemEventsMixin, MacroActions, MixlarPlugin): def on_device_connect(self): self.push_widget_data("status", "online")Six no-op-by-default hooks: on_app_launch(exe), on_app_terminate(exe),
on_system_wake(), on_device_connect(), on_device_disconnect(), and
on_deep_link(url). Override only what you need.
Settings helpers
Section titled “Settings helpers”| Method | Signature | Purpose |
|---|---|---|
_pget |
(self, key, default="") -> Any |
Read a namespaced setting (plugin_<id>_<key>); goes through the host settings object when present, an in-memory dict off-app |
_pset |
(self, key, value) -> None |
Write a namespaced setting, same storage |
These are the low-level primitives build_settings_ui and a
settings.schema.json-driven UI both end up writing through — reach for them
directly for anything not covered by the declarative schema.
Putting it together
Section titled “Putting it together”- Subclass
MixlarPluginand set the metadata attributes. - Mix in
MacroActions,SliderMode, and/orSystemEventsMixinahead ofMixlarPluginin the base list, depending on which surfaces you use. - Override only the hooks you need — every other method already has a safe default.
- Validate and run it locally with the Mixlar CLI before packaging and signing it — see Pack, Sign & Publish.