#!/bin/sh
# Builds padmon and (re)loads the launchd agent that runs `padmon daemon` at login.
# The agent runs ~/Applications/padmon.app (an ad-hoc-signed bundle, so macOS can grant
# it Bluetooth permission). The Claude Code hooks and the statusline use target/release/padmon.
set -eu
repo=$(cd "$(dirname "$0")/.." && pwd)
bin="$repo/target/release/padmon"
label=dev.padmon.daemon
plist="$HOME/Library/LaunchAgents/$label.plist"
log="$HOME/Library/Logs/padmon.log"
domain="gui/$(id -u)"
cd "$repo"
devenv shell -q --no-tui -- cargo build --release
for lib in $(otool -L "$bin" | awk '/\/nix\/store\/.*libiconv/ {print $1}'); do
install_name_tool -change "$lib" /usr/lib/libiconv.2.dylib "$bin" 2>/dev/null
done
codesign -s - --force "$bin" 2>/dev/null
mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs"
app="$HOME/Applications/padmon.app"
exe="$app/Contents/MacOS/padmon"
info="$app/Contents/Info.plist"
stamp="$repo/target/padmon.app.stamp"
new_info=$(cat <<'EOF'
CFBundleIdentifierdev.padmon.daemon
CFBundleNamepadmon
CFBundleExecutablepadmon
CFBundlePackageTypeAPPL
CFBundleVersion1
LSUIElement
NSBluetoothAlwaysUsageDescriptionpadmon writes session colors to the JP-Mini pad controller over Bluetooth.
EOF
)
launchctl bootout "$domain/$label" 2>/dev/null || true
# Replace and re-sign only when the build or Info.plist changed: a new signature makes macOS ask for Bluetooth permission again.
want=$({ shasum -a 256 < "$bin"; printf '%s\n' "$new_info" | shasum -a 256; echo system-libiconv; })
if [ ! -x "$exe" ] || [ "$(cat "$stamp" 2>/dev/null)" != "$want" ]; then
mkdir -p "$app/Contents/MacOS"
cp "$bin" "$exe.new"
# The Nix toolchain links libiconv from /nix/store. At login, launchd can start the daemon
# before the Nix volume is mounted, so use the system copy (in the dyld shared cache).
for lib in $(otool -L "$exe.new" | awk '/\/nix\/store\/.*libiconv/ {print $1}'); do
install_name_tool -change "$lib" /usr/lib/libiconv.2.dylib "$exe.new" 2>/dev/null
done
if otool -L "$exe.new" | grep -q /nix/store; then
echo "warning: padmon still uses a library from /nix/store; the daemon may fail at login:" >&2
otool -L "$exe.new" | grep /nix/store >&2
fi
mv -f "$exe.new" "$exe" # new inode: never modify a mapped signed binary
printf '%s\n' "$new_info" > "$info"
codesign -s - --force "$app"
printf '%s\n' "$want" > "$stamp"
echo "padmon.app updated. macOS asks once for Bluetooth permission when the controller is next used over Bluetooth."
fi
cat > "$plist" <
Label$label
ProgramArguments
$exedaemon
RunAtLoad
KeepAlive
ThrottleInterval5
ProcessTypeInteractive
StandardErrorPath$log
EOF
launchctl bootout "$domain/$label" 2>/dev/null || true
sleep 1 # bootstrap right after bootout often fails with "5: Input/output error"
launchctl bootstrap "$domain" "$plist" || { sleep 2; launchctl bootstrap "$domain" "$plist"; }
echo "padmon daemon loaded ($label). Log: $log"