#!/bin/bash # --- CONFIGURATION --- SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" if [ -f "$SCRIPT_DIR/version.json" ]; then APP_VERSION=$(python3 -c "import json; print(json.load(open('$SCRIPT_DIR/version.json'))['version'])" 2>/dev/null || echo "1.0.8") else APP_VERSION="1.0.8" fi APP_NAME="Snap Back" # --- CYBERPUNK THEME COLORS --- BOLD='\033[1m' # Neon Palette (256-color) NEON_CYAN='\033[38;5;51m' NEON_BLUE='\033[38;5;33m' NEON_GREEN='\033[38;5;82m' NEON_PURPLE='\033[38;5;129m' ALERT_RED='\033[38;5;196m' WARN_YELLOW='\033[38;5;226m' DIM_GREY='\033[38;5;238m' TXT_GREY='\033[38;5;250m' NC='\033[0m' # No Color # --- UI UTILITIES --- # Tactical Separator print_line() { printf "${NEON_BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" } # Modular Step Function show_step() { local step_num="$1" local desc="$2" # Pads number with zero if less than 10 local fmt_num=$(printf "%02d" $step_num) printf "${NEON_PURPLE}❯❯ [${fmt_num}] ${NEON_CYAN}${desc}${NC}\n" } # Spinner with rotating slash show_spinner() { local pid=$1 local delay=0.1 local spinstr='|/-\' printf " " while ps -p $pid > /dev/null 2>&1; do local temp=${spinstr#?} printf "${NEON_GREEN}%c${NC}" "$spinstr" local spinstr=$temp${spinstr%"$temp"} sleep $delay printf "\b" done printf " \b\b" } # Cyber Header show_banner() { clear printf "${NEON_CYAN}" cat << "EOF" █▀ █▄ █ ▄▀█ █▀█ █▄▄ ▄▀█ █▀▀ █▄▀ ▄█ █ ▀█ █▀█ █▀▀ █▄█ █▀█ █▄▄ █ █ EOF printf "${NC}\n" print_line printf " ${BOLD}BUILDER & INSTALLER PROTOCOL${NC}\n" printf " ${TXT_GREY}Author: Arun Thomas${NC}\n" print_line printf "\n" } # High-Impact Outcome: Success show_success_art() { printf "\n${NEON_GREEN}" cat << "EOF" █▀ █ █ █▀▀ █▀▀ █▀▀ █▀ █▀ ▄█ █▄█ █▄▄ █▄▄ ██▄ ▄█ ▄█ EOF printf "${NC}\n" } # High-Impact Outcome: Failure show_failure_art() { printf "\n${ALERT_RED}" cat << "EOF" █▀▀ ▄▀█ █ █ █▀▀ █▀▄ █▀ █▀█ █ █▄▄ ██▄ █▄▀ EOF printf "${NC}\n" } # --- WINDOW RESIZE --- resize_window() { osascript -e 'tell application "Terminal" to set number of rows of front window to 42' -e 'tell application "Terminal" to set number of columns of front window to 85' &>/dev/null || \ osascript -e 'tell application "iTerm" to set columns of current session of current window to 85' -e 'tell application "iTerm" to set rows of current session of current window to 42' &>/dev/null || true } # --- EXECUTION START --- resize_window show_banner # --- STEP 0: SYSTEM CHECK --- show_step 0 "Scanning system for compilation tools..." if ! xcode-select -p &>/dev/null; then printf " ${WARN_YELLOW}⚠ Tools missing. Initiating download sequence...${NC}\n" xcode-select --install print_line printf " ${WARN_YELLOW}WAITING FOR USER INTERACTION...${NC}\n" printf " Please complete the installer prompt.\n" printf " Press [ENTER] when installation is confirmed..." read -r if ! xcode-select -p &>/dev/null; then show_failure_art printf "\n" printf " ${ALERT_RED}Installation not confirmed. Xcode Command Line Tools are required.${NC}\n" printf " ${TXT_GREY}Please rerun the script and complete the installation.${NC}\n" exit 1 fi else printf " ${NEON_GREEN}✔ Xcode Command Line Tools detected.${NC}\n" fi printf "\n" # --- STEP 1: ENVIRONMENT --- # Create a secure temporary directory BUILD_DIR=$(mktemp -d) show_step 1 "Initializing clean build workspace..." printf " Target Path: ${TXT_GREY}$BUILD_DIR${NC}\n" # Move into the temp directory pushd "$BUILD_DIR" > /dev/null # --- STEP 2: ASSET GENERATION --- show_step 2 "Synthesizing high-resolution assets..." ( cat > GenIcon.swift <<'EOF' import Cocoa let size = CGSize(width: 1024, height: 1024) let image = NSImage(size: size) image.lockFocus() let context = NSGraphicsContext.current!.cgContext // 1. Background (Dark Rounded Rect) let bgPath = NSBezierPath(roundedRect: NSRect(origin: .zero, size: size), xRadius: 224, yRadius: 224) NSColor(red: 0.12, green: 0.12, blue: 0.14, alpha: 1.0).setFill() // Dark Grey bgPath.fill() context.setShadow(offset: CGSize(width: 0, height: -10), blur: 25, color: NSColor.black.withAlphaComponent(0.6).cgColor) // 2. Corners (Restored & Adjusted) let strokePath = NSBezierPath() strokePath.lineWidth = 70 strokePath.lineCapStyle = .round strokePath.lineJoinStyle = .round let padding: CGFloat = 160 let legLength: CGFloat = 160 func drawCorner(start: CGPoint, corner: CGPoint, end: CGPoint) { strokePath.move(to: start) strokePath.line(to: corner) strokePath.line(to: end) } // Top Left drawCorner(start: CGPoint(x: padding, y: 1024 - padding - legLength), corner: CGPoint(x: padding, y: 1024 - padding), end: CGPoint(x: padding + legLength, y: 1024 - padding)) // Top Right drawCorner(start: CGPoint(x: 1024 - padding - legLength, y: 1024 - padding), corner: CGPoint(x: 1024 - padding, y: 1024 - padding), end: CGPoint(x: 1024 - padding, y: 1024 - padding - legLength)) // Bottom Left drawCorner(start: CGPoint(x: padding, y: padding + legLength), corner: CGPoint(x: padding, y: padding), end: CGPoint(x: padding + legLength, y: padding)) // Bottom Right drawCorner(start: CGPoint(x: 1024 - padding - legLength, y: padding), corner: CGPoint(x: 1024 - padding, y: padding), end: CGPoint(x: 1024 - padding, y: padding + legLength)) let cyan = NSColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 1.0) cyan.setStroke() strokePath.stroke() // 3. Central Arrow (Reduced Size & Centered) let arrowPath = NSBezierPath() let center = CGPoint(x: 512, y: 512) let radius: CGFloat = 200 // Reduced radius to fit inside corners let startAngle: CGFloat = -30 let endAngle: CGFloat = 220 arrowPath.appendArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false) arrowPath.lineWidth = 70 arrowPath.lineCapStyle = .round arrowPath.stroke() // 4. Arrow Head (Classic & Aligned) let endRad = endAngle * .pi / 180.0 let tip = CGPoint( x: center.x + radius * cos(endRad), y: center.y + radius * sin(endRad) ) let tangent = endAngle + 90 let headPath = NSBezierPath() // Classic Triangular Arrowhead headPath.move(to: CGPoint(x: 0, y: 0)) // Tip headPath.line(to: CGPoint(x: -120, y: 100)) // Top Back headPath.line(to: CGPoint(x: -80, y: 0)) // Center Back (Slight indent) headPath.line(to: CGPoint(x: -120, y: -100))// Bottom Back headPath.close() var transform = AffineTransform() // Move to the end of the arc transform.translate(x: tip.x, y: tip.y) // Rotate to tangent transform.rotate(byDegrees: tangent) // Push FORWARD slightly (55px) along the tangent to extend past the round cap. // 35px is flush with cap (70/2), 55px pushes it out to the "corner". transform.translate(x: 55, y: 0) headPath.transform(using: transform) cyan.setFill() headPath.fill() image.unlockFocus() // Export if let tiff = image.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiff), let png = bitmap.representation(using: .png, properties: [:]) { try? png.write(to: URL(fileURLWithPath: "AppIcon.png")) } EOF swiftc GenIcon.swift -o GenIcon ./GenIcon # Generate Iconset mkdir AppIcon.iconset sips -z 16 16 AppIcon.png --out AppIcon.iconset/icon_16x16.png > /dev/null sips -z 32 32 AppIcon.png --out AppIcon.iconset/icon_16x16@2x.png > /dev/null sips -z 32 32 AppIcon.png --out AppIcon.iconset/icon_32x32.png > /dev/null sips -z 64 64 AppIcon.png --out AppIcon.iconset/icon_32x32@2x.png > /dev/null sips -z 128 128 AppIcon.png --out AppIcon.iconset/icon_128x128.png > /dev/null sips -z 256 256 AppIcon.png --out AppIcon.iconset/icon_128x128@2x.png > /dev/null sips -z 256 256 AppIcon.png --out AppIcon.iconset/icon_256x256.png > /dev/null sips -z 512 512 AppIcon.png --out AppIcon.iconset/icon_256x256@2x.png > /dev/null sips -z 512 512 AppIcon.png --out AppIcon.iconset/icon_512x512.png > /dev/null sips -z 1024 1024 AppIcon.png --out AppIcon.iconset/icon_512x512@2x.png > /dev/null iconutil -c icns AppIcon.iconset ) & show_spinner $! printf " ${NEON_GREEN}✔ Assets Compiled (New Logo).${NC}\n\n" # --- STEP 3: BUNDLE STRUCTURE --- show_step 3 "Constructing Application Bundle..." mkdir -p "$APP_NAME.app/Contents/MacOS" mkdir -p "$APP_NAME.app/Contents/Resources" mv AppIcon.icns "$APP_NAME.app/Contents/Resources/" cat > "$APP_NAME.app/Contents/Info.plist" <<'EOF' CFBundleExecutable SnapBack CFBundleIdentifier com.aoh.snapback CFBundleName __APP_NAME__ CFBundleIconFile AppIcon CFBundleShortVersionString __APP_VERSION__ CFBundleVersion 1 CFBundlePackageType APPL LSMinimumSystemVersion 10.15 NSHighResolutionCapable NSAppleEventsUsageDescription __APP_NAME__ needs to control other applications to save and restore window layouts. EOF # Inject Version and Name into Info.plist sed -i '' "s/__APP_VERSION__/$APP_VERSION/g" "$APP_NAME.app/Contents/Info.plist" sed -i '' "s/__APP_NAME__/$APP_NAME/g" "$APP_NAME.app/Contents/Info.plist" printf " ${NEON_GREEN}✔ Info.plist injected.${NC}\n\n" # --- STEP 4: SOURCE INJECTION --- show_step 4 "Injecting Source Code..." cat > SnapBack.swift <<'EOF' import Cocoa import SwiftUI struct UpdateChangelogView: View { let changelog: String var body: some View { ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 6) { Text(changelog) .font(.system(size: 12, weight: .regular)) .foregroundColor(Color(NSColor.labelColor)) .lineSpacing(3) .frame(maxWidth: .infinity, alignment: .leading) .fixedSize(horizontal: false, vertical: true) } .padding(12) } .frame(width: 360, height: 150) .background(Color(NSColor.controlBackgroundColor)) .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 8, style: .continuous) .stroke(Color(NSColor.separatorColor).opacity(0.4), lineWidth: 1) ) } } func createChangelogView(changelog: String) -> NSView { let hostingView = NSHostingView(rootView: UpdateChangelogView(changelog: changelog)) hostingView.frame = NSRect(x: 0, y: 0, width: 360, height: 150) return hostingView } // --- CONFIGURATION --- let appTitle = "__APP_NAME__" let fileManager = FileManager.default let home = fileManager.homeDirectoryForCurrentUser let storageFolder = home.appendingPathComponent("Pictures/Snap Back Profiles") try? fileManager.createDirectory(at: storageFolder, withIntermediateDirectories: true) // --- HELPERS --- func runShell(_ command: String) -> Int32 { let task = Process() task.launchPath = "/bin/zsh" task.arguments = ["-c", command] task.launch() task.waitUntilExit() return task.terminationStatus } func runAppleScript(_ source: String) -> (Bool, String?) { if let script = NSAppleScript(source: source) { var error: NSDictionary? let result = script.executeAndReturnError(&error) if let err = error { let msg = err["NSAppleScriptErrorMessage"] as? String ?? "Unknown AppleScript error" return (false, msg) } let resString = result.stringValue return (true, resString) } return (false, "Failed to initialize NSAppleScript") } func getSavedProfiles() -> [String] { guard let items = try? fileManager.contentsOfDirectory(atPath: storageFolder.path) else { return [] } return items.filter { item in let path = storageFolder.appendingPathComponent(item) var isDir: ObjCBool = false return fileManager.fileExists(atPath: path.path, isDirectory: &isDir) && isDir.boolValue && !item.hasPrefix(".") } } func showSuccess(message: String) { NSSound(named: "Glass")?.play() let script = "display notification \"\(message)\" with title \"__APP_NAME__\"" let _ = runAppleScript(script) } func showError(message: String) { let alert = NSAlert() alert.messageText = "Error" alert.informativeText = message alert.addButton(withTitle: "OK") alert.runModal() } func showScriptError(details: String?) { let alert = NSAlert() alert.messageText = "Operation Failed" var info = "__APP_NAME__ encountered an error." if let d = details { info += "\n\nError: \(d)" } // Check for permissions let err = details?.lowercased() ?? "" let isPermissionIssue = err.contains("not allowed") || err.contains("not authorized") || err.contains("(-1743)") alert.informativeText = info if isPermissionIssue { alert.addButton(withTitle: "Open Settings") alert.addButton(withTitle: "Cancel") } else { alert.addButton(withTitle: "OK") } let response = alert.runModal() if isPermissionIssue && response == .alertFirstButtonReturn { let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation")! NSWorkspace.shared.open(url) } } var aboutUpdateBtn: NSButton? func setUpdateLoading(_ loading: Bool) { DispatchQueue.main.async { guard let btn = aboutUpdateBtn else { return } btn.isEnabled = !loading btn.attributedTitle = NSAttributedString(string: loading ? "Checking..." : "Updates", attributes: [ .foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 12.5, weight: .medium) ]) } } func checkForUpdates(isManual: Bool = false) { let now = Date() if !isManual { if let lastCheck = UserDefaults.standard.object(forKey: "lastUpdateCheckDate") as? Date, now.timeIntervalSince(lastCheck) < 86400 { return // Only check once per 24 hours on automatic launch } } UserDefaults.standard.set(now, forKey: "lastUpdateCheckDate") setUpdateLoading(true) URLCache.shared.removeAllCachedResponses() let ts = Int(now.timeIntervalSince1970) let versionURLStr = "https://raw.githubusercontent.com/arunofhyd/SnapBack/main/version.json?t=\(ts)" guard let url = URL(string: versionURLStr) else { setUpdateLoading(false) if isManual { DispatchQueue.main.async { let alert = NSAlert() NSApp.activate(ignoringOtherApps: true) alert.messageText = "Check for Updates Failed" alert.informativeText = "Unable to connect to update server." alert.addButton(withTitle: "OK") alert.runModal() } } return } var request = URLRequest(url: url) request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData request.addValue("no-cache", forHTTPHeaderField: "Cache-Control") request.addValue("no-cache", forHTTPHeaderField: "Pragma") let task = URLSession.shared.dataTask(with: request) { data, response, error in setUpdateLoading(false) guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], let remoteVer = json["version"] as? String, let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { if isManual { DispatchQueue.main.async { let alert = NSAlert() NSApp.activate(ignoringOtherApps: true) alert.messageText = "Check for Updates Failed" alert.informativeText = "Could not check for updates. Please try again later." alert.addButton(withTitle: "OK") alert.runModal() } } return } let rComponents = remoteVer.split(separator: ".").compactMap { Int($0) } let cComponents = currentVer.split(separator: ".").compactMap { Int($0) } var updateAvailable = false for i in 0.. c { updateAvailable = true; break } if r < c { break } } if updateAvailable { var fullChangelog = "" if let changes = json["changelog"] as? [[String: Any]] { let unreadEntries = changes.filter { entry in guard let v = (entry["version"] as? String)?.replacingOccurrences(of: "v", with: "") else { return false } let rComp = v.split(separator: ".").compactMap { Int($0) } let cComp = currentVer.split(separator: ".").compactMap { Int($0) } for i in 0.. c { return true } if r < c { return false } } return false } fullChangelog = unreadEntries.compactMap { entry -> String? in guard let v = entry["version"] as? String, let notes = entry["changes"] as? [String] else { return nil } let list = notes.map { "• \($0)" }.joined(separator: "\n") return "Version \(v):\n\(list)" }.joined(separator: "\n\n") } DispatchQueue.main.async { let alert = NSAlert() NSApp.activate(ignoringOtherApps: true) alert.icon = NSImage(named: "AppIcon") ?? NSApp.applicationIconImage alert.messageText = "SnapBack \(remoteVer) is available" let cur = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? currentVer alert.informativeText = "You have v\(cur). Here's what's new:" if !fullChangelog.isEmpty { alert.accessoryView = createChangelogView(changelog: fullChangelog) } alert.addButton(withTitle: "Update Now") alert.addButton(withTitle: "Later") let response = alert.runModal() if response == .alertFirstButtonReturn { downloadAndInstallUpdate() } } } else if isManual { DispatchQueue.main.async { let alert = NSAlert() NSApp.activate(ignoringOtherApps: true) alert.icon = NSImage(named: "AppIcon") ?? NSApp.applicationIconImage alert.messageText = "You're up to date" let cur = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? currentVer alert.informativeText = "__APP_NAME__ v\(cur) is the latest version." alert.addButton(withTitle: "OK") alert.runModal() } } } task.resume() } func downloadAndInstallUpdate() { let commandURL = "https://raw.githubusercontent.com/arunofhyd/SnapBack/main/install-snapback.command" guard let url = URL(string: commandURL) else { return } let task = URLSession.shared.downloadTask(with: url) { tempURL, response, error in DispatchQueue.main.async { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 if error != nil || statusCode != 200 { let err = NSAlert() err.alertStyle = .warning err.messageText = "Download Failed" let reason = error?.localizedDescription ?? "Server returned HTTP \(statusCode)" err.informativeText = "Could not download the update:\n\(reason)\n\nPlease check your internet connection and try again." err.addButton(withTitle: "OK") err.runModal() return } guard let tempURL = tempURL else { return } let downloadsDir = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first! let destURL = downloadsDir.appendingPathComponent("install-snapback.command") try? FileManager.default.removeItem(at: destURL) do { try FileManager.default.copyItem(at: tempURL, to: destURL) try FileManager.default.setAttributes( [.posixPermissions: NSNumber(value: 0o755)], ofItemAtPath: destURL.path ) NSWorkspace.shared.open(destURL) } catch { let err = NSAlert() err.alertStyle = .warning err.messageText = "Could Not Save Installer" err.informativeText = "The installer was downloaded but couldn't be saved:\n\(error.localizedDescription)" err.addButton(withTitle: "OK") err.runModal() } } } task.resume() } var permissionsWindow: NSWindow? var permissionButtons: [NSButton] = [] var permissionsTimer: Timer? class PermissionsActionHandler: NSObject, NSWindowDelegate { static let shared = PermissionsActionHandler() @objc func timerFired() { checkPermissionsStatus() } @objc func openScreenRecording() { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") { NSWorkspace.shared.open(url) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { checkPermissionsStatus() } } @objc func openAccessibility() { let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary _ = AXIsProcessTrustedWithOptions(options) if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { NSWorkspace.shared.open(url) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { checkPermissionsStatus() } } @objc func openAutomation() { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") { NSWorkspace.shared.open(url) } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { checkPermissionsStatus() } } @objc func closePermissions() { permissionsTimer?.invalidate() permissionsTimer = nil permissionButtons = [] UserDefaults.standard.set(true, forKey: "HasShownFirstRunPermissions") if let win = permissionsWindow { NSApp.stopModal() win.close() permissionsWindow = nil } } func windowWillClose(_ notification: Notification) { permissionsTimer?.invalidate() permissionsTimer = nil permissionButtons = [] NSApp.stopModal() permissionsWindow = nil } } func checkPermissionsStatus() { guard permissionsWindow != nil, permissionButtons.count >= 3 else { return } // Row 0: Screen Recording var screenOk = false if #available(macOS 10.15, *) { screenOk = CGPreflightScreenCaptureAccess() } else { screenOk = true } updatePermButton(permissionButtons[0], isGranted: screenOk) // Row 1: Accessibility let axOk = AXIsProcessTrusted() updatePermButton(permissionButtons[1], isGranted: axOk) } func updatePermButton(_ btn: NSButton, isGranted: Bool) { if isGranted { if btn.title != "✓" { btn.title = "✓" btn.font = NSFont.systemFont(ofSize: 13, weight: .bold) btn.contentTintColor = .systemGreen btn.toolTip = "Granted (Click to open settings)" } } else { if btn.title != "Open" { btn.title = "Open" btn.font = NSFont.systemFont(ofSize: 12, weight: .medium) btn.contentTintColor = nil btn.toolTip = "Click to open settings" } } } func showPermissionsGuide(isFirstLaunch: Bool = false) { if permissionsWindow != nil { permissionsWindow?.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) return } struct PermItem { let symbol: String let color: NSColor let title: String let desc: String let action: Selector } let width: CGFloat = 460 let height: CGFloat = 340 let win = NSWindow( contentRect: NSRect(x: 0, y: 0, width: width, height: height), styleMask: [.titled, .closable, .fullSizeContentView], backing: .buffered, defer: false ) win.titleVisibility = .hidden win.titlebarAppearsTransparent = true win.isMovableByWindowBackground = true win.standardWindowButton(.miniaturizeButton)?.isHidden = true win.standardWindowButton(.zoomButton)?.isHidden = true win.center() win.isReleasedWhenClosed = false win.level = .floating let bg = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: width, height: height)) bg.material = .popover bg.blendingMode = .behindWindow bg.state = .active // App Icon let icon = NSImageView(frame: NSRect(x: (width - 56)/2, y: height - 72, width: 56, height: 56)) icon.image = NSImage(named: "AppIcon") ?? NSApp.applicationIconImage icon.imageScaling = .scaleProportionallyUpOrDown bg.addSubview(icon) // Title & Subtitle let title = NSTextField(labelWithString: "Permissions & Setup") title.font = NSFont.systemFont(ofSize: 20, weight: .bold) title.alignment = .center title.frame = NSRect(x: 0, y: height - 100, width: width, height: 24) bg.addSubview(title) let sub = NSTextField(labelWithString: "Configure SnapBack for full system control") sub.font = NSFont.systemFont(ofSize: 12, weight: .medium) sub.textColor = .secondaryLabelColor sub.alignment = .center sub.frame = NSRect(x: 16, y: height - 120, width: width - 32, height: 16) bg.addSubview(sub) let items: [PermItem] = [ PermItem( symbol: "record.circle.fill", color: .systemRed, title: "Screen Recording (Required)", desc: "Allows capturing window layout previews and coordinates.", action: #selector(PermissionsActionHandler.openScreenRecording) ), PermItem( symbol: "hand.point.up.left.fill", color: .systemPurple, title: "Accessibility (Required)", desc: "Allows moving, resizing, and restoring open windows.", action: #selector(PermissionsActionHandler.openAccessibility) ), PermItem( symbol: "safari.fill", color: .systemBlue, title: "Automation / Browser Tabs", desc: "Allows capturing and restoring open browser tabs.", action: #selector(PermissionsActionHandler.openAutomation) ) ] let rowHeight: CGFloat = 48 let rowYPositions: [CGFloat] = [156, 102, 48] permissionButtons = [] for (idx, item) in items.enumerated() { let rowY = rowYPositions[idx] // Icon let symView = NSImageView(frame: NSRect(x: 32, y: rowY + (rowHeight - 26)/2, width: 26, height: 26)) let symCfg = NSImage.SymbolConfiguration(pointSize: 20, weight: .semibold) symView.image = NSImage(systemSymbolName: item.symbol, accessibilityDescription: nil)?.withSymbolConfiguration(symCfg) symView.contentTintColor = item.color bg.addSubview(symView) // Text let textWidth = width - 68 - 115 let hLabel = NSTextField(labelWithString: item.title) hLabel.font = NSFont.systemFont(ofSize: 13, weight: .semibold) hLabel.frame = NSRect(x: 68, y: rowY + 24, width: textWidth, height: 18) bg.addSubview(hLabel) let dLabel = NSTextField(labelWithString: item.desc) dLabel.font = NSFont.systemFont(ofSize: 11.5, weight: .regular) dLabel.textColor = .secondaryLabelColor dLabel.lineBreakMode = .byTruncatingTail dLabel.frame = NSRect(x: 68, y: rowY + 4, width: textWidth, height: 18) bg.addSubview(dLabel) // Action Button let btn = NSButton(title: "Open", target: PermissionsActionHandler.shared, action: item.action) btn.frame = NSRect(x: width - 32 - 70, y: rowY + (rowHeight - 28)/2, width: 70, height: 28) btn.bezelStyle = .rounded btn.font = NSFont.systemFont(ofSize: 12, weight: .medium) bg.addSubview(btn) permissionButtons.append(btn) } // Done Button (Crisp contrast pill) let doneBtn = NSButton(title: "Done", target: PermissionsActionHandler.shared, action: #selector(PermissionsActionHandler.closePermissions)) doneBtn.frame = NSRect(x: (width - 150)/2, y: 10, width: 150, height: 32) doneBtn.isBordered = false doneBtn.wantsLayer = true doneBtn.layer?.backgroundColor = NSColor.white.cgColor doneBtn.layer?.cornerRadius = 16 doneBtn.layer?.masksToBounds = true doneBtn.attributedTitle = NSAttributedString(string: "Done", attributes: [ .foregroundColor: NSColor.black, .font: NSFont.systemFont(ofSize: 13, weight: .semibold) ]) bg.addSubview(doneBtn) win.delegate = PermissionsActionHandler.shared win.contentView = bg win.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) permissionsWindow = win // Initial check and auto-polling timer for live updates checkPermissionsStatus() permissionsTimer?.invalidate() let t = Timer(timeInterval: 0.8, target: PermissionsActionHandler.shared, selector: #selector(PermissionsActionHandler.timerFired), userInfo: nil, repeats: true) RunLoop.current.add(t, forMode: .common) permissionsTimer = t NSApp.runModal(for: win) } func checkFirstRun() { let key = "HasShownFirstRunPermissions" let defaults = UserDefaults.standard let axTrusted = AXIsProcessTrusted() var screenTrusted = false if #available(macOS 10.15, *) { screenTrusted = CGPreflightScreenCaptureAccess() } else { screenTrusted = true } if !axTrusted || !screenTrusted || !defaults.bool(forKey: key) { showPermissionsGuide(isFirstLaunch: true) } } var aboutWindow: NSWindow? class AboutActionHandler: NSObject, NSWindowDelegate { static let shared = AboutActionHandler() @objc func contactDev() { let subject = "SnapBack feedback" let encoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? subject if let url = URL(string: "mailto:arunthomas04042001@gmail.com?subject=\(encoded)") { NSWorkspace.shared.open(url) } } @objc func openGit() { if let url = URL(string: "https://github.com/arunofhyd/SnapBack") { NSWorkspace.shared.open(url) } } @objc func checkUpdates() { checkForUpdates(isManual: true) } @objc func closeAboutWin() { if let win = aboutWindow { NSApp.stopModal() win.close() aboutWindow = nil } } func windowWillClose(_ notification: Notification) { NSApp.stopModal() aboutUpdateBtn = nil aboutWindow = nil } } func showAbout() { if aboutWindow != nil { aboutWindow?.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) return } struct AboutFeature { let symbol: String let color: NSColor let title: String let desc: String } let features: [AboutFeature] = [ AboutFeature(symbol: "camera.viewfinder", color: .systemCyan, title: "Instant Layout Snapshots", desc: "Saves multi-monitor window geometry and active browser tabs with zero latency."), AboutFeature(symbol: "arrow.counterclockwise.circle.fill", color: .systemGreen, title: "1-Click Layout Restore", desc: "Instantly brings all open application windows and browser tabs back to their exact recorded positions."), AboutFeature(symbol: "lock.shield.fill", color: .systemPurple, title: "100% On-Device & Private", desc: "All window coordinates, screenshots, and workspace profiles stay strictly local on your Mac."), AboutFeature(symbol: "slider.horizontal.3", color: .systemBlue, title: "Multi-Profile Management", desc: "Seamlessly switch between Work, Coding, Reading, and Meeting workspace arrangements."), AboutFeature(symbol: "chevron.left.forwardslash.chevron.right", color: .systemPink, title: "Free & Open Source", desc: "SnapBack is free and open source. Check out the project repository on GitHub.") ] let width: CGFloat = 460 let textWidth: CGFloat = width - 115 let para = NSMutableParagraphStyle() para.lineSpacing = 2 let textFont = NSFont.systemFont(ofSize: 11.5, weight: .regular) let titleFont = NSFont.systemFont(ofSize: 13, weight: .semibold) var featureHeights: [CGFloat] = [] var totalFeaturesHeight: CGFloat = 0 for f in features { let attr = NSAttributedString(string: f.desc, attributes: [ .font: textFont, .paragraphStyle: para ]) let measured = attr.boundingRect( with: NSSize(width: textWidth, height: .greatestFiniteMagnitude), options: [.usesLineFragmentOrigin, .usesFontLeading]) let h = ceil(measured.height) + 24 featureHeights.append(h) totalFeaturesHeight += h + 16 } totalFeaturesHeight -= 16 let headerHeight: CGFloat = 204 let bottomSpaceNeeded: CGFloat = 124 let finalHeight = headerHeight + totalFeaturesHeight + bottomSpaceNeeded let win = NSWindow( contentRect: NSRect(x: 0, y: 0, width: width, height: finalHeight), styleMask: [.titled, .closable, .fullSizeContentView], backing: .buffered, defer: false ) win.titleVisibility = .hidden win.titlebarAppearsTransparent = true win.isMovableByWindowBackground = true win.standardWindowButton(.miniaturizeButton)?.isHidden = true win.standardWindowButton(.zoomButton)?.isHidden = true win.center() win.isReleasedWhenClosed = false win.level = .floating let bg = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: width, height: finalHeight)) bg.material = .popover bg.blendingMode = .behindWindow bg.state = .active let icon = NSImageView(frame: NSRect(x: (width - 64)/2, y: finalHeight - 88, width: 64, height: 64)) icon.image = NSImage(named: "AppIcon") ?? NSApp.applicationIconImage icon.imageScaling = .scaleProportionallyUpOrDown bg.addSubview(icon) let title = NSTextField(labelWithString: "__APP_NAME__") title.font = NSFont.systemFont(ofSize: 24, weight: .bold) title.alignment = .center title.frame = NSRect(x: 0, y: finalHeight - 124, width: width, height: 28) bg.addSubview(title) let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "__APP_VERSION__" let ver = NSTextField(labelWithString: "Version \(currentVer)") ver.font = NSFont.systemFont(ofSize: 11.5, weight: .medium) ver.textColor = .tertiaryLabelColor ver.alignment = .center ver.frame = NSRect(x: 0, y: finalHeight - 144, width: width, height: 15) bg.addSubview(ver) let sub = NSTextField(labelWithString: "Instant Window & Tab Layout Snapshots for macOS") sub.font = NSFont.systemFont(ofSize: 12, weight: .medium) sub.textColor = .secondaryLabelColor sub.alignment = .center sub.frame = NSRect(x: 16, y: finalHeight - 168, width: width - 32, height: 16) bg.addSubview(sub) var currentY = finalHeight - headerHeight for (i, f) in features.enumerated() { let itemH = featureHeights[i] let itemY = currentY - itemH let symSize: CGFloat = 24 let symView = NSImageView(frame: NSRect(x: 36, y: itemY + (itemH - symSize)/2 + 2, width: symSize, height: symSize)) let symCfg = NSImage.SymbolConfiguration(pointSize: 18, weight: .semibold) symView.image = NSImage(systemSymbolName: f.symbol, accessibilityDescription: nil)? .withSymbolConfiguration(symCfg) symView.contentTintColor = f.color bg.addSubview(symView) let titleLabel = NSTextField(labelWithString: f.title) titleLabel.frame = NSRect(x: 74, y: itemY + itemH - 20, width: textWidth, height: 18) titleLabel.font = titleFont titleLabel.textColor = .labelColor titleLabel.isEditable = false titleLabel.drawsBackground = false titleLabel.isBordered = false bg.addSubview(titleLabel) let attr = NSAttributedString(string: f.desc, attributes: [ .font: textFont, .foregroundColor: NSColor.secondaryLabelColor, .paragraphStyle: para ]) let descLabel = NSTextField(labelWithAttributedString: attr) descLabel.frame = NSRect(x: 74, y: itemY, width: textWidth, height: itemH - 22) descLabel.lineBreakMode = .byWordWrapping descLabel.maximumNumberOfLines = 0 descLabel.isEditable = false descLabel.drawsBackground = false descLabel.isBordered = false bg.addSubview(descLabel) currentY = itemY - 16 } // Author Note let credit = NSTextField(labelWithString: "Built by Arun Thomas") credit.frame = NSRect(x: 0, y: 78, width: width, height: 16) credit.alignment = .center credit.font = NSFont.systemFont(ofSize: 11.5, weight: .semibold) credit.textColor = .secondaryLabelColor bg.addSubview(credit) // Action Buttons let buttonsY: CGFloat = 26 let contactW: CGFloat = 92 let gitW: CGFloat = 92 let updateW: CGFloat = 92 let closeW: CGFloat = 104 let spacing: CGFloat = 10 let totalW = contactW + gitW + updateW + closeW + (3 * spacing) let startX = (width - totalW) / 2 let contact = NSButton(title: "Contact", target: nil, action: nil) contact.frame = NSRect(x: startX, y: buttonsY, width: contactW, height: 34) contact.isBordered = false contact.wantsLayer = true contact.layer?.backgroundColor = NSColor.white.cgColor contact.layer?.cornerRadius = 17 contact.layer?.masksToBounds = true contact.attributedTitle = NSAttributedString(string: "Contact", attributes: [ .foregroundColor: NSColor.black, .font: NSFont.systemFont(ofSize: 12.5, weight: .medium) ]) contact.target = AboutActionHandler.shared contact.action = #selector(AboutActionHandler.contactDev) bg.addSubview(contact) let github = NSButton(title: "GitHub", target: nil, action: nil) github.frame = NSRect(x: startX + contactW + spacing, y: buttonsY, width: gitW, height: 34) github.isBordered = false github.wantsLayer = true github.layer?.backgroundColor = NSColor.black.cgColor github.layer?.cornerRadius = 17 github.layer?.masksToBounds = true github.attributedTitle = NSAttributedString(string: "GitHub", attributes: [ .foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 12.5, weight: .medium) ]) github.target = AboutActionHandler.shared github.action = #selector(AboutActionHandler.openGit) bg.addSubview(github) let update = NSButton(title: "Updates", target: nil, action: nil) update.frame = NSRect(x: startX + contactW + spacing + gitW + spacing, y: buttonsY, width: updateW, height: 34) update.isBordered = false update.wantsLayer = true update.layer?.backgroundColor = NSColor.secondaryLabelColor.withAlphaComponent(0.3).cgColor update.layer?.cornerRadius = 17 update.layer?.masksToBounds = true update.attributedTitle = NSAttributedString(string: "Updates", attributes: [ .foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 12.5, weight: .medium) ]) update.target = AboutActionHandler.shared update.action = #selector(AboutActionHandler.checkUpdates) aboutUpdateBtn = update bg.addSubview(update) let close = NSButton(title: "Done", target: nil, action: nil) close.frame = NSRect(x: startX + contactW + spacing + gitW + spacing + updateW + spacing, y: buttonsY, width: closeW, height: 34) close.isBordered = false close.wantsLayer = true close.layer?.backgroundColor = NSColor.controlAccentColor.cgColor close.layer?.cornerRadius = 17 close.layer?.masksToBounds = true close.keyEquivalent = "\r" close.attributedTitle = NSAttributedString(string: "Done", attributes: [ .foregroundColor: NSColor.white, .font: NSFont.systemFont(ofSize: 12.5, weight: .medium) ]) close.target = AboutActionHandler.shared close.action = #selector(AboutActionHandler.closeAboutWin) bg.addSubview(close) win.delegate = AboutActionHandler.shared win.contentView = bg win.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) aboutWindow = win NSApp.runModal(for: win) } func deleteProfile(name: String) -> Bool { let profileDir = storageFolder.appendingPathComponent(name) do { if fileManager.fileExists(atPath: profileDir.path) { try fileManager.removeItem(at: profileDir) return true } return false } catch { return false } } // --- CORE LOGIC --- func saveProfile(name: String) -> Bool { let profileDir = storageFolder.appendingPathComponent(name) try? fileManager.createDirectory(at: profileDir, withIntermediateDirectories: true) let dataFile = profileDir.appendingPathComponent("data.scpt") let imgFile = profileDir.appendingPathComponent("preview.jpg") let status = runShell("screencapture -x -t jpg \"\(imgFile.path)\"") if status != 0 { showError(message: "Screenshot failed. Check permissions."); return false } // UPGRADE: Handlers with strict 'using terms from' to fix identifier errors let script = #""" script DataCarrier property mainData : {} end script -- HANDLERS on getSafariUrls(winIdx) set uList to {} try tell application "Safari" if (count of windows) >= winIdx then set tabList to every tab of window winIdx repeat with t in tabList set end of uList to (get URL of t) end repeat end if end tell end try return uList end getSafariUrls on getChromeUrls(theApp, winIdx) try set scriptSrc to "on run {wIdx}" & return set scriptSrc to scriptSrc & "tell application \"" & theApp & "\"" & return set scriptSrc to scriptSrc & "set uList to {}" & return set scriptSrc to scriptSrc & "if (count of windows) >= wIdx then" & return set scriptSrc to scriptSrc & "tell window wIdx" & return set scriptSrc to scriptSrc & "set tabList to every tab" & return set scriptSrc to scriptSrc & "repeat with t in tabList" & return set scriptSrc to scriptSrc & "set end of uList to (URL of t)" & return set scriptSrc to scriptSrc & "end repeat" & return set scriptSrc to scriptSrc & "end tell" & return set scriptSrc to scriptSrc & "end if" & return set scriptSrc to scriptSrc & "return uList" & return set scriptSrc to scriptSrc & "end tell" & return set scriptSrc to scriptSrc & "end run" return run script scriptSrc with parameters {winIdx} on error return {} end try end getChromeUrls set thePath to "\#(dataFile.path)" set chromiumVariants to {"Google Chrome", "Brave Browser", "Microsoft Edge", "Arc", "Vivaldi", "Opera"} tell application "Finder" to set screenBounds to bounds of window of desktop set screenW to item 3 of screenBounds set screenH to item 4 of screenBounds tell application "System Events" set processList to (every process where background only is false) repeat with proc in processList try set appName to name of proc if appName is not "__APP_NAME__" and appName is not "app_mode_loader" and appName is not "ControlCenter" then tell proc set allWindows to every window repeat with i from 1 to count of allWindows set thisWindow to item i of allWindows set wPos to position of thisWindow set wSize to size of thisWindow set absX to item 1 of wPos set absY to item 2 of wPos set absW to item 1 of wSize set absH to item 2 of wSize set relX to absX / screenW set relY to absY / screenH set relW to absW / screenW set relH to absH / screenH set wUrls to {} if appName is "Safari" then set wUrls to my getSafariUrls(i) else if appName is in chromiumVariants then set wUrls to my getChromeUrls(appName, i) else if appName is "Firefox" then try set rawUrl to value of attribute "AXValue" of combo box 1 of toolbar "Navigation" of thisWindow if rawUrl starts with "http" then set end of wUrls to rawUrl end try end if set end of mainData of DataCarrier to {procName:appName, winIndex:i, relPos:{relX, relY}, relSize:{relW, relH}, savedUrls:wUrls} end repeat end tell end if end try end repeat end tell set theFile to POSIX file thePath store script DataCarrier in theFile replacing yes """# let (success, errorMsg) = runAppleScript(script) if success { return true } else { showScriptError(details: errorMsg); return false } } func restoreProfile(name: String) { let profileDir = storageFolder.appendingPathComponent(name) let dataFile = profileDir.appendingPathComponent("data.scpt") let script = #""" set theFile to POSIX file "\#(dataFile.path)" set chromiumVariants to {"Google Chrome", "Brave Browser", "Microsoft Edge", "Arc", "Vivaldi", "Opera"} -- HANDLERS FOR RESTORE (Isolates Dictionaries) on restoreSafari(urlList) try tell application "Safari" set shouldReuse to false if (count of windows) = 0 then make new document set shouldReuse to true else if (count of windows) = 1 then try if not (exists URL of document 1) or (URL of document 1 is "") then set shouldReuse to true end try end if if not shouldReuse then make new document if (count of urlList) > 0 then set URL of document 1 to (item 1 of urlList) if (count of urlList) > 1 then repeat with k from 2 to count of urlList tell window 1 to make new tab with properties {URL:(item k of urlList)} end repeat end if end if end tell end try end restoreSafari on restoreChrome(theApp, urlList) try set scriptSrc to "on run {urls}" & return & "tell application \"" & theApp & "\"" & return set scriptSrc to scriptSrc & "if (count of urls) > 0 then" & return set scriptSrc to scriptSrc & "set reuse to false" & return set scriptSrc to scriptSrc & "if (count of windows) = 1 and (count of tabs of window 1) = 1 then" & return set scriptSrc to scriptSrc & "set u to URL of active tab of window 1" & return set scriptSrc to scriptSrc & "if u is \"\" or u starts with \"chrome://\" then set reuse to true" & return set scriptSrc to scriptSrc & "end if" & return set scriptSrc to scriptSrc & "if not reuse then make new window" & return set scriptSrc to scriptSrc & "set URL of active tab of window 1 to (item 1 of urls)" & return set scriptSrc to scriptSrc & "if (count of urls) > 1 then" & return set scriptSrc to scriptSrc & "repeat with k from 2 to count of urls" & return set scriptSrc to scriptSrc & "make new tab at end of tabs of window 1 with properties {URL:(item k of urls)}" & return set scriptSrc to scriptSrc & "end repeat" & return set scriptSrc to scriptSrc & "end if" & return set scriptSrc to scriptSrc & "end if" & return set scriptSrc to scriptSrc & "end tell" & return set scriptSrc to scriptSrc & "end run" run script scriptSrc with parameters {urlList} end try end restoreChrome on restoreFirefox(theApp, urlList) try tell application theApp if (count of urlList) > 0 then repeat with k from 1 to count of urlList open location (item k of urlList) delay 0.2 end repeat end if end tell end try end restoreFirefox try set loadedScript to load script theFile set windowPositions to mainData of loadedScript tell application "System Events" set visible of (every process where visible is true and name is not "__APP_NAME__") to false end tell delay 0.5 tell application "Finder" to set screenBounds to bounds of window of desktop set screenW to item 3 of screenBounds set screenH to item 4 of screenBounds -- MERGED RESTORATION LOOP repeat with winRecord in windowPositions set appName to procName of winRecord set urlList to savedUrls of winRecord set rawIndex to winIndex of winRecord -- Geometry Calcs set relPos to relPos of winRecord set relSize to relSize of winRecord set relX to item 1 of relPos set relY to item 2 of relPos set relW to item 1 of relSize set relH to item 2 of relSize set targetX to (relX * screenW) set targetY to (relY * screenH) set targetW to (relW * screenW) set targetH to (relH * screenH) if targetX < 0 then set targetX to 0 if targetY < 0 then set targetY to 25 set targetPos to {targetX, targetY} set targetSize to {targetW, targetH} try -- 1. Activate & Restore Content tell application appName to activate delay 0.2 if appName is "Safari" then my restoreSafari(urlList) delay 0.5 -- Allow window creation else if appName is in chromiumVariants then my restoreChrome(appName, urlList) delay 0.5 else if appName is "Firefox" then my restoreFirefox(appName, urlList) delay 0.5 else -- Generic App: Just wait delay 0.2 end if -- 2. Restore Geometry tell application "System Events" to tell process appName set targetIndex to rawIndex -- For browsers, target the frontmost window (Window 1) as we just created/touched it if (appName is "Safari" or appName is "Firefox" or appName is in chromiumVariants) then set targetIndex to 1 repeat 3 times if (count of windows) >= targetIndex then set position of window targetIndex to targetPos set size of window targetIndex to targetSize exit repeat end if delay 0.2 end repeat end tell end try end repeat end try """# let (success, errorMsg) = runAppleScript(script) if !success { showScriptError(details: errorMsg) } else { showSuccess(message: "Restored!") } } // --- UI SETUP (PRESERVED) --- let app = NSApplication.shared app.setActivationPolicy(.regular) let alert = NSAlert() alert.messageText = "\(appTitle)" alert.informativeText = "Instantly save and restore your workspace, Never lose your layout again." alert.addButton(withTitle: "Restore") alert.addButton(withTitle: "Save New...") alert.addButton(withTitle: "Close") alert.addButton(withTitle: "About") if alert.buttons.count > 2 { alert.buttons[2].keyEquivalent = "\u{1b}" } let viewWidth: CGFloat = 450 let viewHeight: CGFloat = 300 let container = NSView(frame: NSRect(x: 0, y: 0, width: viewWidth, height: viewHeight)) let dropdown = NSPopUpButton(frame: NSRect(x: 0, y: 260, width: 300, height: 25)) let revealBtn = NSButton(frame: NSRect(x: 310, y: 260, width: 40, height: 25)) revealBtn.title = "📂" revealBtn.bezelStyle = .rounded revealBtn.toolTip = "Reveal Profiles in Finder" let deleteBtn = NSButton(frame: NSRect(x: 360, y: 260, width: 80, height: 25)) deleteBtn.title = "Delete"; deleteBtn.bezelStyle = .rounded // --- ADAPTIVE FRAME LOGIC --- let maxW: CGFloat = 450 let maxH: CGFloat = 250 let imgContainer = NSView(frame: NSRect(x: 0, y: 0, width: maxW, height: maxH)) imgContainer.wantsLayer = true imgContainer.layer?.borderWidth = 1.0 imgContainer.layer?.borderColor = NSColor.clear.cgColor imgContainer.layer?.cornerRadius = 6.0 let gap: CGFloat = 4 let imgView = NSImageView(frame: NSRect(x: gap, y: gap, width: maxW - (gap*2), height: maxH - (gap*2))) imgView.imageScaling = .scaleAxesIndependently imgView.imageFrameStyle = .none imgView.wantsLayer = true imgView.layer?.cornerRadius = 4.0 imgView.layer?.masksToBounds = true imgContainer.addSubview(imgView) // --- HANDLERS --- func refreshList() { let profiles = getSavedProfiles() dropdown.removeAllItems() dropdown.addItems(withTitles: profiles) if profiles.isEmpty { dropdown.addItem(withTitle: "No Profiles Found"); deleteBtn.isEnabled = false } else { deleteBtn.isEnabled = true } updateImage() } func updateImage() { guard let selected = dropdown.titleOfSelectedItem else { imgView.image = nil imgContainer.layer?.borderColor = NSColor.clear.cgColor return } let profileDir = storageFolder.appendingPathComponent(selected) let imgPath = profileDir.appendingPathComponent("preview.jpg") if fileManager.fileExists(atPath: imgPath.path), let img = NSImage(contentsOf: imgPath) { imgView.image = img imgContainer.layer?.borderColor = NSColor.tertiaryLabelColor.cgColor let imgSize = img.size let ratio = imgSize.width / imgSize.height var targetW = maxW var targetH = maxW / ratio if targetH > maxH { targetH = maxH targetW = maxH * ratio } imgContainer.frame = NSRect(x: (viewWidth - targetW) / 2, y: (maxH - targetH) / 2, width: targetW, height: targetH) imgView.frame = NSRect(x: gap, y: gap, width: targetW - (gap*2), height: targetH - (gap*2)) } else { imgView.image = nil imgContainer.layer?.borderColor = NSColor.clear.cgColor } } class UIHandler: NSObject { @objc func dropdownChanged(_ sender: Any) { updateImage() } @objc func deleteClicked(_ sender: Any) { guard let name = dropdown.titleOfSelectedItem, name != "No Profiles Found" else { return } let confirm = NSAlert() confirm.messageText = "Delete '\(name)'?"; confirm.addButton(withTitle: "Delete"); confirm.addButton(withTitle: "Cancel") if confirm.runModal() == .alertFirstButtonReturn { if deleteProfile(name: name) { refreshList() } } } @objc func revealClicked(_ sender: Any) { NSWorkspace.shared.activateFileViewerSelecting([storageFolder]) } } let handler = UIHandler() dropdown.target = handler; dropdown.action = #selector(UIHandler.dropdownChanged(_:)) deleteBtn.target = handler; deleteBtn.action = #selector(UIHandler.deleteClicked(_:)) revealBtn.target = handler; revealBtn.action = #selector(UIHandler.revealClicked(_:)) refreshList() container.addSubview(dropdown) container.addSubview(revealBtn) container.addSubview(deleteBtn) container.addSubview(imgContainer) alert.accessoryView = container app.finishLaunching() app.activate(ignoringOtherApps: true) checkFirstRun() checkForUpdates() // --- LOOP --- while true { let response = alert.runModal() if response == .alertFirstButtonReturn { // Restore if let name = dropdown.titleOfSelectedItem, name != "No Profiles Found" { restoreProfile(name: name) Thread.sleep(forTimeInterval: 1.0) exit(0) } } else if response == .alertSecondButtonReturn { // Save while true { let nameAlert = NSAlert() nameAlert.messageText = "Name Profile"; nameAlert.addButton(withTitle: "Save"); nameAlert.addButton(withTitle: "Cancel") let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) nameAlert.accessoryView = input if nameAlert.runModal() == .alertFirstButtonReturn { let newName = input.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) if newName.isEmpty { continue } let existing = getSavedProfiles().map { $0.lowercased() } if existing.contains(newName.lowercased()) { let dup = NSAlert() dup.messageText = "Replace '\(newName)'?" dup.informativeText = "This will overwrite the existing profile." dup.addButton(withTitle: "Replace") dup.addButton(withTitle: "Cancel") if dup.runModal() == .alertSecondButtonReturn { continue } } if saveProfile(name: newName) { showSuccess(message: "Saved!") refreshList() break } else { break } } else { break } } } else if response == .alertThirdButtonReturn { exit(0) } else { showAbout() } } EOF # Inject Version and Name into Swift Source sed -i '' "s/__APP_VERSION__/$APP_VERSION/g" SnapBack.swift sed -i '' "s/__APP_NAME__/$APP_NAME/g" SnapBack.swift printf " ${NEON_GREEN}✔ Core Logic Integrated.${NC}\n\n" # --- STEP 5: COMPILATION & CLEANUP --- show_step 5 "Compiling Binary Executable..." ( swiftc SnapBack.swift -o "$APP_NAME.app/Contents/MacOS/SnapBack" ) & show_spinner $! if [ $? -eq 0 ]; then printf " ${NEON_GREEN}✔ Compilation Complete.${NC}\n\n" show_step 6 "Finalizing and Signing..." # Ensure binary is executable chmod +x "$APP_NAME.app/Contents/MacOS/SnapBack" chmod +x "$APP_NAME.app/Contents/Resources" # --- Ad-Hoc Signing --- printf " ${TXT_GREY}Applying local signature...${NC} " codesign --force --deep -s - --requirements '=designated => identifier "com.aoh.snapback"' "$APP_NAME.app" &>/dev/null printf "${NEON_GREEN}Secure.${NC}\n" # --- STEP 6: DIRECT AUTO-INSTALL OR FALLBACK --- show_step 6 "Installing Snap Back to /Applications..." DEST="/Applications/$APP_NAME.app" pkill -x "$APP_NAME" >/dev/null 2>&1 || true INSTALLED=false if [ "$FORCE_MODAL" = "1" ] || [ "$1" = "--modal" ] || [ "$1" = "--gui" ] || [ "$2" = "--modal" ]; then INSTALLED=false elif [ -w "/Applications" ]; then rm -rf "$DEST" 2>/dev/null || true if cp -R "$APP_NAME.app" "$DEST" 2>/dev/null; then INSTALLED=true fi fi if [ "$INSTALLED" = "true" ]; then xattr -dr com.apple.quarantine "$DEST" >/dev/null 2>&1 || true printf " ${NEON_GREEN}✔ Snap Back installed to /Applications.${NC}\n\n" print_line show_success_art print_line printf "\n" printf " ${NEON_CYAN}DEPLOYMENT COMPLETE:${NC} $APP_NAME (v$APP_VERSION) is now running in your menu bar.\n" open "$DEST" afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || true popd > /dev/null rm -rf "$BUILD_DIR" exit 0 fi # Fallback to Drag Window cat > Installer.swift <<'EOF' import Cocoa import QuartzCore class DraggableIconView: NSImageView, NSDraggingSource { var fileURL: URL? func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation { return .copy } override func mouseDown(with event: NSEvent) { guard let url = fileURL, let originalImg = self.image else { return } let pasteboardItem = NSPasteboardItem() pasteboardItem.setString(url.path, forType: .fileURL) let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem) let dragRect = self.bounds let dragImg = NSImage(size: bounds.size) dragImg.lockFocus() if let ctx = NSGraphicsContext.current { ctx.imageInterpolation = .high } originalImg.draw(in: bounds) dragImg.unlockFocus() draggingItem.setDraggingFrame(dragRect, contents: dragImg) beginDraggingSession(with: [draggingItem], event: event, source: self) } } class DropTargetView: NSImageView { override func awakeFromNib() { registerForDraggedTypes([.fileURL]) } override init(frame frameRect: NSRect) { super.init(frame: frameRect); registerForDraggedTypes([.fileURL]) } required init?(coder: NSCoder) { super.init(coder: coder); registerForDraggedTypes([.fileURL]) } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { .copy } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { guard let board = sender.draggingPasteboard.propertyList(forType: .fileURL) as? String else { return false } let sourceURL = URL(fileURLWithPath: board) let appName = sourceURL.lastPathComponent let destURL = URL(fileURLWithPath: "/Applications").appendingPathComponent(appName) do { if FileManager.default.fileExists(atPath: destURL.path) { try FileManager.default.removeItem(at: destURL) } try FileManager.default.copyItem(at: sourceURL, to: destURL) let clean = Process() clean.launchPath = "/usr/bin/xattr" clean.arguments = ["-dr", "com.apple.quarantine", destURL.path] clean.standardOutput = Pipe(); clean.standardError = Pipe() try? clean.run(); clean.waitUntilExit() NSSound(named: "Glass")?.play() let alert = NSAlert() alert.messageText = "Installation Successful" alert.informativeText = "Snap Back has been installed to Applications." alert.addButton(withTitle: "Launch App") alert.addButton(withTitle: "Quit") if alert.runModal() == .alertFirstButtonReturn { NSWorkspace.shared.open(destURL) } NSApplication.shared.terminate(nil) } catch { let script = "do shell script \"cp -R '\(sourceURL.path)' /Applications/\" with administrator privileges" var errorDict: NSDictionary? if let appleScript = NSAppleScript(source: script) { appleScript.executeAndReturnError(&errorDict) if errorDict == nil { NSWorkspace.shared.open(destURL) NSApplication.shared.terminate(nil) } } } return true } } class InstallerWindow: NSWindow { override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: [.titled, .closable, .miniaturizable], backing: backingStoreType, defer: flag) self.title = "Install __APP_NAME__" self.center() } } let app = NSApplication.shared app.setActivationPolicy(.regular) let winW: CGFloat = 620, winH: CGFloat = 410 let window = InstallerWindow(contentRect: NSRect(x: 0, y: 0, width: winW, height: winH), styleMask: [.titled, .closable], backing: .buffered, defer: false) let bg = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: winW, height: winH)) bg.material = .windowBackground; bg.state = .active window.contentView = bg let titleLabel = NSTextField(labelWithString: "Install __APP_NAME__") titleLabel.frame = NSRect(x: 0, y: winH - 65, width: winW, height: 30) titleLabel.alignment = .center titleLabel.font = NSFont.systemFont(ofSize: 22, weight: .bold) bg.addSubview(titleLabel) let subLabel = NSTextField(labelWithString: "Drag __APP_NAME__ to Applications or click Instant Install below") subLabel.frame = NSRect(x: 0, y: winH - 90, width: winW, height: 20) subLabel.alignment = .center subLabel.font = NSFont.systemFont(ofSize: 13) subLabel.textColor = .secondaryLabelColor bg.addSubview(subLabel) let iconSize: CGFloat = 128 let midY: CGFloat = 145 let appIconView = DraggableIconView(frame: NSRect(x: 90, y: midY, width: iconSize, height: iconSize)) let appPath = Bundle.main.bundlePath.replacingOccurrences(of: "Install __APP_NAME__.app", with: "__APP_NAME__.app") if let image = NSImage(contentsOfFile: appPath + "/Contents/Resources/AppIcon.icns") { appIconView.image = image } appIconView.fileURL = URL(fileURLWithPath: appPath) appIconView.imageScaling = .scaleProportionallyUpOrDown bg.addSubview(appIconView) let appLabel = NSTextField(labelWithString: "__APP_NAME__") appLabel.frame = NSRect(x: 90, y: midY - 26, width: iconSize, height: 18) appLabel.alignment = .center appLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium) bg.addSubview(appLabel) // Gradient Arrow View (Fading Stem Arrow) class GradientArrowView: NSView { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) let w = bounds.width let h = bounds.height let midY = h / 2.0 let stemH: CGFloat = 10 let headH: CGFloat = 26 let headW: CGFloat = 20 let stemR = w - headW let path = NSBezierPath() path.move(to: NSPoint(x: 0, y: midY - stemH/2)) path.line(to: NSPoint(x: stemR, y: midY - stemH/2)) path.line(to: NSPoint(x: stemR, y: midY - headH/2)) path.line(to: NSPoint(x: w, y: midY)) path.line(to: NSPoint(x: stemR, y: midY + headH/2)) path.line(to: NSPoint(x: stemR, y: midY + stemH/2)) path.line(to: NSPoint(x: 0, y: midY + stemH/2)) path.close() let startColor = NSColor.labelColor.withAlphaComponent(0.0) let endColor = NSColor.labelColor.withAlphaComponent(0.50) let gradient = NSGradient(starting: startColor, ending: endColor) gradient?.draw(in: path, angle: 0) } } let arrowView = GradientArrowView(frame: NSRect(x: (winW - 70)/2, y: midY + iconSize/2 - 18, width: 70, height: 36)) bg.addSubview(arrowView) let folderIconView = DropTargetView(frame: NSRect(x: winW - 90 - iconSize, y: midY, width: iconSize, height: iconSize)) folderIconView.image = NSWorkspace.shared.icon(forFile: "/Applications") folderIconView.imageScaling = .scaleProportionallyUpOrDown bg.addSubview(folderIconView) let appsLabel = NSTextField(labelWithString: "Applications") appsLabel.frame = NSRect(x: winW - 90 - iconSize, y: midY - 26, width: iconSize, height: 18) appsLabel.alignment = .center appsLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium) bg.addSubview(appsLabel) class InstallerActionTarget: NSObject { @objc static func oneClickInstall() { let sourceURL = URL(fileURLWithPath: Bundle.main.bundlePath.replacingOccurrences(of: "Install __APP_NAME__.app", with: "__APP_NAME__.app")) let destURL = URL(fileURLWithPath: "/Applications/__APP_NAME__.app") do { if FileManager.default.fileExists(atPath: destURL.path) { try FileManager.default.removeItem(at: destURL) } try FileManager.default.copyItem(at: sourceURL, to: destURL) let clean = Process() clean.launchPath = "/usr/bin/xattr" clean.arguments = ["-dr", "com.apple.quarantine", destURL.path] clean.standardOutput = Pipe(); clean.standardError = Pipe() try? clean.run(); clean.waitUntilExit() NSSound(named: "Glass")?.play() let alert = NSAlert() alert.messageText = "Installation Successful" alert.informativeText = "Snap Back has been installed to Applications." alert.addButton(withTitle: "Launch App") alert.addButton(withTitle: "Quit") if alert.runModal() == .alertFirstButtonReturn { NSWorkspace.shared.open(destURL) } NSApplication.shared.terminate(nil) } catch { let sanitizedPath = sourceURL.path.replacingOccurrences(of: "'", with: "'\\''") let script = "do shell script \"cp -R '\(sanitizedPath)' /Applications/\" with administrator privileges" var errorDict: NSDictionary? if let appleScript = NSAppleScript(source: script) { appleScript.executeAndReturnError(&errorDict) if errorDict == nil { NSWorkspace.shared.open(destURL) NSApplication.shared.terminate(nil) } } } } } class AnimatedInstallButton: NSButton { private var trackingArea: NSTrackingArea? private var isHovered = false { didSet { animateState() } } private var isPressed = false { didSet { animateState() } } override init(frame frameRect: NSRect) { super.init(frame: frameRect) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { wantsLayer = true isBordered = false focusRingType = .none layer?.cornerRadius = 10 layer?.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.20, alpha: 1.0).cgColor contentTintColor = .white } override func updateTrackingAreas() { super.updateTrackingAreas() if let area = trackingArea { removeTrackingArea(area) } let area = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) addTrackingArea(area) trackingArea = area } override func mouseEntered(with event: NSEvent) { isHovered = true NSCursor.pointingHand.set() } override func mouseExited(with event: NSEvent) { isHovered = false NSCursor.arrow.set() } override func mouseDown(with event: NSEvent) { isPressed = true super.mouseDown(with: event) } override func mouseUp(with event: NSEvent) { isPressed = false super.mouseUp(with: event) } private func animateState() { NSAnimationContext.runAnimationGroup { ctx in ctx.duration = 0.15 ctx.allowsImplicitAnimation = true if isPressed { layer?.backgroundColor = NSColor(red: 0.14, green: 0.14, blue: 0.16, alpha: 1.0).cgColor layer?.transform = CATransform3DMakeScale(0.95, 0.95, 1.0) } else if isHovered { layer?.backgroundColor = NSColor(red: 0.28, green: 0.28, blue: 0.30, alpha: 1.0).cgColor layer?.transform = CATransform3DMakeScale(1.04, 1.04, 1.0) layer?.shadowColor = NSColor.black.cgColor layer?.shadowRadius = 8 layer?.shadowOpacity = 0.35 layer?.shadowOffset = .zero } else { layer?.backgroundColor = NSColor(red: 0.18, green: 0.18, blue: 0.20, alpha: 1.0).cgColor layer?.transform = CATransform3DIdentity layer?.shadowOpacity = 0 } } } } let oneClickBtn = AnimatedInstallButton(frame: NSRect(x: (winW - 270)/2, y: 20, width: 270, height: 42)) oneClickBtn.title = "⚡ One-Click Install to /Applications" oneClickBtn.font = NSFont.systemFont(ofSize: 13, weight: .bold) oneClickBtn.target = InstallerActionTarget.self oneClickBtn.action = #selector(InstallerActionTarget.oneClickInstall) window.contentView?.addSubview(oneClickBtn) window.makeKeyAndOrderFront(nil) app.activate(ignoringOtherApps: true) app.run() EOF sed -i '' "s/__APP_NAME__/$APP_NAME/g" Installer.swift sed -i '' "s/__APP_VERSION__/$APP_VERSION/g" Installer.swift mkdir -p "Install $APP_NAME.app/Contents/MacOS" mkdir -p "Install $APP_NAME.app/Contents/Resources" cp "$APP_NAME.app/Contents/Resources/AppIcon.icns" "Install $APP_NAME.app/Contents/Resources/" swiftc Installer.swift -o "Install $APP_NAME.app/Contents/MacOS/Install $APP_NAME" chmod +x "Install $APP_NAME.app/Contents/MacOS/Install $APP_NAME" cat > "Install $APP_NAME.app/Contents/Info.plist" < CFBundleExecutableInstall $APP_NAME CFBundleIdentifiercom.aoh.snapback.installer CFBundleNameInstall $APP_NAME CFBundleIconFileAppIcon CFBundlePackageTypeAPPL EOF INSTALLER_DIR=$(mktemp -d) mv "$APP_NAME.app" "$INSTALLER_DIR/" mv "Install $APP_NAME.app" "$INSTALLER_DIR/" popd > /dev/null rm -rf "$BUILD_DIR" open "$INSTALLER_DIR/Install $APP_NAME.app" afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || true else show_failure_art printf "\n" printf "${ALERT_RED}CRITICAL ERROR:${NC} Compilation protocol failed.\n" printf "${TXT_GREY}DEBUGGING:${NC} Review output log above for trace data.\n" popd > /dev/null exit 1 fi