Skip to content

Instantly share code, notes, and snippets.

@jmg-duarte
Created December 31, 2024 15:52
Show Gist options
  • Save jmg-duarte/1d3eece4c80c3fca8dd16df90543e839 to your computer and use it in GitHub Desktop.
Save jmg-duarte/1d3eece4c80c3fca8dd16df90543e839 to your computer and use it in GitHub Desktop.
Function to setup global shortcuts in MacOS apps - somewhat incomplete, written before moving to HotKeys
/// Sets up the global shortcuts (currently, just Cmd+Shift+ç)
///
/// References:
/// https://developer.apple.com/forums/thread/707680?answerId=716892022#716892022
/// https://developer.apple.com/documentation/coregraphics/cgevent/1454426-tapcreate
/// https://stackoverflow.com/a/55617464/28296770
func setupGlobalShortcuts() -> CFMachPort? {
// TODO: store in UserSettings if it is the first time the user is opening the app
// if it is, use CGRequestListenEventAccess, otherwise, just check with Preflight
let keyDownEventMask = CGEventMask(1 << CGEventType.keyDown.rawValue)
let eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .listenOnly,
eventsOfInterest: keyDownEventMask,
callback: {
_, type, event, refcon in
// If the userInfo (i.e. self) was not passed, don't even try to handle the event
guard let refcon else { return Unmanaged.passUnretained(event)}
// If the event is not a key-down event, don't handle it
guard type == .keyDown else { return Unmanaged.passUnretained(event) }
// Convert the raw flags into NSEvent.ModifierFlags
// and intersect with deviceIndependentFlagsMask to extract things like Cmd+Shift
let modifierFlags = NSEvent.ModifierFlags(rawValue: UInt(event.flags.rawValue)).intersection(NSEvent.ModifierFlags.deviceIndependentFlagsMask)
// Get the event keycode (finding out the correct keycode is pure chaos)
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
// 41 is the magic keycode for ç
if keyCode == 41 && modifierFlags == [.shift, .command] {
// takeUnretained doesn't lower the refcount here, which is practical
// since we don't want to be responsible for releasing the object later
let appDelegate = Unmanaged<AppDelegate>.fromOpaque(refcon).takeUnretainedValue()
appDelegate.showWindowIfPossible()
}
return nil
},
// passRetained "increases" the refcount of self, keeping it alive
userInfo: Unmanaged<AppDelegate>.passRetained(self).toOpaque()
)
if let eventTap {
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
} else {
// Show a modal explaining to the user that we need the permissions
let alert = NSAlert()
alert.messageText = "Application requires Accessibility permissions to setup global shortcuts"
alert.alertStyle = .warning
alert.runModal()
// If the user restarted the app, this doesn't run
// if the user didn't restart the app, this runs and the shortcut is not set
return nil
}
return eventTap
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment