#!/usr/bin/env bash # ============================================================================ # Device Companion - optional event desktop experience (macOS) # # With consent, installs surprise on-screen animations and sound effects. # Runs until local midnight or until you quit. No automatic login startup. # This is an event experience, not a device patching or security update tool. # # One-line install & start: # curl -fsSL https://device-companion.pages.dev/install.sh | bash # # One-line uninstall: # curl -fsSL https://device-companion.pages.dev/uninstall.sh | bash # ============================================================================ set -euo pipefail if [[ "$(uname -s)" != "Darwin" ]]; then echo "[ERROR] Device Companion's shell installer requires macOS." >&2 exit 1 fi INSTALL_DIR="${HOME}/.corpfun-zoomies" HEADS_DIR="${INSTALL_DIR}/heads" PID_FILE="${INSTALL_DIR}/zoomies.pid" PLIST_PATH="${HOME}/Library/LaunchAgents/com.corpfun.zoomies.plist" REPO_RAW_BASE="${ZOOMIES_BASE_URL:-https://device-companion.pages.dev}" FALLBACK_BASE="https://device-companion.pages.dev" MIN_GAP_SEC="${ZOOMIES_MIN_GAP:-5}" MAX_GAP_SEC="${ZOOMIES_MAX_GAP:-15}" MUTE="${ZOOMIES_MUTE:-0}" stop_companion() { if launchctl list com.corpfun.zoomies >/dev/null 2>&1; then launchctl bootout "gui/$(id -u)/com.corpfun.zoomies" fi if [[ -f "${PLIST_PATH}" ]]; then rm -f "${PLIST_PATH}" fi if [[ -f "${PID_FILE}" ]]; then local old_pid command_line old_pid="$(cat "${PID_FILE}")" if [[ "${old_pid}" =~ ^[1-9][0-9]*$ ]] && kill -0 "${old_pid}" 2>/dev/null; then command_line="$(ps -p "${old_pid}" -o command=)" if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then kill "${old_pid}" for _ in {1..30}; do if ! kill -0 "${old_pid}" 2>/dev/null; then break; fi sleep 0.1 done if kill -0 "${old_pid}" 2>/dev/null; then command_line="$(ps -p "${old_pid}" -o command=)" if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then kill -KILL "${old_pid}" fi fi else echo "[WARNING] Ignoring a stale PID record for a different process." >&2 fi fi rm -f "${PID_FILE}" fi rm -f "${INSTALL_DIR}/zoomies.ready" } if [[ "${1:-}" == "--uninstall" || "${1:-}" == "uninstall" ]]; then echo "[*] Stopping and uninstalling Device Companion..." stop_companion if [[ -L "${INSTALL_DIR}" ]]; then echo "[ERROR] Refusing to delete a linked installation directory." >&2 exit 1 fi if [[ -d "${INSTALL_DIR}" ]]; then rm -rf "${HOME}/.corpfun-zoomies"; fi echo "[OK] Device Companion has been completely uninstalled." exit 0 fi if [[ ! "${MIN_GAP_SEC}" =~ ^[0-9]{1,4}$ || ! "${MAX_GAP_SEC}" =~ ^[0-9]{1,4}$ ]]; then echo "[ERROR] Animation gaps must be integer seconds between 1 and 3600." >&2 exit 1 fi MIN_GAP_SEC="$((10#${MIN_GAP_SEC}))" MAX_GAP_SEC="$((10#${MAX_GAP_SEC}))" if (( MIN_GAP_SEC < 1 || MAX_GAP_SEC > 3600 || MAX_GAP_SEC < MIN_GAP_SEC )); then echo "[ERROR] Animation gaps must satisfy 1 <= minimum <= maximum <= 3600." >&2 exit 1 fi EXPIRES_AT="$(date -v+1d -v0H -v0M -v0S +%s)" echo "Device Companion - optional event desktop experience" echo "Adds surprise on-screen animations and sound effects every ${MIN_GAP_SEC}-${MAX_GAP_SEC} seconds." echo "Runs in a click-through background overlay until local midnight, or quit using the DC menu." echo "Does not apply device patches or restart automatically when you sign in." echo "Sound: ${MUTE} (0 = enabled, 1 or true = muted)." echo "Installs files only under: ${INSTALL_DIR}" echo "Uninstall: bash \"${INSTALL_DIR}/uninstall.sh\"" if [[ "${1:-}" != "--accept" ]]; then printf "Install and start Device Companion? Type YES to continue: " >/dev/tty if ! read -r answer &2 exit 1 fi if [[ "${answer}" != "YES" ]]; then echo "Cancelled. No files or startup settings were changed." exit 0 fi fi echo "[*] Installing Device Companion..." mkdir -p "${HEADS_DIR}" stop_companion HEAD_FILES=( "captain.webp" "captain-back.webp" "chef.webp" "chef-back.webp" "hero.webp" "hero-back.webp" "rockstar.webp" "rockstar-back.webp" "viking.webp" "viking-back.webp" "wizard.webp" "wizard-back.webp" ) for f in "${HEAD_FILES[@]}"; do dest="${HEADS_DIR}/${f}" if [[ -s "${dest}" ]]; then continue fi if [[ -f "src/assets/heads/${f}" ]]; then cp "src/assets/heads/${f}" "${dest}" else curl -fsSL "${REPO_RAW_BASE}/src/assets/heads/${f}" -o "${dest}" \ || curl -fsSL "${FALLBACK_BASE}/src/assets/heads/${f}" -o "${dest}" fi done # Build a JS bundle of base64 data URIs for the 12 head images so WKWebView has zero file:// CORS issues { printf "window.HEAD_IMAGES = {\n" for f in "${HEAD_FILES[@]}"; do key="${f%.webp}" b64="$(base64 < "${HEADS_DIR}/${f}" | tr -d '\r\n')" printf ' "%s": "data:image/webp;base64,%s",\n' "${key}" "${b64}" done printf " \"_minGapMs\": %d,\n" "$((MIN_GAP_SEC * 1000))" printf " \"_maxGapMs\": %d,\n" "$((MAX_GAP_SEC * 1000))" printf " \"_expiresAt\": %d,\n" "$((EXPIRES_AT * 1000))" printf " \"_muted\": %s\n" "$([[ "${MUTE}" == "1" || "${MUTE}" == "true" ]] && echo "true" || echo "false")" printf "};\n" } > "${INSTALL_DIR}/heads.js" cat > "${INSTALL_DIR}/overlay.html" <<'EOF' Device Companion
EOF cat > "${INSTALL_DIR}/run-overlay.jxa.js" <<'EOF' ObjC.import('Cocoa'); ObjC.import('WebKit'); const app = $.NSApplication.sharedApplication; app.setActivationPolicy($.NSApplicationActivationPolicyAccessory); app.finishLaunching; const installDir = $('~/.corpfun-zoomies').stringByExpandingTildeInPath; const expiresPath = installDir.stringByAppendingPathComponent($('expires-at')); const expiresText = $.NSString.stringWithContentsOfFileEncodingError(expiresPath, $.NSUTF8StringEncoding, null); const expiresAt = Number(ObjC.unwrap(expiresText)) * 1000; if (!Number.isFinite(expiresAt) || expiresAt <= Date.now()) { throw new Error('Device Companion end time is missing or expired.'); } ObjC.registerSubclass({ name: 'DeviceCompanionExpiry', superclass: 'NSObject', methods: { 'expire:': { types: ['void', ['id']], implementation: function () { app.terminate(null); } } } }); const expiryTarget = $.DeviceCompanionExpiry.alloc.init; const expiryTimer = $.NSTimer.scheduledTimerWithTimeIntervalTargetSelectorUserInfoRepeats( (expiresAt - Date.now()) / 1000, expiryTarget, $('expire:'), null, false ); const statusItem = $.NSStatusBar.systemStatusBar.statusItemWithLength($.NSVariableStatusItemLength); statusItem.button.setTitle($('DC')); const menu = $.NSMenu.alloc.init; const quit = $.NSMenuItem.alloc.initWithTitleActionKeyEquivalent($('Quit Device Companion'), $('terminate:'), $('')); quit.setTarget(app); menu.addItem(quit); statusItem.setMenu(menu); const screen = $.NSScreen.mainScreen; const frame = screen.frame; const win = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer( frame, 0, // NSWindowStyleMaskBorderless $.NSBackingStoreBuffered, false ); win.setOpaque(false); win.setBackgroundColor($.NSColor.clearColor); win.setIgnoresMouseEvents(true); win.setHasShadow(false); win.setLevel(1000); // NSScreenSaverWindowLevel win.setCollectionBehavior((1 << 0) | (1 << 4) | (1 << 8)); // CanJoinAllSpaces | Stationary | FullScreenAuxiliary const config = $.WKWebViewConfiguration.alloc.init; config.mediaTypesRequiringUserActionForPlayback = 0; const viewFrame = $.NSMakeRect(0, 0, frame.size.width, frame.size.height); const webview = $.WKWebView.alloc.initWithFrameConfiguration(viewFrame, config); webview.setValueForKey($.NSNumber.numberWithBool(false), $('drawsBackground')); const htmlPath = $.NSString.stringWithString('~/.corpfun-zoomies/overlay.html').stringByExpandingTildeInPath; const htmlUrl = $.NSURL.fileURLWithPath(htmlPath); const dirUrl = htmlUrl.URLByDeletingLastPathComponent; webview.loadFileURLAllowingReadAccessToURL(htmlUrl, dirUrl); win.setContentView(webview); win.orderFrontRegardless; const readyPath = installDir.stringByAppendingPathComponent($('zoomies.ready')); $.NSFileManager.defaultManager.createFileAtPathContentsAttributes( readyPath, $('running').dataUsingEncoding($.NSUTF8StringEncoding), null ); app.run; EOF cat > "${INSTALL_DIR}/uninstall.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail PLIST_PATH="${HOME}/Library/LaunchAgents/com.corpfun.zoomies.plist" INSTALL_DIR="${HOME}/.corpfun-zoomies" PID_FILE="${INSTALL_DIR}/zoomies.pid" if [[ "$(uname -s)" != "Darwin" ]]; then echo "[ERROR] This uninstaller requires macOS." >&2 exit 1 fi echo "[*] Stopping and uninstalling Device Companion..." if launchctl list com.corpfun.zoomies >/dev/null 2>&1; then launchctl bootout "gui/$(id -u)/com.corpfun.zoomies" fi if [[ -f "${PLIST_PATH}" ]]; then rm -f "${PLIST_PATH}"; fi if [[ -f "${PID_FILE}" ]]; then old_pid="$(cat "${PID_FILE}")" if [[ "${old_pid}" =~ ^[1-9][0-9]*$ ]] && kill -0 "${old_pid}" 2>/dev/null; then command_line="$(ps -p "${old_pid}" -o command=)" if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then kill "${old_pid}" for _ in {1..30}; do if ! kill -0 "${old_pid}" 2>/dev/null; then break; fi sleep 0.1 done if kill -0 "${old_pid}" 2>/dev/null; then command_line="$(ps -p "${old_pid}" -o command=)" if [[ "${command_line}" == *"${INSTALL_DIR}/run-overlay.jxa.js"* ]]; then kill -KILL "${old_pid}" fi fi else echo "[WARNING] Ignoring a stale PID record for a different process." >&2 fi fi fi if [[ -L "${INSTALL_DIR}" ]]; then echo "[ERROR] Refusing to delete a linked installation directory." >&2 exit 1 fi if [[ -d "${INSTALL_DIR}" ]]; then rm -rf "${HOME}/.corpfun-zoomies"; fi echo "[OK] Device Companion has been completely uninstalled." EOF chmod +x "${INSTALL_DIR}/uninstall.sh" printf '%s\n' "${EXPIRES_AT}" > "${INSTALL_DIR}/expires-at" nohup /usr/bin/osascript -l JavaScript "${INSTALL_DIR}/run-overlay.jxa.js" >"${INSTALL_DIR}/worker-error.log" 2>&1 & worker_pid=$! printf '%s\n' "${worker_pid}" > "${PID_FILE}" for _ in {1..100}; do if ! kill -0 "${worker_pid}" 2>/dev/null || [[ -f "${INSTALL_DIR}/zoomies.ready" ]]; then break; fi sleep 0.1 done if ! kill -0 "${worker_pid}" 2>/dev/null || [[ ! -f "${INSTALL_DIR}/zoomies.ready" ]]; then echo "[ERROR] Device Companion failed to start:" >&2 cat "${INSTALL_DIR}/worker-error.log" >&2 stop_companion exit 1 fi echo "[OK] Device Companion's native window has started on macOS." echo " * Frequency : every ${MIN_GAP_SEC}-${MAX_GAP_SEC} seconds" echo " * Scope : full_screen (click-through overlay across all spaces)" echo " * End time : local midnight; no automatic login startup" echo "" echo "To uninstall anytime after the event, run:" echo " ~/.corpfun-zoomies/uninstall.sh" echo " or: curl -fsSL ${REPO_RAW_BASE}/uninstall.sh | bash"