Skip to content

feat(macos): support CoreMIDI and per-user LaunchAgents - #5

Merged
hyperb1iss merged 6 commits into
mainfrom
nova/macos-support
Sep 6, 2026
Merged

feat(macos): support CoreMIDI and per-user LaunchAgents#5
hyperb1iss merged 6 commits into
mainfrom
nova/macos-support

Conversation

@hyperb1iss

Copy link
Copy Markdown
Owner

💡 Native macOS support

blocksd can now keep ROLI Blocks connected on macOS through CoreMIDI and run as a per-user LaunchAgent. Linux retains its systemd, udev, and runtime-path behavior. Windows support remains future work.

🛠️ Connection and service behavior

The MIDI detector uses CoreMIDI endpoint IDs instead of enumeration indices to identify connections. Unplugging one of two identically named devices no longer replaces the surviving connection when its index changes. Physical input/output ports are paired by owning entity; virtual endpoints cannot consume a physical device's output. Opening a connection checks identity before and after opening the ports.

The macOS installer writes a LaunchAgent with an absolute executable path and per-user logs. Reinstallation waits for launchd to finish unloading the old job before bootstrapping its replacement. The default config lives under Application Support, with legacy config paths retained. A short, owner-only runtime directory avoids macOS Unix-socket path limits and rejects unsafe existing directories.

The CI matrix now covers Linux and macOS on Python 3.13 and 3.14. Installed-wheel checks use a fresh environment so a rebuilt wheel at the same path cannot silently reuse an older installation.

🧪 Validation

  • All 493 tests passed locally on macOS with Python 3.13 and 3.14. Ruff lint/format, ty, shell syntax, dashboard checks/build, and documentation lint/build passed.
  • An installed-wheel smoke test confirmed IPC ping, the bundled HTTP dashboard, and clean signal shutdown.
  • Real launchd startup, repeat installation, and uninstall passed using an isolated temporary service.
  • Real virtual CoreMIDI endpoints retained their IDs after index changes. Independent review caught and verified the physical/virtual pairing regression.
  • User-confirmed hardware operation: USB LUMI Keys and a DNA-connected Lightpad Block M entered API mode and reported topology and battery state.

Sleep/wake and DAW coexistence still need hardware acceptance testing. The existing LittleFoot renderer limitation is unchanged. Homebrew packaging will follow separately; published v0.5.0 does not contain this macOS support.

Use Application Support for Mac configuration while retaining existing
config discovery as a fallback. Keep the local socket address short and
reject unsafe ownership, permissions, or symlinks in its default parent.

Use short isolated socket fixtures so API lifecycle tests also run with
macOS temporary directory names. Linux path defaults remain unchanged.
Key active groups by their complete input/output pair so duplicate
CoreMIDI display names do not collapse into a single device group.

Join retired groups before reopening reindexed ports, allowing native
handles and protocol cleanup to finish before replacements take over.
Describe source installation, LaunchAgent management, native paths, and
pending hardware acceptance without implying a published Mac release.

Run Python and installed-distribution checks on macOS alongside Linux,
and advertise the supported runtime platforms in CLI and package metadata.
Dispatch service setup to launchd on macOS and retain the systemd and
udev workflow on Linux. Generate a plist with an absolute executable,
per-user logs, login startup, and restart after failed exits.

Wait for launchd to finish unloading before registering a replacement;
real reinstall checks exposed a bootstrap race after bootout returned.
Preserve CLI exit exceptions and report service failures directly.
Read stable CoreMIDI endpoint IDs and owning entities alongside RtMidi
names. Pair physical endpoints by entity and keep virtual name matching
separate so duplicate names cannot consume another device's output.

Keep live groups through list-index shifts and removal of either
identically named device. Validate identities around port opening and
release native clients if hotplug invalidates the scan. Linux retains
name-based identity without loading the CoreMIDI framework.

Real virtual endpoints verified stable IDs after reindexing and exposed
CoreMIDI's object-not-found result for virtual endpoints without entities.
A same-version local wheel rebuild reused an older uv tool environment,
so distribution checks could pass without importing newly added modules.
Refresh and reinstall flags did not invalidate that environment on uv
0.9.30. Disable cache for this consumption check to test the actual wheel.
@hyperb1iss
hyperb1iss merged commit e089b39 into main Sep 6, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant