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,54 @@
|
||||
import Foundation
|
||||
|
||||
/// UserDefaults-backed settings for FocusFixer.
|
||||
final class Preferences {
|
||||
static let shared = Preferences()
|
||||
|
||||
private enum Key {
|
||||
static let enabled = "enabled"
|
||||
static let excludedBundleIDs = "excludedBundleIDs"
|
||||
static let debounceIntervalMS = "debounceIntervalMS"
|
||||
}
|
||||
|
||||
private static let defaultDebounceIntervalMS = 150
|
||||
|
||||
private let defaults: UserDefaults
|
||||
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
defaults.register(defaults: [
|
||||
Key.enabled: true,
|
||||
Key.excludedBundleIDs: [String](),
|
||||
Key.debounceIntervalMS: Self.defaultDebounceIntervalMS
|
||||
])
|
||||
}
|
||||
|
||||
var isEnabled: Bool {
|
||||
get { defaults.bool(forKey: Key.enabled) }
|
||||
set { defaults.set(newValue, forKey: Key.enabled) }
|
||||
}
|
||||
|
||||
var excludedBundleIDs: Set<String> {
|
||||
get { Set(defaults.stringArray(forKey: Key.excludedBundleIDs) ?? []) }
|
||||
set { defaults.set(Array(newValue), forKey: Key.excludedBundleIDs) }
|
||||
}
|
||||
|
||||
var debounceInterval: TimeInterval {
|
||||
get { TimeInterval(defaults.integer(forKey: Key.debounceIntervalMS)) / 1000.0 }
|
||||
set { defaults.set(Int(newValue * 1000), forKey: Key.debounceIntervalMS) }
|
||||
}
|
||||
|
||||
func isExcluded(bundleID: String) -> Bool {
|
||||
excludedBundleIDs.contains(bundleID)
|
||||
}
|
||||
|
||||
func setExcluded(_ excluded: Bool, bundleID: String) {
|
||||
var ids = excludedBundleIDs
|
||||
if excluded {
|
||||
ids.insert(bundleID)
|
||||
} else {
|
||||
ids.remove(bundleID)
|
||||
}
|
||||
excludedBundleIDs = ids
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user