// eventtap.swift - CGEventTap monitor: what the macOS event system MADE of the HID reports. // Logs keyDown/keyUp/flagsChanged with keycode and modifier flags (Shift explicitly called out). // Requires Accessibility (and on newer macOS also Input Monitoring) for the hosting terminal app. import AppKit import CoreGraphics import Foundation let mask: CGEventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << CGEventType.flagsChanged.rawValue) let callback: CGEventTapCallBack = { _, type, event, _ in let keycode = event.getIntegerValueField(.keyboardEventKeycode) let flags = event.flags let shift = flags.contains(.maskShift) ? "SHIFT=1" : "SHIFT=0" let fmt = ISO8601DateFormatter() fmt.formatOptions = [.withInternetDateTime, .withFractionalSeconds] // kCGKeyboardEventKeyboardType + source pid help attribute events to a specific keyboard let kbType = event.getIntegerValueField(.keyboardEventKeyboardType) print("\(fmt.string(from: Date())) | \(type == .keyDown ? "keyDown" : type == .keyUp ? "keyUp" : "flagsChanged") | keycode=\(keycode) | \(shift) | flags=0x\(String(flags.rawValue, radix: 16)) | kbType=\(kbType)") fflush(stdout) return Unmanaged.passUnretained(event) // listen-only; pass event through unchanged } guard let tap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .listenOnly, eventsOfInterest: mask, callback: callback, userInfo: nil) else { FileHandle.standardError.write(""" CGEvent.tapCreate returned nil - Accessibility permission missing. Grant it: System Settings > Privacy & Security > Accessibility > enable your terminal app, then fully quit and relaunch the terminal. (AXIsProcessTrusted()=\(AXIsProcessTrusted())) """.data(using: .utf8)!) exit(1) } let src = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), src, .commonModes) CGEvent.tapEnable(tap: tap, enable: true) FileHandle.standardError.write("eventtap: tap active, type on any keyboard (Ctrl-C to stop)...\n".data(using: .utf8)!) CFRunLoopRun()