From c69547321723e10c1c72b00cc9d3168fbf0b7342 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 20 Jul 2026 16:54:10 -0700 Subject: [PATCH] Keep JukeWizard resident across sessions --- juke-wizard/Sources/JukeWizard/main.swift | 19 +++++++--- juke-wizard/install.sh | 46 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/juke-wizard/Sources/JukeWizard/main.swift b/juke-wizard/Sources/JukeWizard/main.swift index 022b170ae..152be7af3 100644 --- a/juke-wizard/Sources/JukeWizard/main.swift +++ b/juke-wizard/Sources/JukeWizard/main.swift @@ -8,6 +8,7 @@ // jukewizard [ ...] [--watch ] // bin/jukewizard --queue focused ordered queue // jukewizard --spotify-search "artist or track" headless Spotify search +// jukewizard --background start resident without raising the full window // (no args → opens ~/Desktop/MASTER-playlist.m3u8 if present) // // --watch auto-pop: when a fresh audio file lands here, add it @@ -24,11 +25,13 @@ final class JukeAppDelegate: NSObject, NSApplicationDelegate { var paths: [String] = [] var selectPath: String? = nil var spotifySearch: String? = nil + var launchInBackground = false var i = 0 while i < args.count { if args[i] == "--watch", i + 1 < args.count { watch.append(args[i + 1]); i += 2; continue } if args[i] == "--select", i + 1 < args.count { selectPath = args[i + 1]; i += 2; continue } if args[i] == "--spotify-search", i + 1 < args.count { spotifySearch = args[i + 1]; i += 2; continue } + if args[i] == "--background" { launchInBackground = true; i += 1; continue } paths.append(args[i]); i += 1 } if paths.isEmpty { @@ -51,14 +54,20 @@ final class JukeAppDelegate: NSObject, NSApplicationDelegate { } controller = JukeController(library: library, watch: watch, select: selectPath, spotifySearch: spotifySearch) - controller?.showWindow(nil) - if controller?.window?.isMiniaturized == true { controller?.window?.deminiaturize(nil) } - controller?.window?.makeKeyAndOrderFront(nil) - NSApp.activate(ignoringOtherApps: true) + if launchInBackground { + controller?.window?.orderOut(nil) + } else { + controller?.showWindow(nil) + if controller?.window?.isMiniaturized == true { controller?.window?.deminiaturize(nil) } + controller?.window?.makeKeyAndOrderFront(nil) + NSApp.activate(ignoringOtherApps: true) + } // Launch Services can re-apply the previous process's minimized Dock // state just after didFinishLaunching. Restore once more on the next // run-loop turn so a relaunch can never masquerade as a crash. - DispatchQueue.main.async { [weak self] in self?.controller?.quickOpenFull() } + if !launchInBackground { + DispatchQueue.main.async { [weak self] in self?.controller?.quickOpenFull() } + } } // Stay resident when the window closes — the spinning-CD menu-bar item is diff --git a/juke-wizard/install.sh b/juke-wizard/install.sh index 3b4faf683..063f80a83 100755 --- a/juke-wizard/install.sh +++ b/juke-wizard/install.sh @@ -5,6 +5,10 @@ set -eu ROOT="$(cd "$(dirname "$0")" && pwd)" INSTALL_ROOT="${JUKEWIZARD_HOME:-$HOME/.local/lib/jukewizard}" BIN_DIR="${JUKEWIZARD_BIN_DIR:-$HOME/.local/bin}" +LAUNCH_LABEL="computer.aesthetic.jukewizard" +LAUNCH_AGENT_DIR="$HOME/Library/LaunchAgents" +LAUNCH_AGENT="$LAUNCH_AGENT_DIR/$LAUNCH_LABEL.plist" +LOG_DIR="$HOME/Library/Logs" /usr/bin/swift build -c release --package-path "$ROOT" BUILD_BIN="$(/usr/bin/swift build -c release --package-path "$ROOT" --show-bin-path)" @@ -17,5 +21,47 @@ test -d "$BUNDLE" /usr/bin/ditto "$BUNDLE" "$INSTALL_ROOT/JukeWizard_JukeWizard.bundle" /usr/bin/install -m 0755 "$ROOT/bin/jukewizard-installed" "$BIN_DIR/jukewizard" +# Own the resident menu-bar process with the user's Aqua launchd session. +# Abnormal exits restart; a deliberate Quit exits successfully and stays quit. +/bin/mkdir -p "$LAUNCH_AGENT_DIR" "$LOG_DIR" +/bin/cat > "$LAUNCH_AGENT" < + + + + Label + $LAUNCH_LABEL + ProgramArguments + + $BIN_DIR/jukewizard + --background + + RunAtLoad + + KeepAlive + + SuccessfulExit + + + ProcessType + Interactive + LimitLoadToSessionType + Aqua + ThrottleInterval + 5 + StandardOutPath + $LOG_DIR/JukeWizard.log + StandardErrorPath + $LOG_DIR/JukeWizard.log + + +EOF +/usr/bin/plutil -lint "$LAUNCH_AGENT" >/dev/null +uid="$(/usr/bin/id -u)" +/bin/launchctl bootout "gui/$uid/$LAUNCH_LABEL" 2>/dev/null || true +/usr/bin/pkill -x JukeWizard 2>/dev/null || true +/bin/launchctl bootstrap "gui/$uid" "$LAUNCH_AGENT" + echo "installed JukeWizard -> $INSTALL_ROOT/JukeWizard" echo "launcher -> $BIN_DIR/jukewizard" +echo "launch agent -> $LAUNCH_AGENT" -- 2.51.2