feat(macos): support CoreMIDI and per-user LaunchAgents - #5
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 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
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.