diff --git a/Sources/main.swift b/Sources/main.swift index 30ecb6f..079d9f3 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -4,14 +4,24 @@ import Carbon.HIToolbox // MARK: - Kill switch: `xdr-boost --kill` terminates any running instance if CommandLine.arguments.contains("--kill") || CommandLine.arguments.contains("-k") { + let myPID = ProcessInfo.processInfo.processIdentifier let pipe = Pipe() let proc = Process() - proc.executableURL = URL(fileURLWithPath: "/usr/bin/pkill") + proc.executableURL = URL(fileURLWithPath: "/usr/bin/pgrep") proc.arguments = ["-f", "xdr-boost"] proc.standardOutput = pipe - proc.standardError = pipe + proc.standardError = Pipe() try? proc.run() proc.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + if let output = String(data: data, encoding: .utf8) { + for line in output.split(separator: "\n") { + if let pid = Int32(line.trimmingCharacters(in: .whitespaces)), pid != myPID { + kill(pid, SIGTERM) + } + } + } fputs("All xdr-boost instances killed\n", stderr) exit(0) } @@ -40,7 +50,8 @@ class XDRApp: NSObject, NSApplicationDelegate { var boostRenderer: Renderer? var isActive = false var shouldBeActive = false // tracks user intent across sleep/lock cycles - var boostLevel: Double = 2.0 + var boostLevel: Double = 2.0 // user's chosen level — never mutated by clamping + var effectiveBoost: Double = 2.0 // actual level after clamping to maxEDR var maxEDR: CGFloat = 1.0 var hotkeyRef: EventHotKeyRef? var watchdogTimer: Timer? @@ -48,6 +59,10 @@ class XDRApp: NSObject, NSApplicationDelegate { var toggleItem: NSMenuItem! var shortcutItem: NSMenuItem! var boostItems: [NSMenuItem] = [] + var edrInfoItem: NSMenuItem! + + // Debouncing to prevent overlapping reassert calls + var pendingReassert: DispatchWorkItem? func applicationDidFinishLaunching(_ notification: Notification) { guard let dev = MTLCreateSystemDefaultDevice() else { @@ -62,10 +77,11 @@ class XDRApp: NSObject, NSApplicationDelegate { if CommandLine.arguments.count > 1, let v = Double(CommandLine.arguments[1]) { boostLevel = min(max(v, 1.0), Double(maxEDR)) } + effectiveBoost = min(boostLevel, Double(maxEDR)) setupStatusBar() registerGlobalHotkey() - observeSleepWake() + observeSystemEvents() fputs("XDR Boost ready — click menu bar icon or press Ctrl+Option+Cmd+V to toggle\n", stderr) fputs("Emergency kill: run `xdr-boost --kill` or press Ctrl+Option+Cmd+V\n", stderr) fputs("Max EDR: \(maxEDR)x\n", stderr) @@ -89,7 +105,6 @@ class XDRApp: NSObject, NSApplicationDelegate { if status == noErr { hotkeyRef = ref - // Install Carbon event handler for hotkey var eventType = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)) InstallEventHandler(GetApplicationEventTarget(), { (_, event, userData) -> OSStatus in let app = Unmanaged.fromOpaque(userData!).takeUnretainedValue() @@ -105,22 +120,24 @@ class XDRApp: NSObject, NSApplicationDelegate { func setupStatusBar() { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - if let button = statusItem.button { - button.title = "☀" - } + updateStatusBarIcon() let menu = NSMenu() - toggleItem = NSMenuItem(title: "Turn On", action: #selector(toggleXDR), keyEquivalent: "b") + toggleItem = NSMenuItem(title: "Turn On XDR Boost", action: #selector(toggleXDR), keyEquivalent: "b") toggleItem.target = self menu.addItem(toggleItem) - shortcutItem = NSMenuItem(title: "Shortcut: Ctrl+Option+Cmd+V", action: nil, keyEquivalent: "") + shortcutItem = NSMenuItem(title: "⌃⌥⌘V", action: nil, keyEquivalent: "") shortcutItem.isEnabled = false menu.addItem(shortcutItem) menu.addItem(NSMenuItem.separator()) + edrInfoItem = NSMenuItem(title: edrInfoText(), action: nil, keyEquivalent: "") + edrInfoItem.isEnabled = false + menu.addItem(edrInfoItem) + let levelHeader = NSMenuItem(title: "Brightness Level", action: nil, keyEquivalent: "") levelHeader.isEnabled = false menu.addItem(levelHeader) @@ -150,50 +167,170 @@ class XDRApp: NSObject, NSApplicationDelegate { statusItem.menu = menu } - // MARK: - Watchdog & Display Changes + func edrInfoText() -> String { + return "EDR Headroom: \(String(format: "%.1f", maxEDR))x" + } + + func updateStatusBarIcon() { + guard let button = statusItem?.button else { return } + if isActive { + let levelStr = String(format: "%.1f", effectiveBoost) + button.title = "☀︎ \(levelStr)x" + } else { + button.title = "☀" + } + } + + // MARK: - System Event Observers + + func observeSystemEvents() { + let nc = NotificationCenter.default + let wnc = NSWorkspace.shared.notificationCenter - func observeSleepWake() { // Display config changed (resolution, arrangement, external monitors) - NotificationCenter.default.addObserver( - self, selector: #selector(handleDisplayChange), - name: NSApplication.didChangeScreenParametersNotification, object: nil) + nc.addObserver(self, selector: #selector(handleDisplayChange), + name: NSApplication.didChangeScreenParametersNotification, object: nil) + + // Space / Mission Control — fires when exiting Mission Control or switching spaces + wnc.addObserver(self, selector: #selector(handleSpaceChange), + name: NSWorkspace.activeSpaceDidChangeNotification, object: nil) - // Watchdog: every 3 seconds, check if XDR should be on but overlay is dead - // This handles sleep/wake, lid close/open, lock/unlock — all of them + // Screen wake — EDR headroom resets after wake + wnc.addObserver(self, selector: #selector(handleScreenWake), + name: NSWorkspace.screensDidWakeNotification, object: nil) + + // Watchdog: every 3 seconds, check overlay health and track EDR headroom. + // Kept at 3s to avoid contributing to flicker — event observers handle the + // time-sensitive stuff, the watchdog is purely a safety net. watchdogTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in - guard let self = self, self.shouldBeActive else { return } + self?.watchdogCheck() + } + } - if let window = self.overlayWindow { - // Window exists — just make sure it's visible and in front - if !window.isVisible { - window.orderFrontRegardless() - fputs("Watchdog — window restored\n", stderr) - } - } else { - // Window is gone (nil) — need to fully recreate - self.isActive = false - self.maxEDR = NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 - if self.maxEDR > 1.0 { - self.activate() - fputs("Watchdog — XDR recreated\n", stderr) - } + func watchdogCheck() { + updateEDRHeadroom() + + guard shouldBeActive else { return } + + // EDR unavailable (e.g. brightness at minimum) + if maxEDR <= 1.0 { + if isActive { + deactivate() + fputs("Watchdog — EDR unavailable, deactivated\n", stderr) + } + return + } + + // shouldBeActive but not isActive — EDR came back after being unavailable + if !isActive { + activate() + fputs("Watchdog — EDR restored, reactivated\n", stderr) + return + } + + // Window exists but got hidden (e.g. after sleep/wake edge case) + if let window = overlayWindow, !window.isVisible { + window.orderFrontRegardless() + fputs("Watchdog — window restored\n", stderr) + } else if overlayWindow == nil { + // Window was destroyed — recreate + isActive = false + activate() + fputs("Watchdog — XDR recreated\n", stderr) + } + } + + /// Update maxEDR from current screen and adjust effective boost if needed. + /// Does NOT deactivate/reactivate — just updates the clear color in-place. + func updateEDRHeadroom() { + let currentMaxEDR = NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 + // Use epsilon to avoid flapping on float jitter + guard abs(currentMaxEDR - maxEDR) > 0.05 else { return } + + let oldMax = maxEDR + maxEDR = currentMaxEDR + edrInfoItem?.title = edrInfoText() + fputs("EDR headroom: \(String(format: "%.1f", oldMax))x → \(String(format: "%.1f", maxEDR))x\n", stderr) + + // Recompute effective boost — user's chosen level stays unchanged + let newEffective = min(boostLevel, max(Double(maxEDR), 1.0)) + if abs(newEffective - effectiveBoost) > 0.05 { + effectiveBoost = newEffective + if isActive, let view = boostView { + view.clearColor = MTLClearColor(red: effectiveBoost, green: effectiveBoost, blue: effectiveBoost, alpha: 1.0) + updateStatusBarIcon() + fputs("Effective boost: \(String(format: "%.1f", effectiveBoost))x\n", stderr) } } } + @objc func handleSpaceChange() { + guard shouldBeActive, isActive else { return } + // Debounced reassert after Mission Control / space switch animation + scheduleReassert(delay: 0.5) + } + + @objc func handleScreenWake() { + guard shouldBeActive else { return } + scheduleReassert(delay: 1.5) + } + @objc func handleDisplayChange() { + let oldMaxEDR = maxEDR maxEDR = NSScreen.main?.maximumPotentialExtendedDynamicRangeColorComponentValue ?? 1.0 - if isActive { - deactivate() - if maxEDR > 1.0 && shouldBeActive { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in - self?.activate() - fputs("Display changed — XDR refreshed\n", stderr) + edrInfoItem?.title = edrInfoText() + + guard isActive, shouldBeActive else { return } + + // If just the EDR headroom changed (brightness key), update in-place — no flash + if let window = overlayWindow, let screen = NSScreen.main { + let frameChanged = window.frame != screen.frame + let edrChanged = abs(maxEDR - oldMaxEDR) > 0.05 + + if frameChanged { + // Resolution or display arrangement changed — resize overlay + window.setFrame(screen.frame, display: false) + if let view = boostView { + view.frame = NSRect(origin: .zero, size: screen.frame.size) + } + fputs("Display changed — overlay resized\n", stderr) + } + + if edrChanged { + effectiveBoost = min(boostLevel, max(Double(maxEDR), 1.0)) + if let view = boostView { + view.clearColor = MTLClearColor(red: effectiveBoost, green: effectiveBoost, blue: effectiveBoost, alpha: 1.0) } + updateStatusBarIcon() + } + + // If EDR is gone, deactivate cleanly + if maxEDR <= 1.0 { + deactivate() + fputs("Display changed — EDR lost\n", stderr) } } } + /// Debounced reassert — cancels any pending reassert to prevent overlapping calls. + func scheduleReassert(delay: Double) { + pendingReassert?.cancel() + let work = DispatchWorkItem { [weak self] in + guard let self = self, self.shouldBeActive else { return } + self.updateEDRHeadroom() + + if let window = self.overlayWindow { + window.orderFrontRegardless() + } else if self.maxEDR > 1.0 { + self.isActive = false + self.activate() + fputs("Reasserted overlay\n", stderr) + } + } + pendingReassert = work + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: work) + } + // MARK: - Toggle @objc func toggleXDR() { @@ -208,19 +345,22 @@ class XDRApp: NSObject, NSApplicationDelegate { @objc func setBoostLevel(_ sender: NSMenuItem) { boostLevel = Double(sender.tag) / 100.0 + effectiveBoost = min(boostLevel, max(Double(maxEDR), 1.0)) for item in boostItems { item.state = (item.tag == sender.tag) ? .on : .off } if isActive, let view = boostView { - // Update in-place — no teardown, no flash - view.clearColor = MTLClearColor(red: boostLevel, green: boostLevel, blue: boostLevel, alpha: 1.0) + view.clearColor = MTLClearColor(red: effectiveBoost, green: effectiveBoost, blue: effectiveBoost, alpha: 1.0) } + updateStatusBarIcon() } // MARK: - XDR Overlay func activate() { - guard let screen = NSScreen.main else { return } + guard let screen = NSScreen.main, maxEDR > 1.0 else { return } + + effectiveBoost = min(boostLevel, Double(maxEDR)) let frame = screen.frame let window = NSWindow(contentRect: frame, styleMask: .borderless, backing: .buffered, defer: false) @@ -230,24 +370,22 @@ class XDRApp: NSObject, NSApplicationDelegate { window.hasShadow = false window.ignoresMouseEvents = true window.hidesOnDeactivate = false - window.sharingType = .none // exclude from screenshots and screen recordings - window.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary] + window.sharingType = .none + window.animationBehavior = .none + window.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary, .ignoresCycle] - // Single MTKView that both triggers EDR and provides the boost let boostView = MTKView(frame: frame, device: device) boostView.colorPixelFormat = .rgba16Float boostView.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3) boostView.layer?.isOpaque = false boostView.preferredFramesPerSecond = 10 - boostView.clearColor = MTLClearColor(red: boostLevel, green: boostLevel, blue: boostLevel, alpha: 1.0) + boostView.clearColor = MTLClearColor(red: effectiveBoost, green: effectiveBoost, blue: effectiveBoost, alpha: 1.0) if let layer = boostView.layer as? CAMetalLayer { layer.wantsExtendedDynamicRangeContent = true } boostRenderer = Renderer(device: device) boostView.delegate = boostRenderer - // Multiply compositing on the content view layer — composites with - // the desktop content BEHIND the window, not within it boostView.wantsLayer = true window.contentView = boostView window.contentView?.layer?.compositingFilter = "multiply" @@ -256,9 +394,9 @@ class XDRApp: NSObject, NSApplicationDelegate { self.boostView = boostView isActive = true - statusItem.button?.title = "☀︎" - toggleItem.title = "Turn Off" - fputs("XDR ON — \(boostLevel)x\n", stderr) + toggleItem.title = "Turn Off XDR Boost" + updateStatusBarIcon() + fputs("XDR ON — \(String(format: "%.1f", effectiveBoost))x (headroom: \(String(format: "%.1f", maxEDR))x)\n", stderr) } func deactivate() { @@ -268,8 +406,8 @@ class XDRApp: NSObject, NSApplicationDelegate { boostRenderer = nil isActive = false - statusItem.button?.title = "☀" - toggleItem.title = "Turn On" + toggleItem.title = "Turn On XDR Boost" + updateStatusBarIcon() fputs("XDR OFF\n", stderr) } diff --git a/com.xdr-boost.agent.plist b/com.xdr-boost.agent.plist index 72d126c..9f47852 100644 --- a/com.xdr-boost.agent.plist +++ b/com.xdr-boost.agent.plist @@ -11,7 +11,10 @@ RunAtLoad KeepAlive - + + SuccessfulExit + + ProcessType Interactive