Files
uskey/Sources/EventTapManager.swift
loveuer 99bbbbc73a
Some checks failed
Build and Release / Build DMG for arm64 (push) Has been cancelled
Build and Release / Build DMG for x86_64 (push) Has been cancelled
Build and Release / Create Release (push) Has been cancelled
Fix keyboard remapping in password fields and secure input contexts
Changed event handling to create and post new keyboard events instead of
modifying existing events, which resolves issues with password fields in
web browsers and other secure input contexts that ignore modified events.

🤖 Generated with [Qoder][https://qoder.com]
2025-12-03 19:07:25 +08:00

121 lines
3.8 KiB
Swift

@preconcurrency import Cocoa
@preconcurrency import CoreGraphics
@preconcurrency import ApplicationServices
class EventTapManager {
private var eventTap: CFMachPort?
private var runLoopSource: CFRunLoopSource?
private var isEnabled: Bool = false
var keyMapper: KeyMapper
init(keyMapper: KeyMapper) {
self.keyMapper = keyMapper
}
func start() -> Bool {
guard !isEnabled else { return true }
Logger.debug("Creating event tap...")
let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
let selfPtr = Unmanaged.passUnretained(self).toOpaque()
let callback: CGEventTapCallBack = { proxy, type, event, refcon in
guard type == .keyDown || type == .keyUp else {
return Unmanaged.passRetained(event)
}
guard let refcon = refcon else {
Logger.error("Event callback: refcon is nil")
return Unmanaged.passRetained(event)
}
let manager = Unmanaged<EventTapManager>.fromOpaque(refcon).takeUnretainedValue()
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
if manager.keyMapper.hasMappingFor(keyCode: keyCode) {
if let mappedKey = manager.keyMapper.getMappedKey(for: keyCode) {
Logger.debug("Remapping: \(keyCode) -> \(mappedKey)")
let newEvent = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(mappedKey), keyDown: type == .keyDown)
if let newEvent = newEvent {
newEvent.flags = event.flags
newEvent.post(tap: .cghidEventTap)
}
return nil
}
}
return Unmanaged.passRetained(event)
}
eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(eventMask),
callback: callback,
userInfo: selfPtr
)
guard let eventTap = eventTap else {
Logger.error("Failed to create event tap - check accessibility permissions")
return false
}
Logger.debug("Event tap created successfully")
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
guard let runLoopSource = runLoopSource else {
Logger.error("Failed to create run loop source")
return false
}
Logger.debug("Adding event tap to run loop...")
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
isEnabled = true
Logger.info("Event tap started")
return true
}
func stop() {
guard isEnabled else { return }
Logger.debug("Stopping event tap...")
if let eventTap = eventTap {
CGEvent.tapEnable(tap: eventTap, enable: false)
if let runLoopSource = runLoopSource {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
}
}
eventTap = nil
runLoopSource = nil
isEnabled = false
Logger.info("Event tap stopped")
}
func isRunning() -> Bool {
return isEnabled
}
func reload(config: Config) {
let wasEnabled = isEnabled
if wasEnabled {
stop()
}
keyMapper.loadFromConfig(config)
if wasEnabled {
_ = start()
}
}
}