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. |
Related
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. |
Related
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. |
Related
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. |
Related
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. |
Related
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. |
Related
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. |
Related
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. |
Related
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. |