macOS setup
Verified on macOS 15 and 26 (Tahoe) on Apple silicon. This guide is for the machine being controlled; the client side just needs the binary.
Why signing matters
macOS gates screen capture and synthetic input behind two permissions, Screen Recording and
Accessibility, granted per application in System Settings. The grant is keyed to the app’s code
signature. An unsigned or ad-hoc-signed binary is identified by its exact hash, so every
rebuild or upgrade loses the grants and you are back to clicking prompts. A signed .app
bundle with a stable certificate keeps the grants across upgrades. A self-signed certificate you
make yourself is sufficient; no Apple developer account is needed.
Everything below assumes you build on the Mac. Cross-compiling from Linux is possible but you still need the Mac to sign.
Steps
-
Rust and the source
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal git clone https://github.com/bscott/rdc ~/code/rdc -
Create the signing identity, once, from Terminal in the GUI session (not over SSH; the trust step needs your login password):
~/code/rdc/scripts/macos/make-signing-identity.shThis creates a self-signed code-signing certificate named
rdc-devin a dedicated keychain (~/Library/Keychains/rdc-signing.keychain-db) with its password in~/.config/rdc/signing-keychain-pass(mode 0600), so later builds can sign non-interactively, including over SSH. The login keychain is locked in SSH sessions, which is why a separate keychain is used. -
Build, bundle and sign
~/code/rdc/scripts/macos/bundle-and-sign.shProduces
~/Applications/rdc.app, signed withrdc-dev. The script refuses to fall back to ad-hoc signing unlessRDC_ALLOW_ADHOC=1, for the reason above. -
Configure the allowlist
mkdir -p ~/Library/Application\ Support/rdc cat > ~/Library/Application\ Support/rdc/config.toml <<'EOF2' [serve] port = 7770 allow = ["you@example.com"] EOF2 -
Install the LaunchAgent from inside the bundle
~/Applications/rdc.app/Contents/MacOS/rdc service installThe daemon starts, logs to
~/Library/Application Support/rdc/serve.log, and pops the two permission prompts on the Mac’s screen. -
Grant the permissions on the Mac’s console (or via a KVM). Click “Open System Settings” on each prompt and turn on
rdcunder Privacy & Security → Screen & System Audio Recording and Privacy & Security → Accessibility. Ifrdcisn’t listed, add it with the+button and pick~/Applications/rdc.app(in the file picker: Locations → your home → Applications).macOS may also show a second dialog saying rdc wants to “bypass the system private window picker”; allow it. It appears because the capture fallback path uses an older API.
-
Restart and verify
launchctl kickstart -k gui/$(id -u)/dev.rdc.daemon grep permission ~/Library/Application\ Support/rdc/serve.log | tail -2 ~/Applications/rdc.app/Contents/MacOS/rdc doctorBoth permissions should read
granted, anddoctorshould show a screenshot in well under a second with a window count larger than one.
Upgrading
cd ~/code/rdc && git pull && ./scripts/macos/bundle-and-sign.sh
launchctl kickstart -k gui/$(id -u)/dev.rdc.daemon
Because the certificate is unchanged, the permissions carry over. No prompts.
Things to know
-
Monthly re-consent. Since macOS 15, the system periodically asks you to re-confirm Screen Recording for every app that uses it. When that happens screenshots show only the wallpaper;
rdc doctorreports the permission missing and the daemon log says so on restart. Click the prompt once and restart the daemon. -
Stuck grants. If Settings shows
rdcenabled but the daemon still reports “NOT granted”, the entry belongs to an older signature. Clear it and let the daemon re-prompt:tccutil reset ScreenCapture dev.rdc.daemon tccutil reset Accessibility dev.rdc.daemon launchctl kickstart -k gui/$(id -u)/dev.rdc.daemon -
Jump to a Settings pane from SSH when you’re driving the Mac remotely:
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture" open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" -
Tailscale variants. rdc finds the Tailscale LocalAPI for the App Store app, the standalone
Tailscale.app(system extension) and open-sourcetailscaled. Nothing to configure. -
Bundle identifier.
dev.rdc.daemonis the bundle id and LaunchAgent label, defined inscripts/macos/bundle-and-sign.shandsrc/service/mod.rs. -
Screenshots use the system
screencapturetool, which is fast (about 0.3 s for a 2560×1440 display). The CoreGraphics fallback is much slower on recent macOS. -
Window focus activates the owning application; raising one specific window of a multi-window app is not implemented.