Created
December 31, 2024 15:52
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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