fix(display): fix mirror_of_display syntax, add Homebrew fallback, update docs
- `mirror:` was wrong; correct displayplacer param is `mirror_of_display:<uuid>` - Same fix applied to the strip regex (extend path) and mirror detection check - Binary lookup now tries resources/bin → /opt/homebrew/bin → /usr/local/bin so dev/venue Macs with `brew install displayplacer` work without bundling - Updated TODO_AGENTS.md: marks auto-detection complete, documents one-time brew install step, bundling path, and optional per-device configStr override Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -325,9 +325,16 @@ export function registerSystemHandlers() {
|
||||
ipcMain.handle('native:set-display-layout', async (event, { mode, configStr }) => {
|
||||
if (os.platform() !== 'darwin') return { success: false, error: 'Display control only supported on macOS' };
|
||||
|
||||
const binPath = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'bin', 'displayplacer')
|
||||
: path.join(__dirname, '../../resources/bin/displayplacer');
|
||||
// Try bundled binary first; fall back to common Homebrew/system locations.
|
||||
// Install on a dev/venue Mac via: brew install displayplacer
|
||||
const _bin_candidates = app.isPackaged
|
||||
? [path.join(process.resourcesPath, 'bin', 'displayplacer')]
|
||||
: [
|
||||
path.join(__dirname, '../../resources/bin/displayplacer'),
|
||||
'/opt/homebrew/bin/displayplacer', // Apple Silicon Homebrew
|
||||
'/usr/local/bin/displayplacer', // Intel Homebrew
|
||||
];
|
||||
const binPath = _bin_candidates.find(p => fs.existsSync(p)) ?? _bin_candidates[0];
|
||||
|
||||
// Explicit config string always takes priority — allows manual override per device.
|
||||
if (configStr) {
|
||||
@@ -359,18 +366,18 @@ export function registerSystemHandlers() {
|
||||
}
|
||||
const primary_id = primary_id_match[1];
|
||||
|
||||
// Primary display unchanged; secondary display(s) get mirror:<primary_id>.
|
||||
// Primary display unchanged; secondary display(s) get mirror_of_display:<primary_id>.
|
||||
const mirror_args = display_strings.map((s, i) => {
|
||||
if (i === 0) return `"${s}"`;
|
||||
const without_existing_mirror = s.replace(/\s*mirror:\S+/g, '').trim();
|
||||
return `"${without_existing_mirror} mirror:${primary_id}"`;
|
||||
const without_existing_mirror = s.replace(/\s*mirror_of_display:\S+/g, '').trim();
|
||||
return `"${without_existing_mirror} mirror_of_display:${primary_id}"`;
|
||||
}).join(' ');
|
||||
|
||||
return await runExec(`"${binPath}" ${mirror_args}`);
|
||||
}
|
||||
|
||||
if (mode === 'extend') {
|
||||
const any_mirrored = display_strings.some(s => /\bmirror:\S+/.test(s));
|
||||
const any_mirrored = display_strings.some(s => /\bmirror_of_display:\S+/.test(s));
|
||||
if (!any_mirrored) {
|
||||
// Already extended — re-apply current layout to ensure it is active.
|
||||
return await runExec(`"${binPath}" ${display_strings.map(s => `"${s}"`).join(' ')}`);
|
||||
@@ -379,7 +386,7 @@ export function registerSystemHandlers() {
|
||||
// Remove mirror keys and compute side-by-side origins from each display's resolution.
|
||||
let x_offset = 0;
|
||||
const extend_args = display_strings.map((s) => {
|
||||
const without_mirror = s.replace(/\s*mirror:\S+/g, '').trim();
|
||||
const without_mirror = s.replace(/\s*mirror_of_display:\S+/g, '').trim();
|
||||
const res_match = without_mirror.match(/\bres:(\d+)x\d+/);
|
||||
const width = res_match ? parseInt(res_match[1]) : 1920;
|
||||
const updated = without_mirror.replace(/\borigin:\([^)]+\)/, `origin:(${x_offset},0)`);
|
||||
|
||||
Reference in New Issue
Block a user