feat(display): add list-modes and set-mode commands

display_control.m:
  list-modes  — JSON array of all online displays with every usable mode
                (width, height, refresh, pixel_width, pixel_height, hidpi, is_current)
  set-mode <display_index> <width> <height> [--refresh <hz>] [--hidpi] [--no-hidpi]
              — picks best matching CGDisplayMode; auto-prefers HiDPI on
                built-in and non-HiDPI on externals; highest refresh wins on ties

system_handlers.ts:
  native:list-display-modes  — runs binary, parses JSON, returns displays[]
  native:set-display-mode    — runs binary with supplied args

preload/index.ts + shared/types.ts:
  list_display_modes() / set_display_mode() exposed through bridge with full types
This commit is contained in:
Scott Idem
2026-05-20 18:16:43 -04:00
parent b2cd0736df
commit 54308c6d4a
4 changed files with 304 additions and 3 deletions

View File

@@ -11,8 +11,12 @@
#import <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <math.h>
#define MAX_DISPLAYS 8
#define MAX_MODES 256
typedef struct { long src_index; size_t w, h, pw, ph; double refresh; } ModeEntry;
static int mirror_displays(void) {
CGDirectDisplayID onlineDspys[MAX_DISPLAYS] = {0};
@@ -125,10 +129,200 @@ static void print_status(void) {
(numOnline > 1 && numActive < numOnline) ? "mirrored" : "extended");
}
// list-modes
// Outputs a JSON array describing every online display and its available modes.
static void list_modes(void) {
CGDirectDisplayID displays[MAX_DISPLAYS];
CGDisplayCount count = 0;
CGGetOnlineDisplayList(MAX_DISPLAYS, displays, &count);
CGDirectDisplayID mainID = CGMainDisplayID();
printf("[\n");
for (CGDisplayCount d = 0; d < count; d++) {
CGDirectDisplayID dID = displays[d];
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(dID);
size_t curW = CGDisplayModeGetWidth(currentMode);
size_t curH = CGDisplayModeGetHeight(currentMode);
double curR = CGDisplayModeGetRefreshRate(currentMode);
size_t curPW = CGDisplayModeGetPixelWidth(currentMode);
size_t curPH = CGDisplayModeGetPixelHeight(currentMode);
// Include HiDPI duplicate entries so scaled modes are visible.
CFStringRef optKeys[] = { kCGDisplayShowDuplicateLowResolutionModes };
CFBooleanRef optVals[] = { kCFBooleanTrue };
CFDictionaryRef opts = CFDictionaryCreate(NULL,
(const void **)optKeys, (const void **)optVals, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(dID, opts);
CFRelease(opts);
CFIndex total = CFArrayGetCount(allModes);
// Collect usable modes first so we know the count for comma handling.
ModeEntry usable[MAX_MODES];
int usable_count = 0;
for (CFIndex m = 0; m < total && usable_count < MAX_MODES; m++) {
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, m);
if (!CGDisplayModeIsUsableForDesktopGUI(mode)) continue;
usable[usable_count++] = (ModeEntry){
.src_index = (long)m,
.w = CGDisplayModeGetWidth(mode),
.h = CGDisplayModeGetHeight(mode),
.pw = CGDisplayModeGetPixelWidth(mode),
.ph = CGDisplayModeGetPixelHeight(mode),
.refresh = CGDisplayModeGetRefreshRate(mode)
};
}
printf(" {\n");
printf(" \"index\": %u,\n", d);
printf(" \"id\": %u,\n", (unsigned int)dID);
printf(" \"is_main\": %s,\n", dID == mainID ? "true" : "false");
printf(" \"current_width\": %zu,\n", curW);
printf(" \"current_height\": %zu,\n", curH);
printf(" \"current_refresh\": %.2f,\n", curR);
printf(" \"current_pixel_width\": %zu,\n", curPW);
printf(" \"current_pixel_height\": %zu,\n", curPH);
printf(" \"modes\": [\n");
for (int i = 0; i < usable_count; i++) {
ModeEntry *e = &usable[i];
int isCurrent = (e->w == curW && e->h == curH &&
e->pw == curPW && e->ph == curPH &&
fabs(e->refresh - curR) < 0.5);
printf(" {\"index\":%ld,\"width\":%zu,\"height\":%zu,"
"\"refresh\":%.2f,\"pixel_width\":%zu,\"pixel_height\":%zu,"
"\"hidpi\":%s,\"is_current\":%s}%s\n",
e->src_index, e->w, e->h, e->refresh, e->pw, e->ph,
(e->pw > e->w || e->ph > e->h) ? "true" : "false",
isCurrent ? "true" : "false",
(i < usable_count - 1) ? "," : "");
}
printf(" ]\n");
printf(" }%s\n", (d < count - 1) ? "," : "");
CGDisplayModeRelease(currentMode);
CFRelease(allModes);
}
printf("]\n");
}
// set-mode
// display_idx : index from list-modes (0 = primary, 1 = first external, ...)
// req_w/h : logical width × height (what macOS calls "looks like X×Y")
// req_refresh : 0 = pick highest available; >0 = must be within 1 Hz
// force_hidpi : 1 = HiDPI only; -1 = non-HiDPI only; 0 = auto
// auto prefers HiDPI on the built-in, non-HiDPI on externals
static int set_mode(int display_idx, size_t req_w, size_t req_h,
double req_refresh, int force_hidpi) {
CGDirectDisplayID displays[MAX_DISPLAYS];
CGDisplayCount count = 0;
CGGetOnlineDisplayList(MAX_DISPLAYS, displays, &count);
if (display_idx < 0 || (CGDisplayCount)display_idx >= count) {
fprintf(stderr, "Display index %d out of range (0..%u).\n",
display_idx, count > 0 ? count - 1 : 0);
return 1;
}
CGDirectDisplayID dID = displays[display_idx];
int isMain = (dID == CGMainDisplayID());
CFStringRef optKeys[] = { kCGDisplayShowDuplicateLowResolutionModes };
CFBooleanRef optVals[] = { kCFBooleanTrue };
CFDictionaryRef opts = CFDictionaryCreate(NULL,
(const void **)optKeys, (const void **)optVals, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(dID, opts);
CFRelease(opts);
CFIndex total = CFArrayGetCount(allModes);
CGDisplayModeRef bestMode = NULL;
double bestRefresh = -1.0;
int bestScore = -1; // higher = more preferred
for (CFIndex m = 0; m < total; m++) {
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, m);
if (!CGDisplayModeIsUsableForDesktopGUI(mode)) continue;
size_t w = CGDisplayModeGetWidth(mode);
size_t h = CGDisplayModeGetHeight(mode);
if (w != req_w || h != req_h) continue;
double refresh = CGDisplayModeGetRefreshRate(mode);
if (req_refresh > 0.0 && fabs(refresh - req_refresh) > 1.0) continue;
size_t pw = CGDisplayModeGetPixelWidth(mode);
int isHiDPI = (pw > w);
if (force_hidpi == 1 && !isHiDPI) continue;
if (force_hidpi == -1 && isHiDPI) continue;
// Score: prefer HiDPI on main display, non-HiDPI on external.
int score = (force_hidpi == 0)
? ((isMain && isHiDPI) || (!isMain && !isHiDPI)) ? 1 : 0
: 0;
if (bestMode == NULL || score > bestScore ||
(score == bestScore && refresh > bestRefresh)) {
bestMode = mode;
bestRefresh = refresh;
bestScore = score;
}
}
if (!bestMode) {
fprintf(stderr, "No matching mode for display %d: %zux%zu",
display_idx, req_w, req_h);
if (req_refresh > 0.0) fprintf(stderr, " @%.0fHz", req_refresh);
if (force_hidpi == 1) fprintf(stderr, " [HiDPI required]");
if (force_hidpi == -1) fprintf(stderr, " [non-HiDPI required]");
fprintf(stderr, ".\nRun: display_control list-modes\n");
CFRelease(allModes);
return 1;
}
size_t setW = CGDisplayModeGetWidth(bestMode);
size_t setH = CGDisplayModeGetHeight(bestMode);
double setR = CGDisplayModeGetRefreshRate(bestMode);
size_t setPW = CGDisplayModeGetPixelWidth(bestMode);
size_t setPH = CGDisplayModeGetPixelHeight(bestMode);
CGDisplayConfigRef config;
CGError err = CGBeginDisplayConfiguration(&config);
if (err != kCGErrorSuccess) {
fprintf(stderr, "CGBeginDisplayConfiguration failed: %d\n", err);
CFRelease(allModes);
return 1;
}
err = CGConfigureDisplayWithDisplayMode(config, dID, bestMode, NULL);
if (err != kCGErrorSuccess) {
fprintf(stderr, "CGConfigureDisplayWithDisplayMode failed: %d\n", err);
CGCancelDisplayConfiguration(config);
CFRelease(allModes);
return 1;
}
err = CGCompleteDisplayConfiguration(config, kCGConfigurePermanently);
if (err != kCGErrorSuccess) {
fprintf(stderr, "CGCompleteDisplayConfiguration failed: %d\n", err);
CFRelease(allModes);
return 1;
}
printf("Set display %d to %zux%zu @%.0fHz (pixel %zux%zu).\n",
display_idx, setW, setH, setR, setPW, setPH);
CFRelease(allModes);
return 0;
}
// main
int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc < 2) {
fprintf(stderr, "Usage: display_control <mirror|extend|status>\n");
fprintf(stderr, "Usage: display_control <mirror|extend|status|list-modes|set-mode>\n");
return 1;
}
@@ -141,8 +335,31 @@ int main(int argc, const char * argv[]) {
} else if (strcmp(cmd, "status") == 0) {
print_status();
return 0;
} else if (strcmp(cmd, "list-modes") == 0) {
list_modes();
return 0;
} else if (strcmp(cmd, "set-mode") == 0) {
if (argc < 5) {
fprintf(stderr, "Usage: display_control set-mode <display_index> <width> <height> [--refresh <hz>] [--hidpi] [--no-hidpi]\n");
return 1;
}
int display_idx = atoi(argv[2]);
size_t req_w = (size_t)atol(argv[3]);
size_t req_h = (size_t)atol(argv[4]);
double req_refresh = 0.0;
int force_hidpi = 0;
for (int i = 5; i < argc; i++) {
if (strcmp(argv[i], "--refresh") == 0 && i + 1 < argc) {
req_refresh = atof(argv[++i]);
} else if (strcmp(argv[i], "--hidpi") == 0) {
force_hidpi = 1;
} else if (strcmp(argv[i], "--no-hidpi") == 0) {
force_hidpi = -1;
}
}
return set_mode(display_idx, req_w, req_h, req_refresh, force_hidpi);
} else {
fprintf(stderr, "Unknown command: %s\nUsage: display_control <mirror|extend|status>\n", cmd);
fprintf(stderr, "Unknown command: %s\nUsage: display_control <mirror|extend|status|list-modes|set-mode>\n", cmd);
return 1;
}
}

View File

@@ -413,6 +413,63 @@ export function registerSystemHandlers() {
return { success: false, error: `Unsupported display mode: ${mode}` };
});
// 6b. List Display Modes
ipcMain.handle('native:list-display-modes', async () => {
if (os.platform() !== 'darwin') return { success: false, error: 'Display control only supported on macOS' };
const dc_bin = app.isPackaged
? path.join(process.resourcesPath, 'bin', 'display_control')
: path.join(__dirname, '../../resources/bin/display_control');
if (!fs.existsSync(dc_bin)) {
return { success: false, error: 'display_control binary not found. Build via scripts/build-display-control.sh.' };
}
const result = await runExec(`"${dc_bin}" list-modes`);
if (!result.success || !result.stdout) {
return { success: false, error: result.error ?? 'list-modes returned no output' };
}
try {
const displays = JSON.parse(result.stdout);
return { success: true, displays };
} catch (e: any) {
return { success: false, error: `Failed to parse list-modes output: ${e.message}`, raw: result.stdout };
}
});
// 6c. Set Display Mode
ipcMain.handle('native:set-display-mode', async (event, {
display_index,
width,
height,
refresh_rate,
hidpi
}: {
display_index: number;
width: number;
height: number;
refresh_rate?: number;
hidpi?: boolean | null;
}) => {
if (os.platform() !== 'darwin') return { success: false, error: 'Display control only supported on macOS' };
const dc_bin = app.isPackaged
? path.join(process.resourcesPath, 'bin', 'display_control')
: path.join(__dirname, '../../resources/bin/display_control');
if (!fs.existsSync(dc_bin)) {
return { success: false, error: 'display_control binary not found. Build via scripts/build-display-control.sh.' };
}
let cmd = `"${dc_bin}" set-mode ${display_index} ${width} ${height}`;
if (refresh_rate) cmd += ` --refresh ${refresh_rate}`;
if (hidpi === true) cmd += ' --hidpi';
if (hidpi === false) cmd += ' --no-hidpi';
return await runExec(cmd);
});
// 7. Update App
ipcMain.handle('native:update-app', async (event, { source, url, path: localPath }) => {
// 1. Determine Source File

View File

@@ -5,7 +5,7 @@ contextBridge.exposeInMainWorld('aetherNative', {
get_device_config: () => ipcRenderer.invoke('get-device-config'),
get_jwt: () => ipcRenderer.invoke('get-jwt'),
get_device_info: () => ipcRenderer.invoke('get-device-info'),
open_folder: (path: string) => ipcRenderer.invoke('native:open-folder', path),
run_cmd: (args: any) => ipcRenderer.invoke('native:run-cmd', args),
run_cmd_sync: (args: any) => ipcRenderer.invoke('native:run-cmd-sync', args),
@@ -27,6 +27,8 @@ contextBridge.exposeInMainWorld('aetherNative', {
window_control: (args: any) => ipcRenderer.invoke('native:window-control', args),
manage_recording: (args: any) => ipcRenderer.invoke('native:manage-recording', args),
set_display_layout: (args: any) => ipcRenderer.invoke('native:set-display-layout', args),
list_display_modes: () => ipcRenderer.invoke('native:list-display-modes'),
set_display_mode: (args: any) => ipcRenderer.invoke('native:set-display-mode', args),
power_control: (args: any) => ipcRenderer.invoke('native:power-control', args),
open_external: (args: any) => ipcRenderer.invoke('native:open-external', args),
});

View File

@@ -1,3 +1,26 @@
export interface DisplayMode {
index: number;
width: number;
height: number;
refresh: number;
pixel_width: number;
pixel_height: number;
hidpi: boolean;
is_current: boolean;
}
export interface DisplayInfo {
index: number;
id: number;
is_main: boolean;
current_width: number;
current_height: number;
current_refresh: number;
current_pixel_width: number;
current_pixel_height: number;
modes: DisplayMode[];
}
export interface SeedConfig {
event_device_id: string;
primary_api_base_url: string;
@@ -37,6 +60,8 @@ export interface AetherNativeBridge {
open_external: (args: {url: string, app?: 'chrome' | 'firefox'}) => Promise<{success: boolean, error?: string}>;
manage_recording: (args: {action: 'start' | 'stop' | 'status', options?: {fps?: number, audioDeviceId?: string, output?: string}}) => Promise<{success: boolean, isRecording?: boolean, pid?: number, error?: string}>;
set_display_layout: (args: {mode: 'mirror' | 'extend', configStr?: string | null}) => Promise<{success: boolean, error?: string, stdout?: string, stderr?: string}>;
list_display_modes: () => Promise<{success: boolean, displays?: DisplayInfo[], error?: string, raw?: string}>;
set_display_mode: (args: {display_index: number, width: number, height: number, refresh_rate?: number, hidpi?: boolean | null}) => Promise<{success: boolean, stdout?: string, stderr?: string, error?: string}>;
update_app: (args: {source: 'url' | 'file', url?: string, path?: string}) => Promise<{success: boolean, message?: string, downloadedPath?: string, error?: string}>;
// Self-Documentation