Initial FocusFixer implementation
Menu-bar utility that fixes cross-display keyboard focus loss: a listen-only CGEvent tap locates the window under the cursor and reasserts app activation + AX focus on it, with debounce, tap auto-recovery, and an Accessibility permission gate. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import ApplicationServices
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
/// Requests and polls for the Accessibility (TCC) grant FocusFixer needs
|
||||
/// to observe events and drive the AX API.
|
||||
final class PermissionManager {
|
||||
private let logger = Logger(subsystem: "com.local.focusfixer", category: "PermissionManager")
|
||||
private var pollTimer: Timer?
|
||||
|
||||
var isTrusted: Bool {
|
||||
AXIsProcessTrusted()
|
||||
}
|
||||
|
||||
/// Shows the system Accessibility prompt (if not already trusted) and
|
||||
/// polls until the grant appears, then calls `onGranted` on the main
|
||||
/// queue. Safe to call repeatedly; a prior poll is cancelled first.
|
||||
func waitUntilTrusted(pollInterval: TimeInterval = 1.0, onGranted: @escaping () -> Void) {
|
||||
stopPolling()
|
||||
|
||||
if isTrusted {
|
||||
onGranted()
|
||||
return
|
||||
}
|
||||
|
||||
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary
|
||||
_ = AXIsProcessTrustedWithOptions(options)
|
||||
logger.info("Accessibility permission not granted yet; prompting and polling")
|
||||
|
||||
pollTimer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] timer in
|
||||
guard let self else {
|
||||
timer.invalidate()
|
||||
return
|
||||
}
|
||||
guard self.isTrusted else { return }
|
||||
timer.invalidate()
|
||||
self.pollTimer = nil
|
||||
self.logger.info("Accessibility permission granted")
|
||||
onGranted()
|
||||
}
|
||||
}
|
||||
|
||||
func stopPolling() {
|
||||
pollTimer?.invalidate()
|
||||
pollTimer = nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user