Files
focus-fixer/FocusFixer/WindowLocator.swift
T
sttlab-tech 561a547b70 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>
2026-08-10 12:02:29 +02:00

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)
}
}