Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<screenId> res:<width>x<height> hz:<num> color_depth:<num> scaling:<on/off> origin:(<x>,<y>) degree:<0/90/180/270>"`

Apply screen config using mode: `displayplacer "id:<screenId> mode:<modeNum> origin:(<x>,<y>) degree:<0/90/180/270>"`
Expand All @@ -23,6 +25,7 @@ Silence errors per-screen using quiet: `displayplacer "id:<screenId> mode:<modeN
Disable a screen: `displayplacer "id:<screenId> 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).
Expand All @@ -32,14 +35,27 @@ Disable a screen: `displayplacer "id:<screenId> 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:<screenId> mode:<modeNum>"`. 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:<screenId> degree:<0/90/180/270>"`
Expand All @@ -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!
140 changes: 98 additions & 42 deletions src/DisplayPlacer.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <math.h>
#include <stdio.h>
#include "Header.h"
#include "printjson.h"

int main(int argc, char* argv[]) {
if (argc == 1 || strcmp(argv[1], "--help") == 0) {
Expand All @@ -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;
Expand Down Expand Up @@ -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:<screenId> res:<width>x<height> hz:<num> color_depth:<num> scaling:<on/off> origin:(<x>,<y>) degree:<0/90/180/270>\"\n"
"\n"
" Apply screen config using mode: displayplacer \"id:<screenId> mode:<modeNum> origin:(<x>,<y>) degree:<0/90/180/270>\"\n"
Expand Down Expand Up @@ -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;
Expand All @@ -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_bool_val("", "main_display", CGDisplayIsMain(curScreen), 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_bool_val("", "builtin", CGDisplayIsBuiltin(curScreen), true);
}
printf("\n");

char* enabled = isScreenEnabled(curScreen) ? "true" : "false";
printf("Enabled: %s\n", enabled);
print_bool_val("Enabled", "enabled", isScreenEnabled(curScreen), 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_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);

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

Expand Down
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions src/printjson.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <ApplicationServices/ApplicationServices.h>
#include <stdio.h>

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_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);
} else {
printf("%s: ", name);
}
printf(valfmt, val);
if (print_json) {
printf("\"");
if (addcomma) {
printf(",");
}
}
printf("\n");
}
10 changes: 10 additions & 0 deletions src/printjson.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#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);
void print_bool_val(const char *name, const char *json_name, bool val, bool addcomma);