Skip to content

Instantly share code, notes, and snippets.

@PerArneng
Last active March 30, 2025 15:33
Show Gist options
  • Save PerArneng/910e2c350515c0001df5feb9dedb487d to your computer and use it in GitHub Desktop.
Save PerArneng/910e2c350515c0001df5feb9dedb487d to your computer and use it in GitHub Desktop.
This gist demonstrates a Swift script that sets up a global event tap on macOS to capture mouse button presses. It logs which mouse button is pressed, along with the frontmost application’s bundle identifier and window title, requiring appropriate Accessibility permissions granted in System Settings → Privacy & Security → Accessibility.
#!/usr/bin/env swift
import Cocoa
import CoreGraphics
// We store the event tap reference globally so we can re-enable it if it's disabled:
var eventTapCFMachPort: CFMachPort?
// MARK: - Helper to Get Frontmost App Info
func frontmostAppInfo() -> (bundleID: String?, windowTitle: String?) {
guard let frontApp = NSWorkspace.shared.frontmostApplication else {
return (nil, nil)
}
let bundleID = frontApp.bundleIdentifier
let pid = frontApp.processIdentifier // pid_t (non-optional)
// Use Accessibility API to get the frontmost window title
let axApp = AXUIElementCreateApplication(pid)
var focusedWindow: AnyObject?
let result = AXUIElementCopyAttributeValue(
axApp,
kAXFocusedWindowAttribute as CFString,
&focusedWindow
)
guard result == .success, let windowRef = focusedWindow else {
return (bundleID, nil)
}
var titleCF: CFTypeRef?
let titleResult = AXUIElementCopyAttributeValue(
windowRef as! AXUIElement,
kAXTitleAttribute as CFString,
&titleCF
)
var windowTitle: String?
if titleResult == .success, let titleStr = titleCF as? String {
windowTitle = titleStr
}
return (bundleID, windowTitle)
}
// MARK: - Event Tap Callback
let eventTapCallback: CGEventTapCallBack = { proxy, type, event, refcon in
// If the tap was disabled by user input or timeout, re-enable it
if type == .tapDisabledByUserInput || type == .tapDisabledByTimeout {
if let tapPort = eventTapCFMachPort {
CGEvent.tapEnable(tap: tapPort, enable: true)
}
return Unmanaged.passUnretained(event)
}
// Check for mouse down events: left, right, or "other"
if [.leftMouseDown, .rightMouseDown, .otherMouseDown].contains(type) {
let buttonNumber = event.getIntegerValueField(.mouseEventButtonNumber)
let (bundleID, windowTitle) = frontmostAppInfo()
let appInfo = "BundleID=\(bundleID ?? "Unknown"), WindowTitle=\(windowTitle ?? "Unknown")"
print("Mouse button pressed: \(buttonNumber) | \(appInfo)")
}
return Unmanaged.passUnretained(event)
}
// MARK: - Create Event Tap Using Modern API
let eventMask = (1 << CGEventType.leftMouseDown.rawValue)
| (1 << CGEventType.rightMouseDown.rawValue)
| (1 << CGEventType.otherMouseDown.rawValue)
// Use the newer 'tapCreate' API:
guard let eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(eventMask),
callback: eventTapCallback,
userInfo: nil
) else {
fputs("Failed to create event tap.\n", stderr)
exit(1)
}
// Store the tap port globally (so we can re-enable it in the callback)
eventTapCFMachPort = eventTap
// Create a run loop source from the tap, add to the current run loop, enable it
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
print("Listening for mouse button presses... (Press Ctrl+C to stop)")
RunLoop.current.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment