From 0135219b822ce1799ba666ec7110cd1d62394e63 Mon Sep 17 00:00:00 2001 From: Bart Meuris Date: Thu, 13 Feb 2025 16:15:41 +0100 Subject: [PATCH 1/2] Added json output --- README.md | 18 ++++++ src/DisplayPlacer.c | 140 +++++++++++++++++++++++++++++++------------- src/Makefile | 2 +- src/printjson.c | 61 +++++++++++++++++++ src/printjson.h | 9 +++ 5 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 src/printjson.c create mode 100644 src/printjson.h diff --git a/README.md b/README.md index 6ae1da1..75b1eee 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Install via Homebrew with `brew install displayplacer` or visit the [releases](h Show current screen info and possible resolutions: `displayplacer list` +Show current screen info and possible resolutions in json: `displayplacer list --json` + Apply screen config (hz & color_depth are optional): `displayplacer "id: res:x hz: color_depth: scaling: origin:(,) degree:<0/90/180/270>"` Apply screen config using mode: `displayplacer "id: mode: origin:(,) degree:<0/90/180/270>"` @@ -23,6 +25,7 @@ Silence errors per-screen using quiet: `displayplacer "id: mode: enabled:false"` #### Instructions: + 1. Manually set rotations 1st*, resolutions 2nd, and arrangement 3rd. For extra resolutions and rotations read 'Notes' below. - Open System Preferences -> Displays - Choose desired screen rotations (use displayplacer for rotating internal MacBook screen). @@ -32,14 +35,27 @@ Disable a screen: `displayplacer "id: enabled:false"` 2. Use `displayplacer list` to print your current layout's args, so you can create profiles for scripting/hotkeys with [Automator](https://github.com/jakehilborn/displayplacer/issues/13), BetterTouchTool, etc. #### ScreenIds Switching: + Unfortunately, macOS sometimes changes the persistent screenIds when there are race conditions from external screens waking up in non-determinisic order. If none of the screenId options below work for your setup, please search around in the GitHub Issues for conversation regarding this. Many people have written shell scripts to work around this issue. Recommended discussions are [one](https://github.com/jakehilborn/displayplacer/issues/80), [two](https://github.com/jakehilborn/displayplacer/issues/30), [three](https://github.com/jakehilborn/displayplacer/issues/89), [four](https://github.com/jakehilborn/displayplacer/issues/77), [five](https://github.com/jakehilborn/displayplacer/issues/100), [six](https://github.com/jakehilborn/displayplacer/pull/96). You can mix and match screenId types across your setup. + - Persistent screenIds usually stay the same. They are recommended for most use cases. - Contextual screenIds change when switching GPUs or when cables switch ports. If you notice persistent screenIds switching around, try using the contextual screenIds. - Serial screenIds are tied to your display hardware. If the serial screenIds are unique for all of your monitors, use these. +#### JSON output + +The `list` subcommand accepts the `--json` parameter to output a valid json structure, which in general is the same as the plain `list` output. + +However this structure is completely unformatted and not very human-frienly, if you want to have a more readable output, pipe it into the `jq` tool: + +```shell +displayplacer list --json | jq +``` + #### Notes: + - *`displayplacer list` and system prefs only show resolutions for the screen's current rotation. - Use an extra resolution shown in `displayplacer list` by executing `displayplacer "id: mode:"`. Some of the resolutions listed do not work. If you select one, displayplacer will default to another working resolution. - Rotate your internal MacBook screen by executing `displayplacer "id: degree:<0/90/180/270>"` @@ -50,7 +66,9 @@ You can mix and match screenId types across your setup. - screenId is optional if there is only one screen. Rule of thumb is that displayplacer is expecting the entire profile config per screen though, so this may be buggy. #### Backward Compatability: + `displayplacer list` output changed slightly in v1.4.0. If this broke your scripts, use `displayplacer list --v1.3.0`. #### Feedback: + Please create a GitHub Issue for any feedback, feature requests, bugs, Homebrew issues, etc. Happy to accept pull requests too! diff --git a/src/DisplayPlacer.c b/src/DisplayPlacer.c index f06a3a5..efa9187 100644 --- a/src/DisplayPlacer.c +++ b/src/DisplayPlacer.c @@ -4,6 +4,7 @@ #include #include #include "Header.h" +#include "printjson.h" int main(int argc, char* argv[]) { if (argc == 1 || strcmp(argv[1], "--help") == 0) { @@ -23,6 +24,9 @@ int main(int argc, char* argv[]) { } if (strcmp(argv[1], "list") == 0) { + if ((argc == 3) && (strcmp(argv[2], "--json") == 0)) { + print_json = true; + } listScreens(); printCurrentProfile(); return 0; @@ -198,6 +202,8 @@ void printHelp() { "Usage:\n" " Show current screen info and possible resolutions: displayplacer list\n" "\n" + " Show current screen info and possible resolutions in json: displayplacer list --json\n" + "\n" " Apply screen config (hz & color_depth are optional): displayplacer \"id: res:x hz: color_depth: scaling: origin:(,) degree:<0/90/180/270>\"\n" "\n" " Apply screen config using mode: displayplacer \"id: mode: origin:(,) degree:<0/90/180/270>\"\n" @@ -254,14 +260,18 @@ void printVersion() { ); } + void listScreens() { CGDisplayCount screenCount; CGGetOnlineDisplayList(INT_MAX, NULL, &screenCount); //get number of online screens and store in screenCount CGDirectDisplayID screenList[screenCount]; CGGetOnlineDisplayList(INT_MAX, screenList, &screenCount); - + print_json_start('{', NULL); // JSON OPEN + print_json_start('[', "screens"); // SCREENS LIST for (int i = 0; i < screenCount; i++) { + print_json_start('{', NULL); // SCREEN BLOCK + CGDirectDisplayID curScreen = screenList[i]; int curModeId; @@ -271,79 +281,125 @@ void listScreens() { char curScreenUUID[UUID_SIZE]; CFStringGetCString(CFUUIDCreateString(kCFAllocatorDefault, CGDisplayCreateUUIDFromDisplayID(curScreen)), curScreenUUID, sizeof(curScreenUUID), kCFStringEncodingUTF8); - printf("Persistent screen id: %s\n", curScreenUUID); - printf("Contextual screen id: %i\n", curScreen); + + print_str_val("Persistent screen id", "persistent_screen_id", "%s", curScreenUUID, true); + print_int_val("Contextual screen id", "contextual_screen_id", "%i", curScreen, true); UInt32 serialID = CGDisplaySerialNumber(screenList[i]); - printf("Serial screen id: s%u\n", serialID); + print_int_val("Serial screen id", "serial_screen_id", "s%u", serialID, true); if (CGDisplayIsBuiltin(curScreen)) { - printf("Type: MacBook built in screen\n"); + print_str_val("Type", "type", "%s", "MacBook built in screen", true); } else { CGSize size = CGDisplayScreenSize(curScreen); int diagonal = round(sqrt((size.width * size.width) + (size.height * size.height)) / 25.4); //25.4mm in an inch - printf("Type: %i inch external screen\n", diagonal); + char buf[128]; + snprintf(buf, sizeof(buf), "%i inch external screen", diagonal); + // print_int_val("Type", "type", "\"%i inch external screen\"", diagonal, true); + print_str_val("Type", "type", "%s", buf, true); + } + if (!print_json) { + printf("Resolution: %ix%i\n", (int) CGDisplayPixelsWide(curScreen), (int) CGDisplayPixelsHigh(curScreen)); + } else { + print_json_start('{', "resolution"); // RESOLUTION BLOCK + print_int_val("", "x", "%i", (int) CGDisplayPixelsWide(curScreen), true); + print_int_val("", "y", "%i", (int) CGDisplayPixelsHigh(curScreen), false); + print_json_end('}', true); // RESOLUTION BLOCK } - - printf("Resolution: %ix%i\n", (int) CGDisplayPixelsWide(curScreen), (int) CGDisplayPixelsHigh(curScreen)); if (curMode.derived.freq) { - printf("Hertz: %i\n", curMode.derived.freq); + print_int_val("Hertz", "hertz", "%i", curMode.derived.freq, true); } else { - printf("Hertz: N/A\n"); + print_str_val("Hertz", "hertz", "%s", "N/A", true); } - - printf("Color Depth: %i\n", curMode.derived.depth); + print_int_val("Color Depth", "color_depth", "%i", curMode.derived.depth, true); char* scaling = (curMode.derived.density == 2.0) ? "on" : "off"; - printf("Scaling: %s\n", scaling); - - printf("Origin: (%i,%i)", (int) CGDisplayBounds(curScreen).origin.x, (int) CGDisplayBounds(curScreen).origin.y); - if (CGDisplayIsMain(curScreen)) { - printf(" - main display"); - } - printf("\n"); + print_str_val("Scaling", "scaling", "%s", scaling, true); - printf("Rotation: %i", (int) CGDisplayRotation(curScreen)); - if (CGDisplayIsBuiltin(curScreen)) { - printf(" - rotate internal screen example (may crash computer, but will be rotated after rebooting): `displayplacer \"id:%s degree:90\"`", curScreenUUID); + if (!print_json) { + printf("Origin: (%i,%i)", (int) CGDisplayBounds(curScreen).origin.x, (int) CGDisplayBounds(curScreen).origin.y); + if (CGDisplayIsMain(curScreen)) { + printf(" - main display"); + } + printf("\n"); + } else { + print_json_start('{', "origin"); // ORIGIN BLOCK + print_int_val("", "x", "%i", (int) CGDisplayBounds(curScreen).origin.x, true); + print_int_val("", "y", "%i", (int) CGDisplayBounds(curScreen).origin.y, true); + print_str_val("", "main_display", "%s", ((CGDisplayIsMain(curScreen)) ? "true" : "false"), false); + print_json_end('}', true); // ORIGIN BLOCK + } + if (!print_json) { + printf("Rotation: %i", (int) CGDisplayRotation(curScreen)); + if (CGDisplayIsBuiltin(curScreen)) { + printf(" - rotate internal screen example (may crash computer, but will be rotated after rebooting): `displayplacer \"id:%s degree:90\"`", curScreenUUID); + } + printf("\n"); + } else { + print_str_val("", "builtin", "%s", (CGDisplayIsBuiltin(curScreen)) ? "true" : "false", true); } - printf("\n"); - - char* enabled = isScreenEnabled(curScreen) ? "true" : "false"; - printf("Enabled: %s\n", enabled); + print_str_val("Enabled", "enabled", "%s", isScreenEnabled(curScreen) ? "true" : "false", true); int modeCount; modes_D4* modes; CopyAllDisplayModes(curScreen, &modes, &modeCount); - printf("Resolutions for rotation %i:\n", (int) CGDisplayRotation(curScreen)); + print_json_start('{', "rotation"); // ROTATION BLOCK + if (!print_json) { + printf("Resolutions for rotation %i:\n", (int) CGDisplayRotation(curScreen)); + } else { + print_int_val("Resolutions for rotation", "degrees", "%i", (int) CGDisplayRotation(curScreen), true); + } + print_json_start('[', "modes"); // MODES LIST for (int j = 0; j < modeCount; j++) { + print_json_start('{', NULL); modes_D4 mode = modes[j]; - printf(" mode %i: res:%dx%d", j, mode.derived.width, mode.derived.height); - - if (mode.derived.freq) { - printf(" hz:%i", mode.derived.freq); - } + if (print_json) { + // print_json_start('{', "mode"); + print_int_val("", "mode_id", "%i", j, true); + print_int_val("", "x", "%i", (int) CGDisplayPixelsWide(curScreen), true); + print_int_val("", "y", "%i", (int) CGDisplayPixelsHigh(curScreen), true); + print_int_val("", "hz", "%i", mode.derived.freq, true); + print_int_val("", "color_depth", "%i", mode.derived.depth, true); + print_str_val("", "scaling", "%s", (mode.derived.density == 2.0) ? "true": "false", true); + print_str_val("", "current_mode", "%s", (j == curModeId) ? "true": "false", false); + // print_json_end('}', true); + } else { + printf(" mode %i: res:%dx%d", j, mode.derived.width, mode.derived.height); + + if (mode.derived.freq) { + printf(" hz:%i", mode.derived.freq); + } - printf(" color_depth:%i", mode.derived.depth); + printf(" color_depth:%i", mode.derived.depth); - if (mode.derived.density == 2.0) { - printf(" scaling:on"); - } + if (mode.derived.density == 2.0) { + printf(" scaling:on"); + } - if (j == curModeId) { - printf(" <-- current mode"); + if (j == curModeId) { + printf(" <-- current mode"); + } + printf("\n"); } - - printf("\n"); + print_json_end('}', (j != (modeCount - 1))); } - printf("\n"); free(modes); - } + print_json_end(']', false); // MODES LIST + print_json_end('}', false); // ROTATION BLOCK + print_json_end('}', (i != (screenCount - 1))); // SCREEN BLOCK + print_plain_nl(); + } + // print_json_end('}', false); + print_json_end(']', false);// SCREENS LIST + print_json_end('}', false); // JSON OPEN } void printCurrentProfile() { + if (print_json) { + return; + } CGDisplayCount screenCount; CGGetOnlineDisplayList(INT_MAX, NULL, &screenCount); //get number of online screens and store in screenCount diff --git a/src/Makefile b/src/Makefile index 3dd9ed1..025f1d2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -51,7 +51,7 @@ INSTALL_DATA ?= $(INSTALL) -m 644 all: displayplacer displayplacer: - $(CC) -I. $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) DisplayPlacer.c Legacy/v130.c -x objective-c MonitorPanel.m -o $@ $< -F./Headers -F/System/Library/PrivateFrameworks -framework IOKit -framework ApplicationServices -framework DisplayServices -framework CoreDisplay -framework OSD -framework MonitorPanel -framework SkyLight $(WARNINGS) + $(CC) -I. $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) DisplayPlacer.c printjson.c Legacy/v130.c -x objective-c MonitorPanel.m -o $@ $< -F./Headers -F/System/Library/PrivateFrameworks -framework IOKit -framework ApplicationServices -framework DisplayServices -framework CoreDisplay -framework OSD -framework MonitorPanel -framework SkyLight $(WARNINGS) .PHONY: debug debug: CFLAGS += -g diff --git a/src/printjson.c b/src/printjson.c new file mode 100644 index 0000000..ca66831 --- /dev/null +++ b/src/printjson.c @@ -0,0 +1,61 @@ +#include +#include + +bool print_json = false; + +void print_json_start(char type, const char *name) { + if (!print_json) { + return; + } + if (name) { + printf("\"%s\": ", name); + } + printf("%c\n", type); +} + +void print_plain_nl() { + if (!print_json) { + printf("\n"); + } +} +void print_json_end(char type, bool addcomma) { + if (print_json) { + printf("%c", type); + if (addcomma) { + printf(","); + } + printf("\n"); + } +} + +void print_str_val(const char *name, const char *json_name, const char *valfmt, const char *val, bool addcomma) { + if (print_json) { + printf("\"%s\": \"", json_name); + } else { + printf("%s: ", name); + } + printf(valfmt, val); + if (print_json) { + printf("\""); + if (addcomma) { + printf(","); + } + } + printf("\n"); +} + +void print_int_val(const char *name, const char *json_name, const char *valfmt, int val, bool addcomma) { + if (print_json) { + printf("\"%s\": \"", json_name); + } else { + printf("%s: ", name); + } + printf(valfmt, val); + if (print_json) { + printf("\""); + if (addcomma) { + printf(","); + } + } + printf("\n"); +} \ No newline at end of file diff --git a/src/printjson.h b/src/printjson.h new file mode 100644 index 0000000..7a9c8fe --- /dev/null +++ b/src/printjson.h @@ -0,0 +1,9 @@ +#pragma once + +extern bool print_json; + +void print_plain_nl(); +void print_json_start(char type, const char *name); +void print_json_end(char type, bool addcomma); +void print_str_val(const char *name, const char *json_name, const char *valfmt, const char *val, bool addcomma); +void print_int_val(const char *name, const char *json_name, const char *valfmt, int val, bool addcomma); \ No newline at end of file From bceabd7ad9fa5ba110998e1751adb18aa9168c2d Mon Sep 17 00:00:00 2001 From: Bart Meuris Date: Thu, 13 Feb 2025 16:34:31 +0100 Subject: [PATCH 2/2] added separate function for printing boolean values --- src/DisplayPlacer.c | 10 +++++----- src/printjson.c | 15 +++++++++++++++ src/printjson.h | 3 ++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/DisplayPlacer.c b/src/DisplayPlacer.c index efa9187..7c2fc9b 100644 --- a/src/DisplayPlacer.c +++ b/src/DisplayPlacer.c @@ -326,7 +326,7 @@ void listScreens() { print_json_start('{', "origin"); // ORIGIN BLOCK print_int_val("", "x", "%i", (int) CGDisplayBounds(curScreen).origin.x, true); print_int_val("", "y", "%i", (int) CGDisplayBounds(curScreen).origin.y, true); - print_str_val("", "main_display", "%s", ((CGDisplayIsMain(curScreen)) ? "true" : "false"), false); + print_bool_val("", "main_display", CGDisplayIsMain(curScreen), false); print_json_end('}', true); // ORIGIN BLOCK } if (!print_json) { @@ -336,9 +336,9 @@ void listScreens() { } printf("\n"); } else { - print_str_val("", "builtin", "%s", (CGDisplayIsBuiltin(curScreen)) ? "true" : "false", true); + print_bool_val("", "builtin", CGDisplayIsBuiltin(curScreen), true); } - print_str_val("Enabled", "enabled", "%s", isScreenEnabled(curScreen) ? "true" : "false", true); + print_bool_val("Enabled", "enabled", isScreenEnabled(curScreen), true); int modeCount; modes_D4* modes; @@ -362,8 +362,8 @@ void listScreens() { print_int_val("", "y", "%i", (int) CGDisplayPixelsHigh(curScreen), true); print_int_val("", "hz", "%i", mode.derived.freq, true); print_int_val("", "color_depth", "%i", mode.derived.depth, true); - print_str_val("", "scaling", "%s", (mode.derived.density == 2.0) ? "true": "false", true); - print_str_val("", "current_mode", "%s", (j == curModeId) ? "true": "false", false); + print_bool_val("", "scaling", (mode.derived.density == 2.0), true); + print_bool_val("", "current_mode", (j == curModeId), false); // print_json_end('}', true); } else { printf(" mode %i: res:%dx%d", j, mode.derived.width, mode.derived.height); diff --git a/src/printjson.c b/src/printjson.c index ca66831..dac223b 100644 --- a/src/printjson.c +++ b/src/printjson.c @@ -44,6 +44,21 @@ void print_str_val(const char *name, const char *json_name, const char *valfmt, printf("\n"); } +void print_bool_val(const char *name, const char *json_name, bool val, bool addcomma) { + if (print_json) { + printf("\"%s\": ", json_name); + } else { + printf("%s: ", name); + } + printf("%s", val ? "true" : "false"); + if (print_json) { + if (addcomma) { + printf(","); + } + } + printf("\n"); +} + void print_int_val(const char *name, const char *json_name, const char *valfmt, int val, bool addcomma) { if (print_json) { printf("\"%s\": \"", json_name); diff --git a/src/printjson.h b/src/printjson.h index 7a9c8fe..032d768 100644 --- a/src/printjson.h +++ b/src/printjson.h @@ -6,4 +6,5 @@ void print_plain_nl(); void print_json_start(char type, const char *name); void print_json_end(char type, bool addcomma); void print_str_val(const char *name, const char *json_name, const char *valfmt, const char *val, bool addcomma); -void print_int_val(const char *name, const char *json_name, const char *valfmt, int val, bool addcomma); \ No newline at end of file +void print_int_val(const char *name, const char *json_name, const char *valfmt, int val, bool addcomma); +void print_bool_val(const char *name, const char *json_name, bool val, bool addcomma);