adding mac support mainly
This commit is contained in:
98
mac-support.md
Normal file
98
mac-support.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# macOS Support — Change Catalog
|
||||
|
||||
All changes made on the `Mac` branch to achieve a unified Linux/macOS codebase.
|
||||
No Windows support planned.
|
||||
|
||||
---
|
||||
|
||||
## Status Legend
|
||||
- ✅ Done
|
||||
- ⬜ Pending
|
||||
|
||||
---
|
||||
|
||||
## Design philosophy
|
||||
|
||||
The goal is a single binary that works on both platforms with no build tags. All
|
||||
platform differences are resolved at runtime via `runtime.GOOS`. The user experience
|
||||
should feel native on each OS — launchd on macOS, systemd on Linux, etc.
|
||||
|
||||
The biggest architectural difference is privileged commands:
|
||||
- **Linux** uses a root helper daemon + Unix socket. The whitelist is root-owned so the
|
||||
user cannot modify it. The main process never runs as root.
|
||||
- **macOS** skips the daemon entirely. `priv:` commands are looked up in the user's own
|
||||
`privileged.yaml` and executed via `osascript`, which shows the standard macOS admin
|
||||
auth dialog. No root process, no group management, no socket chown.
|
||||
|
||||
---
|
||||
|
||||
## Code Changes
|
||||
|
||||
### `internal/device/streamdeck.go`
|
||||
- ✅ **Platform-aware open error hint** — Linux: `sudo chmod a+rw /dev/hidraw*`; macOS: `brew install hidapi` + Input Monitoring note. The hint is embedded in the error returned from `Open()` so it surfaces wherever the error is logged.
|
||||
- ✅ **Broaden HID read error matching** — `ReadButtons()` catches `"timeout"`, `"timed out"`, and `"interrupted"` as non-fatal. Linux hidraw returns `"timeout"`; macOS IOHIDManager may return `"timed out"`. Both should result in a retry rather than counting toward the 3-error device-death threshold.
|
||||
|
||||
### `cmd/streamdeck/main.go`
|
||||
- ✅ **Runtime helper socket path** — `helperSocketPath()` returns `/run/streamdeck-go/helper.sock` on Linux and `/var/run/streamdeck-go/helper.sock` on macOS. `/run` is a Linux-specific tmpfs; `/var/run` is the macOS equivalent.
|
||||
- ✅ **No root daemon on macOS** — `runPrivileged()` dispatches to:
|
||||
- `runPrivilegedDarwin()` on macOS: reads `~/.config/streamdeck-go/privileged.yaml`, looks up the command, runs it via `osascript -e 'do shell script "..." with administrator privileges'`. The OS shows its standard admin auth dialog. No daemon, no sudo, no group.
|
||||
- `runPrivilegedHelper()` on Linux: existing Unix socket approach, unchanged.
|
||||
- ✅ **Remove dead code** — unused `blank()` function removed.
|
||||
|
||||
### `cmd/streamdeck-helper/main.go`
|
||||
- ✅ **Runtime socket path** — same `/run` vs `/var/run` split as above. The helper binary is Linux-only in practice but the path function is correct on both platforms for consistency.
|
||||
- ✅ **Runtime whitelist path** — Linux: `/etc/streamdeck-go/privileged.yaml`; unchanged (helper not used on macOS).
|
||||
|
||||
---
|
||||
|
||||
## New Files
|
||||
|
||||
### `launchd/com.woodarddigital.streamdeck-go.plist`
|
||||
- ✅ macOS LaunchAgent for the user daemon. Installed to `~/Library/LaunchAgents/` by `install.sh`.
|
||||
- `RunAtLoad: true` — starts at login.
|
||||
- `KeepAlive: true` — restarts automatically if the process exits.
|
||||
- Log path substituted at install time by `install.sh` via `sed` → `~/Library/Logs/streamdeck-go.log`. This path persists across reboots (unlike `/tmp` which is cleared on reboot).
|
||||
- Binary path also substituted at install time.
|
||||
|
||||
### `launchd/com.woodarddigital.streamdeck-go-helper.plist`
|
||||
- ✅ Kept for reference but **not used on macOS** — no root daemon needed.
|
||||
- On macOS, privileged commands are handled inline via osascript (see above).
|
||||
|
||||
---
|
||||
|
||||
## Updated Files
|
||||
|
||||
### `install.sh`
|
||||
- ✅ **OS detection** — `uname -s` at startup sets `IS_MAC=true/false`. All platform-specific blocks branch on this.
|
||||
- ✅ **Dependency detection** — macOS: checks for hidapi dylib or pkg-config, installs via `brew`. Linux: existing pkg-config / ldconfig paths.
|
||||
- ✅ **Skip udev on macOS** — macOS HID devices are accessible via IOKit without any device rules. The step is replaced with an informational message about Input Monitoring.
|
||||
- ✅ **Binary install path** — macOS: `~/go/bin/` (no sudo required). Linux: `~/.local/bin/`. A PATH warning is shown on macOS if `~/go/bin` is not in the current shell's PATH.
|
||||
- ✅ **Service management** — macOS: generates the launchd plist from the template (substituting binary + log paths via `sed`), then `launchctl load`. Linux: `systemctl --user enable --now`.
|
||||
- ✅ **Log path** — macOS: `~/Library/Logs/streamdeck-go.log` (persistent). Linux: journald (unchanged).
|
||||
- ✅ **`readlink -f` portability** — macOS ships without `readlink -f` (requires coreutils). Replaced with `realpath_portable()` which tries `realpath`, then `python3 -c "os.path.realpath()"`, then a basic `~`-expansion fallback.
|
||||
- ✅ **`install -D` portability** — Linux `install -D` auto-creates parent directories; macOS `install` does not support this flag. Replaced with explicit `mkdir -p "$(dirname dst)"` + `install`.
|
||||
- ✅ **Fix dotfiles path bug** — was `${DOTFILES}/streamdeck-go/.config/streamdeck-go`; corrected to `${DOTFILES}/.config/streamdeck-go` to match the pattern shown in the README.
|
||||
|
||||
### `Makefile`
|
||||
- ✅ **OS detection** — `$(shell uname -s)` sets `OS`; `ifeq ($(OS),Darwin)` / `else` blocks set all platform-specific variables.
|
||||
- ✅ **macOS `install-helper`** — no daemon, no group, no sudo. Just copies `config/privileged.example.yaml` to `~/.config/streamdeck-go/privileged.yaml`. The user edits it to add `priv:` commands.
|
||||
- ✅ **Linux `install-helper`** — unchanged: `dscl`-free, uses `groupadd` + `usermod`, installs helper binary + systemd system service.
|
||||
- ✅ **`udev` target** — guarded: prints a skip message on macOS, runs the udev rule install on Linux.
|
||||
- ✅ **`uninstall` / `uninstall-helper`** — macOS: `launchctl unload` + file removal, no daemon to stop for helper. Linux: `systemctl disable` + file removal.
|
||||
|
||||
---
|
||||
|
||||
## Platform comparison
|
||||
|
||||
| Topic | Linux | macOS |
|
||||
|---|---|---|
|
||||
| HID access | udev rule grants `MODE="0666"` on hidraw | IOKit — no rules needed; accessible to user by default |
|
||||
| Input Monitoring | N/A | May prompt on first run; Stream Deck is not keyboard/mouse so usually auto-granted |
|
||||
| Service manager | systemd (user + system units) | launchd (LaunchAgent for user; no LaunchDaemon needed) |
|
||||
| Socket dir | `/run/streamdeck-go/` | `/var/run/streamdeck-go/` |
|
||||
| Privileged command mechanism | Root helper daemon + Unix socket | `osascript` admin auth dialog (inline, no daemon) |
|
||||
| Whitelist location | `/etc/streamdeck-go/privileged.yaml` (root-owned 640) | `~/.config/streamdeck-go/privileged.yaml` (user-owned 644) |
|
||||
| Binary location | `~/.local/bin/` | `~/go/bin/` |
|
||||
| Log location | `journalctl --user -u streamdeck-go` | `~/Library/Logs/streamdeck-go.log` |
|
||||
| Config path | `~/.config/streamdeck-go/` (XDG) | `~/.config/streamdeck-go/` (XDG — works fine on macOS for CLI tools) |
|
||||
| Sleep/wake | Handled by reconnect loop | Handled by reconnect loop (same code) |
|
||||
Reference in New Issue
Block a user