How the SDK maps to the app
from mixlar import MixlarPlugin looks like an ordinary import, but which
class it actually resolves to depends on where the code is running. Off-app —
your editor, mixlar-sdk validate, mixlar-sdk emulate, tests, CI — you get
a faithful standalone reimplementation with autocomplete and type checking.
Inside the running Mixlar Control app, that same import transparently
becomes the app’s own live class instead. This page explains that seam, why
it’s safe to rely on, and what it means for how you write and ship plugins.
The “single source of truth” seam
Section titled “The “single source of truth” seam”Everything in the SDK is a faithful, importable port of code that already
lives inline in the app (PC Software/mixlar_mini.py) — MixlarPlugin,
PluginRegistry, the color palette, ed25519 package signing, and the
widget/manifest linters. Rather than maintaining two implementations that
could silently drift apart, each SDK module checks at import time whether
it’s running inside the app and, if so, hands you the app’s own object
instead of its own standalone one.
The check lives at the bottom of mixlar/plugin.py:
# Inside the live app, BE the app's class so issubclass() checks pass and# pushes use the real link. Off-app, use the standalone base above._app = sys.modules.get("mixlar_mini")if _app is not None and hasattr(_app, "MixlarPlugin"): MixlarPlugin = _app.MixlarPluginelse: MixlarPlugin = _StandaloneMixlarPluginmixlar/registry.py does the same thing for PluginRegistry:
_app = sys.modules.get("mixlar_mini")if _app is not None and hasattr(_app, "PluginRegistry"): PluginRegistry = _app.PluginRegistryIn other words: the detection is a single sys.modules lookup for
mixlar_mini. If the app has already imported itself into the process (it
always has, by the time plugins load), the SDK mirrors its live classes
instead of defining its own.
Why this matters for you
Section titled “Why this matters for you”Because both branches expose the same class name with the same shape, a
plugin authored against mixlar.MixlarPlugin needs zero changes to run
for real:
issubclass()checks the app’s loader performs against your plugin class succeed, because in-app your class really does subclass the app’s ownMixlarPlugin— not a lookalike.push_widget_data()andpush_widget_image()reach the actual serial link in-app, instead of the standalone class’s stand-in behavior.- Macro/slider registration lands in the same
PluginRegistrytable the app reads, not a shadow copy.
Off-app, the standalone _StandaloneMixlarPlugin class is behaviorally
identical in shape (same hooks, same signatures) but has no device to talk
to — push_widget_data is a no-op unless something (like the emulator) has
set PluginRegistry.instance()._wdata_sender.
Studio-built vs. SDK-built plugins
Section titled “Studio-built vs. SDK-built plugins”You can build a Mixlar plugin two ways, and this seam is what makes them interoperable:
| Legacy / in-app style | SDK style | |
|---|---|---|
How MixlarPlugin resolves |
Injected into the module namespace by the app before exec-ing the file |
Explicit from mixlar import MixlarPlugin |
| Editor support | None — MixlarPlugin, ACCENT, _fa etc. are undefined names until the app injects them |
Full autocomplete and type checking |
Runs standalone (tests, CI, mixlar-sdk emulate) |
Only via the CLI’s compatibility shim | Yes, natively |
| Runs in the app | Yes | Yes, unmodified |
Legacy plugins (see PC Software/Plugin Examples/*.py) rely on the app
injecting names — MixlarPlugin, the color constants, _fa,
_EDITOR_COMBO_STYLE, _style_combo_view — into their module namespace
before executing them. That’s why those examples reference MixlarPlugin
and ACCENT with no import line at all: it only resolves inside the app.
Converting one to explicit SDK imports only touches the top of the file — the class body is untouched:
# Before: works only injected, no editor supportclass MouseBatteryPlugin(MixlarPlugin): plugin_id = "mouse_battery" plugin_icon_color = ACCENT
def build_settings_ui(self, layout, settings): icon = _fa("fa5s.battery-full", color=ACCENT)# After: explicit imports, works everywherefrom mixlar import MixlarPluginfrom mixlar.colors import ACCENTfrom mixlar.qt import _fa
class MouseBatteryPlugin(MixlarPlugin): plugin_id = "mouse_battery" plugin_icon_color = ACCENT
def build_settings_ui(self, layout, settings): icon = _fa("fa5s.battery-full", color=ACCENT)Package trust travels the same seam
Section titled “Package trust travels the same seam”Signing and verification use the same “one implementation, two call sites”
approach. mixlar.signing.package_digest computes the exact SHA-256 digest
that the app’s own _plugin_package_digest computes over a package’s files
— byte-identical, not just equivalent. That means:
- A
.mixpluginpackage signed withmixlar-sdk pack --signormixlar-sdk signverifies correctly when the app checks its signature at install time. - There’s no separate “SDK signature” and “app signature” format to keep in sync — it’s one digest algorithm with two call sites.
See Signing packages for the full trust flow, key generation, and the pinned publisher keyset.
What this means day to day
Section titled “What this means day to day”- Write your plugin once, against
mixlar.MixlarPlugin. Test it withmixlar-sdk validateandmixlar-sdk emulatewith no device attached, thenmixlar-sdk linkit into the app’s plugins directory and it runs for real with no code changes. - Don’t rely on identity checks like
type(x) is SomeSpecificClassagainst SDK classes in your own code — the whole point is that the class object itself differs between the two worlds (standalone vs. the app’s live class), even though the interface is identical. - If you’re hacking on the SDK itself rather than a plugin, the deeper,
maintainer-facing side of this — the app eventually
import mixlar-ing these modules as its own canonical implementation instead of defining them inline — is covered in Convert a plugin and the SDK’s owndocs/migration.md.
Next steps
Section titled “Next steps”- Getting started — install the SDK and scaffold your first plugin.
- Plugin API — the full
MixlarPluginhook reference. - Convert a plugin — migrating a legacy, injection-style plugin to explicit SDK imports.
- Signing packages — keys, trust, and the
.mixpluginformat.