Skip to content

System events

mixlar.events gives your plugin hooks for OS- and app-level events — a tracked foreground app launching, the M1X connecting over USB, the system waking from sleep, a mixlar:// deep link being opened.

These events are delivered by the host (the desktop app), not synthesized by the SDK. The app is expected to call broadcast() — or dispatch() for a single plugin — from its own event sources, passing every loaded plugin instance and the event name/kwargs.

A plugin author subclasses SystemEventsMixin alongside MixlarPlugin and overrides the on_* hook(s) it cares about. Everything else stays a harmless no-op.

from mixlar import MixlarPlugin
from mixlar.events import SystemEventsMixin
class MyPlugin(SystemEventsMixin, MixlarPlugin):
def on_device_connect(self):
self.push_widget_data("status", "online")
def on_app_launch(self, exe):
if exe.lower() == "spotify.exe":
self.push_widget_data("mode", "music")
event constant mixin hook fires when
APP_LAUNCH on_app_launch(exe) a tracked foreground app started
APP_TERMINATE on_app_terminate(exe) a tracked foreground app exited
SYSTEM_WAKE on_system_wake() the OS resumed from sleep
DEVICE_CONNECT on_device_connect() the M1X attached over USB serial
DEVICE_DISCONNECT on_device_disconnect() the M1X detached / the serial link dropped
DEEP_LINK on_deep_link(url) a mixlar://... link was opened (raw URL, unparsed)

Override only the hooks you need — an unoverridden hook is a no-op, so mixing in SystemEventsMixin is always safe even if your plugin only cares about one event.

Two module-level functions drive the hooks:

  • dispatch(plugin, event, **kwargs) — calls the matching hook on a single plugin instance, if it has one.
  • broadcast(plugins, event, **kwargs) — calls dispatch for every plugin in an iterable. This is what the host is expected to call.

Both are crash-isolated: a plugin raising from a hook never takes down the host loop or other plugins, and an unrecognized event name is silently ignored (forward-compatible with future event types added to the SDK).

from mixlar.events import broadcast, APP_LAUNCH
broadcast(loaded_plugins, APP_LAUNCH, exe="spotify.exe")

parse_deep_link(url) parses a mixlar://<plugin_id>/<action>?k=v URL into (plugin_id, params), where params is the query string as a flat dict with the path’s action segment (if present) stashed under params["action"].

from mixlar.events import parse_deep_link
plugin_id, params = parse_deep_link("mixlar://weather-widget/refresh?city=nyc")
# plugin_id == "weather-widget"
# params == {"city": "nyc", "action": "refresh"}

Malformed input degrades gracefully to ("", {}) rather than raising, so you can call it on untrusted, OS-supplied URLs without a try/except.

Host integration — where each call belongs

Section titled “Host integration — where each call belongs”

None of these call sites exist in PC Software/mixlar_mini.py yet; adding them is what turns this contract on. This is the map of exactly where, so whoever wires it up doesn’t have to rediscover it:

  1. APP_LAUNCH / APP_TERMINATE — inside ForegroundWatcher’s poll loop, wherever it currently diffs the foreground process for auto-switch profiles. Call broadcast(plugins, APP_LAUNCH, exe=exe_name) when a new foreground exe is first seen, and APP_TERMINATE when a tracked exe disappears.

  2. SYSTEM_WAKE — hook the Windows sleep/wake notification (e.g. a WM_POWERBROADCAST / PBT_APMRESUMEAUTOMATIC handler, or a QSystemTrayIcon-adjacent OS event) and call broadcast(plugins, SYSTEM_WAKE) on resume.

  3. DEVICE_CONNECT / DEVICE_DISCONNECT — in the serial port scanner / CDCReader connection lifecycle, where the app already detects the M1X attaching/detaching over USB. Call broadcast(plugins, DEVICE_CONNECT) / broadcast(plugins, DEVICE_DISCONNECT).

  4. DEEP_LINK — in the mixlar:// URL-protocol handler (single-instance activation / custom scheme entry point — this handler doesn’t exist yet either). Parse the incoming URL with parse_deep_link() and call broadcast(plugins, DEEP_LINK, url=raw_url), or route directly to the matched plugin using the parsed plugin_id.

See the roadmap for the current status of this integration work.

Since the host doesn’t call these yet, exercise the contract directly in tests or the emulator by calling dispatch/broadcast yourself:

from mixlar.events import broadcast, APP_LAUNCH
broadcast([my_plugin_instance], APP_LAUNCH, exe="spotify.exe")

This is exactly what the app’s ForegroundWatcher will do once wired up — so a plugin written against SystemEventsMixin today needs no changes when that lands. You can drive the same calls from the CLI emulator while iterating on your hooks.

  • Settings — persist state your event hooks update.
  • Macros — trigger key/text automation from a hook.
  • Plugin API — the full MixlarPlugin base class.