feat(display): add display_control CoreGraphics binary, replace displayplacer as primary path
Derived from OSIT MasterKey app (LegacyUtilities.m, Ian Kohl 2019). Uses CGConfigureDisplayMirrorOfDisplay — native macOS API, no Homebrew dependency. Handler now tries display_control first; falls back to displayplacer for missing binary or per-device configStr overrides. Build the binary via scripts/build-display-control.sh on a Mac (requires Xcode CLT only), then commit resources/bin/display_control. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,31 +66,46 @@
|
||||
|
||||
---
|
||||
|
||||
## set_display_layout — displayplacer Setup & Status (updated 2026-05-20)
|
||||
## set_display_layout — Setup & Status (updated 2026-05-20)
|
||||
|
||||
**Reference:** [jakehilborn/displayplacer](https://github.com/jakehilborn/displayplacer)
|
||||
**Primary approach: `display_control` (native CoreGraphics — no Homebrew required)**
|
||||
- Source: `scripts/display_control.m` (derived from OSIT MasterKey app, Ian Kohl 2019)
|
||||
- Build: run `scripts/build-display-control.sh` on a Mac (requires Xcode CLT only)
|
||||
- Output: `resources/bin/display_control` — commit this binary to the repo
|
||||
- Uses `CGConfigureDisplayMirrorOfDisplay` — same CoreGraphics API macOS uses internally
|
||||
- Supports 3+ displays; auto-detects all connected displays; no config string needed
|
||||
|
||||
**Fallback approach: `displayplacer` (requires `brew install displayplacer` on each venue Mac)**
|
||||
- Reference: [jakehilborn/displayplacer](https://github.com/jakehilborn/displayplacer)
|
||||
- Still used when `display_control` binary is not present
|
||||
- Also used for per-device `configStr` overrides (displayplacer-format strings in `event_device.data_json`)
|
||||
|
||||
**Current state (2026-05-20):**
|
||||
|
||||
- ✅ Handler auto-detects displays via `displayplacer list` — no per-device `configStr` required for normal use
|
||||
- ✅ Binary lookup order: `resources/bin/displayplacer` (bundled) → `/opt/homebrew/bin/displayplacer` (Apple Silicon Homebrew) → `/usr/local/bin/displayplacer` (Intel Homebrew)
|
||||
- ✅ Correct `mirror_of_display:<uuid>` syntax used (was `mirror:` — wrong, now fixed)
|
||||
- ✅ Correct `mirror_of_display:<uuid>` syntax used in displayplacer fallback (was `mirror:` — wrong, now fixed)
|
||||
- ✅ Failures logged to Electron console (`[Launcher] set_display_layout:`) instead of silently swallowed
|
||||
- ✅ **Display Mode toggle** added to Launcher config (Native OS section) — Extend/Mirror buttons always visible, no Technical Mode required
|
||||
- ⏳ `display_control` binary not yet built — must be compiled on a Mac and committed
|
||||
|
||||
**One-time setup on each venue Mac:**
|
||||
**To build `display_control` (do this on a Mac):**
|
||||
```bash
|
||||
brew install displayplacer
|
||||
# One-time: install Xcode Command Line Tools if not already installed
|
||||
xcode-select --install
|
||||
|
||||
# Then:
|
||||
./scripts/build-display-control.sh
|
||||
|
||||
# Test it with a second display connected:
|
||||
./resources/bin/display_control status
|
||||
./resources/bin/display_control extend
|
||||
./resources/bin/display_control mirror
|
||||
|
||||
# Commit the binary:
|
||||
git add resources/bin/display_control
|
||||
git commit -m "build: add display_control binary (macOS CoreGraphics)"
|
||||
```
|
||||
The binary is not bundled in the Electron build yet. Homebrew installs it to `/opt/homebrew/bin/` (Apple Silicon) or `/usr/local/bin/` (Intel) — both are in the fallback lookup chain.
|
||||
|
||||
**Bundling the binary (future):**
|
||||
To ship displayplacer inside the `.app` bundle without requiring Homebrew on venue Macs:
|
||||
1. Copy the `displayplacer` binary to `resources/bin/displayplacer`
|
||||
2. Mark it executable: `chmod +x resources/bin/displayplacer`
|
||||
3. Add to `package.json` `extraResources` so `@electron/packager` includes it
|
||||
|
||||
**Optional per-device override (manual tuning):**
|
||||
**Optional per-device override (displayplacer format, for edge cases):**
|
||||
For rooms where auto-detection produces the wrong result, store the raw configStr in `event_device.data_json`:
|
||||
```json
|
||||
{
|
||||
@@ -98,10 +113,4 @@ For rooms where auto-detection produces the wrong result, store the raw configSt
|
||||
"displayplacer_config_mirror": "<output of displayplacer list in mirrored layout>"
|
||||
}
|
||||
```
|
||||
The handler accepts `configStr` and uses it directly, bypassing auto-detection. Pass it from the Svelte call site if needed.
|
||||
|
||||
**displayplacer quick reference:**
|
||||
- `displayplacer list` — prints current display info and a ready-to-run config string at the bottom
|
||||
- `displayplacer "<display_string>" "<display_string>"` — applies layout
|
||||
- Mirror syntax: add `mirror_of_display:<primary_uuid>` to the secondary display string
|
||||
- Extend syntax: set `origin:(<x>,0)` with non-overlapping x offsets per display
|
||||
`configStr` is passed from the Svelte call site and uses the displayplacer fallback path directly.
|
||||
|
||||
Reference in New Issue
Block a user