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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user