Skip to content
MoreDisplays

Type to search.

Manual contents

Recipes

After this page you will have working scripts for the jobs people actually automate, rather than a list of commands to assemble yourself.

Whole solutions rather than individual commands. Each one is complete: paste it, change the group names and the host, and it runs.

All of them assume mdctl is on the PATH and the app is running. It has to be: the tool is a client, and with nothing listening every command exits 3.

Switch when you arrive at a desk

The simplest useful thing, and the one most people end up with. One command over SSH, from whichever machine you are sitting at.

#!/bin/sh
# Switch the Mac to the setup that matches the desk you are at.
# Run it after you connect, or from the client's own launch script.
set -eu

HOST=rack-mini.local
GROUP=${1:?usage: desk <group name>}

ssh -o BatchMode=yes "$HOST" mdctl activate "$GROUP"
desk 'Office'
Activated "Office".

A per-group hotkey does the same thing without a shell, and works through a screen sharing session. Switching without the window covers both.

A button that switches setups

A Stream Deck key, a Keyboard Maestro macro, a Shortcuts action or anything else that can open a URL. The URL scheme takes the group as a query parameter, so spaces need percent-encoding.

open 'moredisplays://activate?group=Office'

Tearing down takes no parameters at all.

open 'moredisplays://deactivate'

From another machine, run the tool over SSH instead. Use a key with no passphrase and a forced command on the Mac if this is anything but a private network.

ssh -o BatchMode=yes rack-mini.local mdctl activate Office
Activated "Office".

A health check that means something

"The app is running" is not the same as "somebody can connect and see a desktop". This checks the two things that actually matter, and reads them out of the four lines status prints.

#!/bin/sh
# Exit 0 only when the machine is usable over a remote session.
# Anything else prints one line and exits 1, which is what a monitor wants.
set -u

MDCTL=/usr/local/bin/mdctl

status=$("$MDCTL" status 2>/dev/null) || { echo 'MoreDisplays is not running'; exit 1; }

state=$(printf '%s\n' "$status" | sed -n 's/^state: //p')
active=$(printf '%s\n' "$status" | sed -n 's/^active group: //p')

case "$state" in
  active) ;;
  *) echo "engine state is '$state'"; exit 1 ;;
esac

[ "$active" = "(none)" ] && { echo 'no group is active'; exit 1; }

echo "ok: $active"
./moredisplays-health.sh; echo exit $?
ok: Home
exit 0

Keep the arrangement you just dragged

Drag the displays in System Settings until they are right, then write that back into the active group. One command, no arguments.

mdctl capture
Captured the layout into "Home".

The Save arrangement changes automatically setting does this on a debounce while a group is active, which is the same thing without the command. It ships off, because silently rewriting a saved group is a surprise the first time it happens.

Put the machine back from cron

For a machine that should sit on one setup and stay there. It notices when something else changed the active group, and does nothing at all the rest of the time.

#!/bin/sh
# Every ten minutes: if the machine is not on the group it should be,
# put it back. Silent when nothing is wrong, so cron only mails on trouble.
set -u

MDCTL=/usr/local/bin/mdctl
WANT='Home'

status=$("$MDCTL" status 2>/dev/null) || exit 0   # app not running: not our problem
active=$(printf '%s\n' "$status" | sed -n 's/^active group: //p')

[ "$active" = "$WANT" ] && exit 0

echo "active group is '$active', restoring '$WANT'"
"$MDCTL" activate "$WANT"
crontab -l | tail -1
*/10 * * * * /usr/local/libexec/moredisplays-restore.sh

Wait for the app before doing anything

Anything that runs at login can reach the socket before the app has opened it. Waiting a few seconds and then giving up quietly is better than failing a login.

#!/bin/sh
# The app opens its socket during launch, so a script that runs at login can
# beat it there. Wait, briefly, then give up quietly rather than failing a boot.
set -u

MDCTL=/usr/local/bin/mdctl

for _ in 1 2 3 4 5 6 7 8 9 10; do
  "$MDCTL" status >/dev/null 2>&1 && break
  sleep 1
done

"$MDCTL" activate Office || {
  echo "mdctl: could not activate, exit $?" >&2
  exit 0
}

A group can also carry activate on launch, which the app applies itself as part of its launch sequence. That is the better answer when all you want is one group at startup; this script is for the cases where something else has to happen first.

The socket lives at ~/Library/Application Support/MoreDisplays/mdctl.sock. MDCTL_SOCKET overrides it, which exists for the test harness rather than for daily use.