#!/bin/bash # Desk Host on a macOS desktop, all in one, run as the desktop's user in its login session: # apps/deskhost/macos/install.sh [--bin ] [--peers 10.0.0.222] [--host ] [--port 8790] [--fps 120] # apps/deskhost/macos/install.sh --settings-only --peers (only sets the keys and restarts the running host) # 1. builds the host (cargo --release, unless --bin names a build) and puts it at ~/.makepad/deskhost/bin/deskhost; # 2. "Desk Host.app" (bundle id nl.makepad.deskhost, in /Applications, else ~/Applications) is a small launcher # (macos/launcher.c) that starts that host as its child. macOS keeps the Screen Recording and Accessibility # grants for the app's code identity; the launcher is built once per Mac and never touched again, so the # grants survive every host update (a child asks with its parent's grants). An app made anew (--new-app, or a # deleted app) needs the grants again; # 3. writes ~/.makepad/deskhost.conf when there is none, or sets the keys given (--peers/--host/--port/--fps); # 4. (re)loads the LaunchAgent nl.makepad.deskhost: started at login, restarted when it dies. # It never asks for a grant: the user gives them in System Settings > Privacy & Security (Screen & System # Audio Recording, Accessibility: "Desk Host"). The host says which one is missing in ~/.makepad/deskhost.status, # its log (~/.makepad/deskhost.log) and to every viewer. # Undo: launchctl bootout gui/$(id -u)/nl.makepad.deskhost; then delete ~/Library/LaunchAgents/nl.makepad.deskhost.plist, # the app and ~/.makepad/deskhost/bin. set -u ROOT=$(cd "$(dirname "$0")/../../.." && pwd) ID=nl.makepad.deskhost HOSTBIN=$HOME/.makepad/deskhost/bin/deskhost AGENT=$HOME/Library/LaunchAgents/$ID.plist CONF=$HOME/.makepad/deskhost.conf bin=; peers=; host=; port=; fps=; only=0; new_app=0 while [ $# -gt 0 ]; do case $1 in --bin) bin=$2; shift 2;; --peers) peers=$2; shift 2;; --host) host=$2; shift 2;; --port) port=$2; shift 2;; --fps) fps=$2; shift 2;; --settings-only) only=1; shift;; --new-app) new_app=1; shift;; *) echo "unknown argument $1" >&2; exit 2;; esac done fail() { echo "DESKHOST-INSTALL-FAILED: $*"; exit 1; } set_keys() { for kv in "allow_peers=$peers" "host=$host" "port=$port" "fps=$fps"; do k=${kv%%=*}; v=${kv#*=} [ -n "$v" ] || continue if grep -q "^$k *=" "$CONF"; then sed "s|^$k *=.*|$k = $v|" "$CONF" > "$CONF.new" && mv "$CONF.new" "$CONF" else echo "$k = $v" >> "$CONF" fi echo "set $k = $v" done } if [ $only = 1 ]; then [ -s "$CONF" ] || fail "no $CONF" set_keys launchctl kickstart -k "gui/$(id -u)/$ID" || fail "kickstart" sleep 3 echo "status: $(cat "$HOME/.makepad/deskhost.status" 2>/dev/null)" echo "DESKHOST-SETTINGS-OK" exit 0 fi # 1. the build if [ -z "$bin" ]; then (cd "$ROOT" && CARGO_INCREMENTAL=0 cargo build --release -p makepad-app-deskhost) || fail "build" bin=${CARGO_TARGET_DIR:-$ROOT/target}/release/makepad-app-deskhost fi [ -x "$bin" ] || fail "no host build at $bin" # 2. the host itself, swapped in (a running host keeps its own copy) mkdir -p "$(dirname "$HOSTBIN")" || fail "bin dir" cp "$bin" "$HOSTBIN.new" && mv "$HOSTBIN.new" "$HOSTBIN" || fail "copy host" # the app: made once, then left alone (its identity holds the grants) if [ -w /Applications ]; then APPS=/Applications; else APPS=$HOME/Applications; mkdir -p "$APPS"; fi APP="$APPS/Desk Host.app" if [ $new_app = 1 ] || [ ! -x "$APP/Contents/MacOS/Desk Host" ]; then STAGE="$APPS/.Desk Host.app.new" rm -rf "$STAGE" mkdir -p "$STAGE/Contents/MacOS" || fail "stage" cc -O2 -Wall -o "$STAGE/Contents/MacOS/Desk Host" "$ROOT/apps/deskhost/macos/launcher.c" || fail "launcher build" cat > "$STAGE/Contents/Info.plist" < CFBundleIdentifier$ID CFBundleNameDesk Host CFBundleDisplayNameDesk Host CFBundleExecutableDesk Host CFBundlePackageTypeAPPL CFBundleShortVersionString1 CFBundleVersion1 LSMinimumSystemVersion13.0 LSUIElement NSScreenCaptureUsageDescriptionDesk Host shows this screen to your Desk Link viewers. EOF codesign --force --sign - --identifier "$ID" "$STAGE" || fail "codesign" launchctl bootout "gui/$(id -u)/$ID" 2>/dev/null rm -rf "$APP" mv "$STAGE" "$APP" || fail "app" echo "made $APP (new identity: give it the grants)" fi codesign --verify "$APP" || fail "codesign verify" echo "app identity: $(codesign -d -r- "$APP" 2>&1 | sed -n "s/^# designated => //p")" # 3. settings (the fleet key itself is installed apart, never by this script) mkdir -p "$HOME/.makepad" if [ ! -s "$CONF" ]; then cat > "$CONF" </dev/null 2>&1; } mkdir -p "$HOME/Library/LaunchAgents" cat > "$AGENT.new" < Label$ID ProgramArguments$APP/Contents/MacOS/Desk Host RunAtLoad KeepAliveSuccessfulExit ThrottleInterval10 ProcessTypeInteractive LimitLoadToSessionTypeAqua StandardErrorPath$HOME/.makepad/deskhost.stderr.log EOF if loaded && cmp -s "$AGENT.new" "$AGENT"; then rm -f "$AGENT.new" launchctl kickstart -k "gui/$(id -u)/$ID" || fail "kickstart" else launchctl bootout "gui/$(id -u)/$ID" 2>/dev/null for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do loaded || break; sleep 0.5; done mv "$AGENT.new" "$AGENT" || fail "agent plist" ok=0 for i in 1 2 3 4 5; do launchctl bootstrap "gui/$(id -u)" "$AGENT" 2>/dev/null && { ok=1; break; }; sleep 1; done [ $ok = 1 ] || fail "launchctl bootstrap" fi sleep 3 echo "status: $(cat "$HOME/.makepad/deskhost.status" 2>/dev/null)" echo "DESKHOST-INSTALL-OK $APP"