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,49 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user