Skip to content
MoreDisplays

Type to search.

Manual contents

Every command on one page

Search the whole CLI with a browser find, or print it and keep it next to the machine.

Every command from the CLI reference, in one document. The grammar comes from mdctl docs, so it cannot fall behind the individual pages or the tool.

9 commands.

mdctl status

Print the app version, the engine state and the group list.

mdctl status

What it prints

One field per line, ending with an `icloud:` line when the app has something to say about whether the library travels.

The first thing to run when something looks wrong, and the first thing to paste into a report. It changes nothing, so it is safe from a monitoring loop.

mdctl status
version: 1.6
state: active
active group: Home
groups: Home, Office, MacBook Air 13-inch (M1)
icloud: syncing, last synced 09:14
service: registered and allowed to run (macOS: enabled)

The fields

version is the running app’s, not the tool’s. state is the engine: idle when nothing is active, active when a group is applied. A field with no value prints (none) rather than an empty string, so a transcript never leaves you wondering whether the value was blank or the line was missing.

The icloud: line appears only when the app has something to say about sharing the library. Its absence means sharing is off; a sentence there means it is on, and either working or explaining why not.

The service: line is the privileged service for displays at the login window. It exists because that service is for a Mac with no monitor, reached over the network, where “have a look in System Settings” is advice nobody at the far end can take. The sentence weighs the registration against your answer, so not set up; you declined it reads as a choice and not set up, though it is switched on reads as a fault.

mdctl status | sed -n 's/^service: //p'
not set up; you declined it (macOS: notRegistered)

Where macOS has been asked but has not been answered, the line says so and names where to answer it: under Login Items in System Settings. The system’s own word for the state is repeated in brackets, unchanged, so a support transcript carries what macOS said rather than what the app made of it.

Reading it from a script

Labelled lines in a fixed order. Read them with sed, not a JSON parser, and branch on the exit code rather than on the text.

mdctl status | sed -n 's/^active group: //p'
Home

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl list

Print every group name, one per line.

mdctl list

What it prints

One group name per line, in sidebar order.

Group names in sidebar order, one per line, with no header and no count. The output exists to be piped.

mdctl list
Home
Office
MacBook Air 13-inch (M1)

An empty library prints nothing and still exits 0. That is the correct answer to “which groups are there” when there are none, and it means a loop over the output needs no special case.

Names come back the way activate wants them

Group names contain spaces, and these are the exact strings activate matches on. Quote them.

mdctl list | head -1
Home

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl displays

Print the displays the machine is showing.

mdctl displays

What it prints

One display per line, left to right across the desk: reference, size, refresh rate, name, and [main] on the one carrying the menu bar.

What the machine is showing right now, left to right across the desk. Not what a group says it should be showing: this is the live topology.

mdctl displays
virtual:4C2E9A31-7F0B-4E58-9C1D-8A0F2B6D5E77   5120x1440 @ 60Hz  Home ultrawide  [main]
physical:A1B2C3D4-5E6F-4071-8293-A4B5C6D7E8F9  3840x2160 (7680x4320 px) @ 60Hz  Studio Display

The reference column comes first because the next command needs it

Everything else on the line is for recognising the display; the reference is what you paste into edid or protect. It is the same spelling the configuration file uses, so a display you can see here is one you can find in the file.

A display the app created is named through its own spec rather than through the ColorSync UUID CoreGraphics hands out for every display, ours included. Two spellings of one reference, with the tool printing the one you could not use, was a real bug.

Points and pixels

The size is in points, and the pixel size follows in brackets only when the two differ. On an ordinary display printing both would be noise; on a HiDPI display the difference is the whole point.

mdctl displays | wc -l
       2

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl edid

Print what one display says about itself.

mdctl edid <reference>

Arguments

Argument Type Required Meaning
reference string Yes The display, spelled the way `displays` prints it.

What it prints

One field per line: the display, where the answer came from, and the identity fields the display declares.

What one display says about itself: manufacturer, product, serial, when it was made, and its native size.

mdctl edid physical:A1B2C3D4-5E6F-4071-8293-A4B5C6D7E8F9
display   physical:A1B2C3D4-5E6F-4071-8293-A4B5C6D7E8F9
source    the panel's own EDID, as macOS decoded it
name      Studio Display
vendor    APP (1552)
model     41005
serial    1234567
made      week 33, 2022
native    5120x2880

It is not the EDID block, and it does not pretend to be

Apple silicon publishes no raw EDID: IODisplayEDID is simply absent from the registry. What it does publish is DisplayAttributes, the same information already decoded. That is the whole of what a read back can honestly be here, and it is the useful half, because nobody reads a hex block to work out which monitor they are looking at.

The source line is the point, not a footnote

“What the panel says” and “what this app wrote when it made the display” are different claims, and a report that blurs them is worse than no report. The second line always says which one you are looking at.

mdctl edid virtual:4C2E9A31-7F0B-4E58-9C1D-8A0F2B6D5E77
display   virtual:4C2E9A31-7F0B-4E58-9C1D-8A0F2B6D5E77
source    what MoreDisplays wrote when it created this display
name      Home ultrawide
vendor    MD (19780)
model     1
native    5120x1440

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl activate

Switch the machine to a group.

mdctl activate <name>

Arguments

Argument Type Required Meaning
name string Yes The group to switch to, matched by name.

What it prints

One line naming the group that was activated.

Creates the displays the group describes, puts every display the group positions into the mode it recorded, applies the arrangement, and sets the main display.

mdctl activate Office
Activated "Office".

One name, matched exactly

No fuzzy matching and no partial match. mdctl list prints the names in exactly the form this command expects, one per line, ready to be copied. Quote anything with a space in it.

mdctl activate 'MacBook Air 13-inch (M1)'
Activated "MacBook Air 13-inch (M1)".

A group whose name begins with a dash is still reachable: -- ends option parsing.

mdctl activate Grading
mdctl: no group named "Grading"

What happens if it fails halfway

Activation is a transaction. If a display cannot be created, or the layout will not settle, the previous state comes back and the command exits 1. If restoring the previous state fails too, the app releases every virtual display and puts a physical one back as main.

The machine is never left with nothing on screen. That is the invariant the whole design is built around, and it has a test behind it.

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl deactivate

Tear the active group down.

mdctl deactivate

What it prints

One line confirming the group was deactivated.

Releases the virtual displays the active group created, and leaves physical displays alone.

mdctl deactivate
Deactivated "Office".

With no group active it is not an error. There is nothing to tear down, the command says so, and it exits 0, so a script that runs it twice does not fail on the second run.

mdctl deactivate
No group is active.

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl capture

Capture the live layout into a group.

mdctl capture [name]

Arguments

Argument Type Required Meaning
name string No The group to write into. Omit to write into the active group.

What it prints

One line naming the group that was written.

Reads the arrangement the machine is in right now and writes it into a group: where each display sits, what mode it is in, and which one is main.

mdctl capture
Captured the layout into "Home".

With no name it writes into the active group, which is the common case: you dragged a display in System Settings and want the group to remember it. With a name it writes into that group instead.

mdctl capture Office
Captured the layout into "Office".

What it does not change

Capture does not add displays to a group or remove them. Membership is something you decide in the window; the arrangement is something the machine can observe.

That distinction was learned the hard way. Background auto-capture once shared this command’s code, and a rolled-back activation reached it: a single-display group came back holding both attached panels, with connected displays turned on, and nothing announced it. Adopting every panel is right when you asked for it and destructive when you did not.

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl help

Print the usage text.

mdctl help

What it prints

The usage text on stdout.

The usage text: every command, its arguments, the environment variable and the exit codes. It needs no running app, so it is also the quickest way to check the tool is on your PATH and can run.

mdctl help
MoreDisplays command line tool.

Usage: mdctl <command> [arguments]

Commands:
list print every group name, one per line
displays print the displays the machine is showing
edid <reference> print what one display says about itself
protect <reference> <state> defend one display's resolution in the active group, or stop
activate <name> switch the machine to a group
deactivate tear the active group down
capture [name] capture the live layout into a group
status print the app version, the engine state and the group list
docs print this description of the tool, as JSON
help print the usage text

Environment:
MDCTL_SOCKET path to the app's socket, for testing

Exit codes: 0 success, 1 command failed, 2 usage error, 3 app not running.

-h and --help print the same text and are accepted anywhere in the arguments, including before a command. A usage error prints it too, after the error itself.

mdctl --help > /dev/null && echo installed
installed

The command list is generated from the same table docs prints, so the help text cannot name a command the tool does not have, or miss one it does.

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.

mdctl docs

Print this description of the tool, as JSON.

mdctl docs

What it prints

A single JSON object on stdout. Nothing is sent to the app.

The tool’s own description of its grammar, as JSON: every command, its arguments, the global flags, the exit codes, the environment, and what each command prints.

mdctl docs | jq -r '.commands[].name'
list
displays
edid
protect
activate
deactivate
capture
status
docs
help

Why it exists

This site used to generate its command reference from a file transcribed by hand out of the tool’s source, with a comment admitting the tool was right whenever the two disagreed. A copy nobody can check is a copy that goes stale, and it did: two commands went missing from it within a day.

So the tool describes itself, this command prints that description, and a test asserts the parser accepts exactly the commands named in it. Every page under /docs/cli is generated from this output.

mdctl docs | jq '{schemaVersion, binary, commands: (.commands | length)}'
{
"schemaVersion": 2,
"binary": "mdctl",
"commands": 10
}

schemaVersion rises when a field changes meaning, so a consumer that has not caught up can say so instead of guessing. It is answered by the tool rather than the app, so it works whether or not anything is listening on the socket.

Exit codes

The same four across every command, so a script can branch on the number without knowing which command produced it.

Code Name When
0 ok The command succeeded.
1 commandFailed The app understood the command and refused or failed it. The reason is on stderr.
2 usage The arguments were wrong. Nothing was sent.
3 appNotRunning No app is listening on the socket.