561a547b70
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>
50 lines
2.0 KiB
Swift
50 lines
2.0 KiB
Swift
import CoreGraphics
|
|
import os
|
|
|
|
/// A single on-screen window as reported by the window server, reduced to
|
|
/// the fields FocusFixer needs. Kept separate from the raw CGWindowList
|
|
/// dictionary so the resolution logic below can be unit tested with
|
|
/// synthetic data instead of the real window server.
|
|
struct WindowInfo: Equatable {
|
|
let ownerPID: pid_t
|
|
let layer: Int
|
|
let bounds: CGRect
|
|
}
|
|
|
|
enum WindowLocator {
|
|
private static let logger = Logger(subsystem: "com.local.focusfixer", category: "WindowLocator")
|
|
|
|
/// Resolves the owning process ID of the frontmost on-screen window
|
|
/// under `point`, using the live window server state.
|
|
static func ownerPID(at point: CGPoint) -> pid_t? {
|
|
guard let list = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as? [[String: Any]] else {
|
|
logger.error("CGWindowListCopyWindowInfo returned nil")
|
|
return nil
|
|
}
|
|
return ownerPID(at: point, in: list.compactMap(parse))
|
|
}
|
|
|
|
/// Pure resolution logic: `windows` is assumed to be in front-to-back
|
|
/// z-order, matching CGWindowListCopyWindowInfo's contract. Only
|
|
/// layer-0 windows (ordinary app windows, excluding menu bar, Dock,
|
|
/// Spotlight, and Control Center) are considered.
|
|
static func ownerPID(at point: CGPoint, in windows: [WindowInfo]) -> pid_t? {
|
|
for window in windows where window.layer == 0 && window.bounds.contains(point) {
|
|
return window.ownerPID
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private static func parse(_ dict: [String: Any]) -> WindowInfo? {
|
|
guard
|
|
let pid = dict[kCGWindowOwnerPID as String] as? pid_t,
|
|
let layer = dict[kCGWindowLayer as String] as? Int,
|
|
let boundsDict = dict[kCGWindowBounds as String] as? [String: Any],
|
|
let bounds = CGRect(dictionaryRepresentation: boundsDict as CFDictionary)
|
|
else {
|
|
return nil
|
|
}
|
|
return WindowInfo(ownerPID: pid, layer: layer, bounds: bounds)
|
|
}
|
|
}
|