import AppKit import ApplicationServices import os /// Activates the app that owns a window and reasserts keyboard focus on /// that specific window via the Accessibility API. final class FocusEnforcer { private let logger = Logger(subsystem: "com.local.focusfixer", category: "FocusEnforcer") private let preferences: Preferences init(preferences: Preferences = .shared) { self.preferences = preferences } /// Activates the owning app and raises/focuses its window, unless it is /// this app, already frontmost, or user-excluded. func enforceFocus(forWindowOwnedBy pid: pid_t) { guard pid != ProcessInfo.processInfo.processIdentifier else { return } guard let app = NSRunningApplication(processIdentifier: pid) else { logger.debug("No running application for pid \(pid, privacy: .public)") return } if let bundleID = app.bundleIdentifier, preferences.isExcluded(bundleID: bundleID) { return } guard !app.isActive else { return } if !app.activate(options: []) { logger.debug("Activation returned false for pid \(pid, privacy: .public)") } raiseFocusedWindow(pid: pid) } private func raiseFocusedWindow(pid: pid_t) { let axApp = AXUIElementCreateApplication(pid) var windowRef: CFTypeRef? let copyResult = AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &windowRef) guard copyResult == .success, let windowRef else { logger.debug("Could not resolve focused window via AX for pid \(pid, privacy: .public): \(copyResult.rawValue, privacy: .public)") return } // AXUIElementCopyAttributeValue guarantees an AXUIElement on success; the // Swift compiler can't express that constraint, hence the forced cast. let axWindow = windowRef as! AXUIElement let raiseResult = AXUIElementPerformAction(axWindow, kAXRaiseAction as CFString) if raiseResult != .success { logger.debug("Raise action failed for pid \(pid, privacy: .public): \(raiseResult.rawValue, privacy: .public)") } let focusResult = AXUIElementSetAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, axWindow) if focusResult != .success { logger.debug("Setting focused window failed for pid \(pid, privacy: .public): \(focusResult.rawValue, privacy: .public)") } } }