#!/bin/bash set -euo pipefail # Load .env if present (for CERT_NAME etc.) ENV_FILE="$(dirname "$0")/.env" if [ -f "$ENV_FILE" ]; then set -a source "$ENV_FILE" set +a fi CERT_NAME="${CERT_NAME:-}" PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" APP_DIR="$PROJECT_DIR/build/MCT.app" INSTALL_DIR="/Applications/MCT.app" SDK=$(xcrun --show-sdk-path) echo "Building MCT..." # Kill running instance if any (matches both build/MCT.app and /Applications/MCT.app) pkill -x MCT 2>/dev/null || true sleep 0.3 # Clean previous bundle so stale resources don't linger rm -rf "$APP_DIR" mkdir -p "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources" # Compile swiftc \ -emit-executable \ -o "$APP_DIR/Contents/MacOS/MCT" \ -sdk "$SDK" \ -target arm64-apple-macosx14.0 \ -swift-version 5 \ -O \ -Xlinker -framework -Xlinker ScreenCaptureKit \ MCT/Model/WindowInfo.swift \ MCT/Model/WindowGroup.swift \ MCT/Model/WindowSnapshot.swift \ MCT/Model/LayoutState.swift \ MCT/Model/GestureProgress.swift \ MCT/API/WindowEnumerator.swift \ MCT/API/ThumbnailCapture.swift \ MCT/API/AccessibilityBridge.swift \ MCT/API/HotKeyManager.swift \ MCT/API/GestureMonitor.swift \ MCT/API/LoginItemManager.swift \ MCT/Layout/LayoutGeometry.swift \ MCT/Layout/LayoutEngine.swift \ MCT/UI/SpringAnimator.swift \ MCT/UI/OverlayWindowController.swift \ MCT/UI/OverlayView.swift \ MCT/UI/WindowThumbnailView.swift \ MCT/UI/AppGroupHeaderView.swift \ MCT/Settings/Preferences.swift \ MCT/Settings/SettingsView.swift \ MCT/App/AppDelegate.swift \ MCT/App/MCTApp.swift # App icon — generate AppIcon.icns once and cache it ICON_SRC="$PROJECT_DIR/tools/generate_icon.swift" ICON_CACHE_DIR="$PROJECT_DIR/build/icon" ICONSET_DIR="$ICON_CACHE_DIR/AppIcon.iconset" ICON_PNG="$ICON_CACHE_DIR/icon_1024.png" ICON_ICNS="$ICON_CACHE_DIR/AppIcon.icns" if [ ! -f "$ICON_ICNS" ] || [ "$ICON_SRC" -nt "$ICON_ICNS" ]; then echo "Generating app icon..." mkdir -p "$ICONSET_DIR" swift "$ICON_SRC" "$ICON_PNG" # Standard iconutil layout sips -z 16 16 "$ICON_PNG" --out "$ICONSET_DIR/icon_16x16.png" >/dev/null sips -z 32 32 "$ICON_PNG" --out "$ICONSET_DIR/icon_16x16@2x.png" >/dev/null sips -z 32 32 "$ICON_PNG" --out "$ICONSET_DIR/icon_32x32.png" >/dev/null sips -z 64 64 "$ICON_PNG" --out "$ICONSET_DIR/icon_32x32@2x.png" >/dev/null sips -z 128 128 "$ICON_PNG" --out "$ICONSET_DIR/icon_128x128.png" >/dev/null sips -z 256 256 "$ICON_PNG" --out "$ICONSET_DIR/icon_128x128@2x.png" >/dev/null sips -z 256 256 "$ICON_PNG" --out "$ICONSET_DIR/icon_256x256.png" >/dev/null sips -z 512 512 "$ICON_PNG" --out "$ICONSET_DIR/icon_256x256@2x.png" >/dev/null sips -z 512 512 "$ICON_PNG" --out "$ICONSET_DIR/icon_512x512.png" >/dev/null cp "$ICON_PNG" "$ICONSET_DIR/icon_512x512@2x.png" iconutil --convert icns "$ICONSET_DIR" --output "$ICON_ICNS" fi cp "$ICON_ICNS" "$APP_DIR/Contents/Resources/AppIcon.icns" # Info.plist cat > "$APP_DIR/Contents/Info.plist" << 'PLIST' CFBundleDevelopmentRegion en CFBundleExecutable MCT CFBundleIconFile AppIcon CFBundleIconName AppIcon CFBundleIdentifier com.mct.app CFBundleInfoDictionaryVersion 6.0 CFBundleName Mission Control Turbo CFBundleDisplayName Mission Control Turbo CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSMinimumSystemVersion 14.0 LSUIElement NSHumanReadableCopyright Copyright 2026. All rights reserved. NSScreenCaptureUsageDescription MCT needs Screen Recording permission to capture window thumbnails for the overlay. NSAccessibilityUsageDescription MCT needs Accessibility permission to register a global hotkey and activate windows. PLIST echo -n "APPL????" > "$APP_DIR/Contents/PkgInfo" # Code-sign the app so macOS recognizes it for permission grants if [ -n "$CERT_NAME" ]; then codesign --force --sign "$CERT_NAME" "$APP_DIR" else codesign --force --sign - "$APP_DIR" fi echo "Built: $APP_DIR" # Install into /Applications so launch-at-login resolves to a stable path, # permission grants survive rebuilds, and the user can launch it like a normal app. echo "Installing to $INSTALL_DIR..." rm -rf "$INSTALL_DIR" cp -R "$APP_DIR" "$INSTALL_DIR" # Re-sign in place — codesign can be path-sensitive on some setups. if [ -n "$CERT_NAME" ]; then codesign --force --sign "$CERT_NAME" "$INSTALL_DIR" else codesign --force --sign - "$INSTALL_DIR" fi # Refresh Launch Services so Finder/launchd see the new bundle. /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \ -f "$INSTALL_DIR" >/dev/null 2>&1 || true echo "Installed: $INSTALL_DIR" open "$INSTALL_DIR"