Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

252 lines
7.4 KiB
Swift

import ApplicationServices
import Carbon.HIToolbox
import Foundation
import MacToolsPluginKit
final class WindowSwitcherShortcutTap: @unchecked Sendable {
var onShortcutPressed: @MainActor (_ reversed: Bool, _ isRepeat: Bool) -> Void = { _, _ in }
var onShortcutReleased: @MainActor () -> Void = {}
var onEscape: @MainActor () -> Void = {}
private let lock = NSLock()
private let userDefaults: UserDefaults
private var currentBinding: ShortcutBinding?
private var activeModifiers: ShortcutModifiers?
private var tap: CFMachPort?
private var runLoopSource: CFRunLoopSource?
private var defaultsObserver: NSObjectProtocol?
init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
self.currentBinding = WindowSwitcherShortcutBindingStore.resolvedBinding(userDefaults: userDefaults)
}
var isRunning: Bool {
lock.withLock { tap != nil }
}
func start() {
lock.lock()
let alreadyRunning = tap != nil
lock.unlock()
guard !alreadyRunning else {
reloadBinding()
return
}
reloadBinding()
installDefaultsObserver()
let mask = CGEventMask(1 << CGEventType.keyDown.rawValue)
| CGEventMask(1 << CGEventType.flagsChanged.rawValue)
guard let newTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: mask,
callback: Self.eventCallback,
userInfo: Unmanaged.passUnretained(self).toOpaque()
) else {
return
}
let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, newTap, 0)
CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes)
CGEvent.tapEnable(tap: newTap, enable: true)
lock.lock()
tap = newTap
runLoopSource = source
lock.unlock()
}
func stop() {
let state = lock.withLock { () -> (CFMachPort?, CFRunLoopSource?, NSObjectProtocol?) in
let state = (tap, runLoopSource, defaultsObserver)
tap = nil
runLoopSource = nil
defaultsObserver = nil
activeModifiers = nil
return state
}
if let tap = state.0 {
CGEvent.tapEnable(tap: tap, enable: false)
}
if let source = state.1 {
CFRunLoopRemoveSource(CFRunLoopGetMain(), source, .commonModes)
}
if let observer = state.2 {
NotificationCenter.default.removeObserver(observer)
}
}
func reloadBinding() {
let binding = WindowSwitcherShortcutBindingStore.resolvedBinding(userDefaults: userDefaults)
lock.withLock {
currentBinding = binding
}
}
private func installDefaultsObserver() {
lock.lock()
let hasObserver = defaultsObserver != nil
lock.unlock()
guard !hasObserver else {
return
}
let observer = NotificationCenter.default.addObserver(
forName: UserDefaults.didChangeNotification,
object: userDefaults,
queue: nil
) { [weak self] _ in
self?.reloadBinding()
}
lock.withLock {
defaultsObserver = observer
}
}
private func bindingSnapshot() -> ShortcutBinding? {
lock.withLock { currentBinding }
}
private func activeModifiersSnapshot() -> ShortcutModifiers? {
lock.withLock { activeModifiers }
}
private func setActiveModifiers(_ modifiers: ShortcutModifiers?) {
lock.withLock {
activeModifiers = modifiers
}
}
private func handle(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput {
if let tap = lock.withLock({ tap }) {
CGEvent.tapEnable(tap: tap, enable: true)
}
return Unmanaged.passUnretained(event)
}
guard AXIsProcessTrusted() else {
setActiveModifiers(nil)
return Unmanaged.passUnretained(event)
}
switch type {
case .keyDown:
return handleKeyDown(event)
case .flagsChanged:
return handleFlagsChanged(event)
default:
return Unmanaged.passUnretained(event)
}
}
private func handleKeyDown(_ event: CGEvent) -> Unmanaged<CGEvent>? {
let keyCode = UInt16(event.getIntegerValueField(.keyboardEventKeycode))
if keyCode == UInt16(kVK_Escape), activeModifiersSnapshot() != nil {
setActiveModifiers(nil)
Task { @MainActor in
self.onEscape()
}
return nil
}
guard let binding = bindingSnapshot(),
keyCode == binding.keyCode,
binding.matches(eventFlags: event.flags, allowingExtraShift: true)
else {
return Unmanaged.passUnretained(event)
}
setActiveModifiers(binding.modifiers)
let isRepeat = event.getIntegerValueField(.keyboardEventAutorepeat) != 0
let reversed = !binding.modifiers.contains(.shift) && event.flags.contains(.maskShift)
Task { @MainActor in
self.onShortcutPressed(reversed, isRepeat)
}
return nil
}
private func handleFlagsChanged(_ event: CGEvent) -> Unmanaged<CGEvent>? {
guard let activeModifiers = activeModifiersSnapshot(),
!activeModifiers.isHeld(in: event.flags)
else {
return Unmanaged.passUnretained(event)
}
setActiveModifiers(nil)
Task { @MainActor in
self.onShortcutReleased()
}
return Unmanaged.passUnretained(event)
}
private nonisolated static let eventCallback: CGEventTapCallBack = { _, type, event, userInfo in
guard let userInfo else {
return nil
}
let tap = Unmanaged<WindowSwitcherShortcutTap>.fromOpaque(userInfo).takeUnretainedValue()
return tap.handle(type: type, event: event)
}
}
private extension ShortcutBinding {
func matches(eventFlags: CGEventFlags, allowingExtraShift: Bool) -> Bool {
let actual = eventFlags.significantShortcutFlags
let required = modifiers.cgEventFlags
if allowingExtraShift, !required.contains(.maskShift) {
return actual == required || actual == required.union(.maskShift)
}
return actual == required
}
}
private extension ShortcutModifiers {
var cgEventFlags: CGEventFlags {
var flags: CGEventFlags = []
if contains(.command) {
flags.insert(.maskCommand)
}
if contains(.control) {
flags.insert(.maskControl)
}
if contains(.option) {
flags.insert(.maskAlternate)
}
if contains(.shift) {
flags.insert(.maskShift)
}
return flags
}
func isHeld(in eventFlags: CGEventFlags) -> Bool {
let actual = eventFlags.significantShortcutFlags
return actual.intersection(cgEventFlags) == cgEventFlags
}
}
private extension CGEventFlags {
var significantShortcutFlags: CGEventFlags {
intersection([.maskCommand, .maskControl, .maskAlternate, .maskShift])
}
}
private extension NSLock {
func withLock<T>(_ body: () -> T) -> T {
lock()
defer { unlock() }
return body()
}
}