Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to listen to global hotkeys with Swift in an OS X app?

Tags:

swift

cocoa

I'm trying to have a handler in my Mac OS X app written in Swift for a global (system-wide) hotkey combo but I just cannot find proper documentation for it. I've read that I'd have to mess around in some legacy Carbon API for it, is there no better way? Can you show me some proof of concept Swift code? Thanks in advance!

like image 807
Csaba Okrona Avatar asked Feb 02 '15 15:02

Csaba Okrona


2 Answers

Since Swift 2.0, you can now pass a function pointer to C APIs.

var gMyHotKeyID = EventHotKeyID() gMyHotKeyID.signature = OSType("swat".fourCharCodeValue) gMyHotKeyID.id = UInt32(keyCode)  var eventType = EventTypeSpec() eventType.eventClass = OSType(kEventClassKeyboard) eventType.eventKind = OSType(kEventHotKeyPressed)  // Install handler. InstallEventHandler(GetApplicationEventTarget(), {(nextHanlder, theEvent, userData) -> OSStatus in     var hkCom = EventHotKeyID()     GetEventParameter(theEvent, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID), nil, sizeof(EventHotKeyID), nil, &hkCom)      // Check that hkCom in indeed your hotkey ID and handle it. }, 1, &eventType, nil, nil)  // Register hotkey. let status = RegisterEventHotKey(UInt32(keyCode), UInt32(modifierKeys), gMyHotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef) 
like image 67
Charlie Monroe Avatar answered Oct 12 '22 21:10

Charlie Monroe


I don't believe you can do this in 100% Swift today. You'll need to call InstallEventHandler() or CGEventTapCreate(), and both of those require a CFunctionPointer, which can't be created in Swift. Your best plan is to use established ObjC solutions such as DDHotKey and bridge to Swift.

You can try using NSEvent.addGlobalMonitorForEventsMatchingMask(handler:), but that only makes copies of events. You can't consume them. That means the hotkey will also be passed along to the currently active app, which can cause problems. Here's an example, but I recommend the ObjC approach; it's almost certainly going to work better.

let keycode = UInt16(kVK_ANSI_X) let keymask: NSEventModifierFlags = .CommandKeyMask | .AlternateKeyMask | .ControlKeyMask  func handler(event: NSEvent!) {     if event.keyCode == self.keycode &&         event.modifierFlags & self.keymask == self.keymask {             println("PRESSED")     } }  // ... to set it up ...     let options = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionaryRef     let trusted = AXIsProcessTrustedWithOptions(options)     if (trusted) {         NSEvent.addGlobalMonitorForEventsMatchingMask(.KeyDownMask, handler: self.handler)     } 

This also requires that accessibility services be approved for this app. It also doesn't capture events that are sent to your own application, so you have to either capture them with your responder chain, our use addLocalMointorForEventsMatchingMask(handler:) to add a local handler.

like image 23
Rob Napier Avatar answered Oct 12 '22 21:10

Rob Napier