Skip to content

Convert & migrate

There are two separate migrations on this page. The first — converting a legacy in-app plugin to explicit mixlar imports — is something you can do any time, to any plugin, at your own pace. The second — the app itself adopting the SDK as its single source of truth instead of defining these classes inline — is a maintainer-side change to mixlar_mini.py. Neither depends on the other.

Converting a legacy plugin to explicit imports

Section titled “Converting a legacy plugin to explicit imports”

Legacy plugins (see PC Software/Plugin Examples/*.py) rely on the app injecting names into their module namespace before exec-ing them: MixlarPlugin, the color constants (ACCENT, CARD2, WHITE, and so on), _fa, _EDITOR_COMBO_STYLE, _style_combo_view. That’s why those examples reference MixlarPlugin and ACCENT with no import line at the top — the names only resolve inside the running app, which means no autocomplete, no type checking, and no way to run the file standalone.

Before — legacy style, only works when injected by the app:

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)
...

After — explicit imports, works everywhere:

from mixlar import MixlarPlugin
from mixlar.colors import ACCENT
from 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)
...

Nothing about the class body changes — only the top of the file. This is safe precisely because of the SDK’s “single source of truth” seam: mixlar.plugin detects a running app and re-exports its live MixlarPlugin; mixlar.colors and mixlar.qt do the same for the palette and the Qt helpers. So:

  • Off-app (your editor, mixlar-sdk validate/emulate, tests, CI) — you get faithful standalone implementations: real values, real behavior, just not backed by the actual running app or a real serial link.
  • Inside the app — the imports resolve to the app’s own objects, byte-identical to what injection would have given you.

Run mixlar-sdk validate after converting to confirm nothing regressed.

This is the bigger, maintainer-side move. Today mixlar_mini.py defines MixlarPlugin (PC Software/mixlar_mini.py:3454), PluginRegistry (:3665), the color constants (around :2749), and _plugin_package_digest / _verify_plugin_signature / _PUBLISHER_KEYS (:3839, :3908, :3903) inline. Instead, the app would import mixlar and use this SDK’s implementations as the canonical ones:

# in mixlar_mini.py, instead of defining these classes inline:
from mixlar.plugin import MixlarPlugin
from mixlar.registry import PluginRegistry
from mixlar.colors import ACCENT, CARD, CARD2, WHITE, GRAY, SEP, RED, GREEN, ORANGE, PURPLE
from mixlar.signing import package_digest as _plugin_package_digest
from mixlar.signing import verify_signature as _verify_plugin_signature
from mixlar.signing import PUBLISHER_KEYS as _PUBLISHER_KEYS

The re-export mechanism in mixlar.plugin / mixlar.registry / mixlar.colors only fires when sys.modules already contains mixlar_mini — it’s designed for the current one-way direction (app injects, SDK detects and mirrors). Flipping it so the app imports from mixlar instead removes the duplication entirely: one implementation, one place bugs get fixed, and this SDK’s own test suite (tests/) becomes a real regression suite for that shared code instead of a parallel reimplementation that could silently drift from the app’s copy.

  • mixlar.manifest.validate_package / mixlar.validator.lint_package become the app’s own manifest and widget validation, instead of a second implementation to keep in sync.
  • mixlar.packaging.pack / unpack / install become the app’s own .mixplugin handling — see Packaging & .mixplugin.
  • The app’s plugin-loading code and mixlar_cli.util.load_plugin_instances become the same function, instead of two implementations that must be kept behaviorally identical by hand (as they are today, deliberately mirrored).

Dependency surface. mixlar currently pulls in cryptography and packaging unconditionally (core pyproject.toml dependencies). Both are almost certainly already present in the app’s own requirements.txt/build — cryptography for OS credential handling, packaging is extremely common transitively — but confirm before assuming.

If the app imports mixlar, Builds/mixlar_mini.spec needs to know about it — PyInstaller doesn’t discover it automatically, since nothing in the app today imports it. Either of these works:

# mixlar_mini.spec — explicit list
hiddenimports = [
..., # existing entries
"mixlar",
"mixlar.plugin", "mixlar.registry", "mixlar.colors", "mixlar.protocol",
"mixlar.manifest", "mixlar.signing", "mixlar.widgets", "mixlar.validator",
"mixlar.settings_schema", "mixlar.events", "mixlar.macros", "mixlar.sliders",
"mixlar.icons", "mixlar.imaging", "mixlar.packaging", "mixlar.emulator",
"mixlar.emulator.device", "mixlar.emulator.render", "mixlar.qt",
]

or, simpler and more future-proof against new submodules, use collect_submodules in the spec’s Analysis block:

from PyInstaller.utils.hooks import collect_submodules
hiddenimports += collect_submodules("mixlar")
  1. Confirm cryptography/packaging are already in the app’s dependency set (they almost certainly are).
  2. Swap the color constants first (mixlar.colors) — lowest risk, no behavioral surface, easiest to verify visually.
  3. Swap signing/manifest/packaging/validator next — these are pure functions with no Qt/registry coupling, and this SDK’s tests/ already cover their exact behavior.
  4. Swap MixlarPlugin/PluginRegistry last — this is the class-identity change every loaded plugin’s issubclass check depends on. Test with a couple of real plugins from PC Software/Plugin Examples/ before calling it done.
  5. Add the PyInstaller hiddenimports (or collect_submodules) change in the same pass as step 4, and do a full Builds\build.bat plus a smoke test — an import that works from source but is missing from the frozen .exe is exactly the kind of bug that doesn’t show up until someone else’s machine.