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!
-
What exactly have you tried? What do you mean by hot keys? Have you tried NSEvent global monitor or CGEventTap – uchuugaka Apr 22 '15 at 13:58
7 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)

- 33,281
- 23
- 160
- 191

- 1,210
- 9
- 24
-
3Does not work in `Swift 4` and without a more detailed explanation it’s impossible to implement. – ixany Nov 02 '17 at 19:02
-
6Works well in Swift 4, I have converted my projects to Swift 4 and it works fine. As a developer, you should know that "doesn't work" is the worst possible description of an issue - what does "doesn't work" mean? It doesn't compile? What compile error are you getting? There's really not much to explain - each function used is documented by Apple. – Charlie Monroe Nov 03 '17 at 06:09
-
3Good vibes only, Charlie! It would be great if you could share your Swift 4 solution as well. The solution you posted is incomplete what makes it hard for beginners to adapt. One thing that would be helpful to say is that importing `Carbon` is necessary. The next problem I faced is that `String` does not have a `.fourCharCodeValue` property. Also it would be great if you could provide an example for `keyCode` and `modifierKeys`. – ixany Nov 03 '17 at 09:17
-
3@ixany - sorry if it sounded hostile - was not meant to be at all. `fourCharCodeValue` converts a string to `Int` - it was used a lot back in the day - 4 ASCII chars convert to 32-bit integer - it makes it easier to identify in dumps than a random integer. You can find my implementation here: https://github.com/charlieMonroe/XUCore/blob/master/XUCore/additions/StringExtensions.swift - I'm currently retrieving `keyCode` from `SRRecorderControl`: https://gist.github.com/charlieMonroe/0923985c704695b252ed9bb879e7f99d – Charlie Monroe Nov 06 '17 at 05:34
-
1I have done my homework and made this solution to work with Swift 5. See [here](https://stackoverflow.com/a/58225397/598057). – Stanislav Pankevich Oct 03 '19 at 19:05
The following code works for me for Swift 5.0.1. This solution is the combination of the solution from the accepted answer by Charlie Monroe and the recommendation by Rob Napier to use DDHotKey.
DDHotKey seems to work out of the box but it had one limitation that I had to change: the eventKind is hardcoded to kEventHotKeyReleased
while I needed both kEventHotKeyPressed
and kEventHotKeyReleased
event types.
eventSpec.eventKind = kEventHotKeyReleased;
If you want to handle both Pressed and Released events, just add a second InstallEventHandler
call which registers the other event kind.
This the complete example of the code that registers the "Command + R" key for the kEventHotKeyReleased
type.
import Carbon
extension String {
/// This converts string to UInt as a fourCharCode
public var fourCharCodeValue: Int {
var result: Int = 0
if let data = self.data(using: String.Encoding.macOSRoman) {
data.withUnsafeBytes({ (rawBytes) in
let bytes = rawBytes.bindMemory(to: UInt8.self)
for i in 0 ..< data.count {
result = result << 8 + Int(bytes[i])
}
})
}
return result
}
}
class HotkeySolution {
static
func getCarbonFlagsFromCocoaFlags(cocoaFlags: NSEvent.ModifierFlags) -> UInt32 {
let flags = cocoaFlags.rawValue
var newFlags: Int = 0
if ((flags & NSEvent.ModifierFlags.control.rawValue) > 0) {
newFlags |= controlKey
}
if ((flags & NSEvent.ModifierFlags.command.rawValue) > 0) {
newFlags |= cmdKey
}
if ((flags & NSEvent.ModifierFlags.shift.rawValue) > 0) {
newFlags |= shiftKey;
}
if ((flags & NSEvent.ModifierFlags.option.rawValue) > 0) {
newFlags |= optionKey
}
if ((flags & NSEvent.ModifierFlags.capsLock.rawValue) > 0) {
newFlags |= alphaLock
}
return UInt32(newFlags);
}
static func register() {
var hotKeyRef: EventHotKeyRef?
let modifierFlags: UInt32 =
getCarbonFlagsFromCocoaFlags(cocoaFlags: NSEvent.ModifierFlags.command)
let keyCode = kVK_ANSI_R
var gMyHotKeyID = EventHotKeyID()
gMyHotKeyID.id = UInt32(keyCode)
// Not sure what "swat" vs "htk1" do.
gMyHotKeyID.signature = OSType("swat".fourCharCodeValue)
// gMyHotKeyID.signature = OSType("htk1".fourCharCodeValue)
var eventType = EventTypeSpec()
eventType.eventClass = OSType(kEventClassKeyboard)
eventType.eventKind = OSType(kEventHotKeyReleased)
// Install handler.
InstallEventHandler(GetApplicationEventTarget(), {
(nextHanlder, theEvent, userData) -> OSStatus in
// var hkCom = EventHotKeyID()
// GetEventParameter(theEvent,
// EventParamName(kEventParamDirectObject),
// EventParamType(typeEventHotKeyID),
// nil,
// MemoryLayout<EventHotKeyID>.size,
// nil,
// &hkCom)
NSLog("Command + R Released!")
return noErr
/// Check that hkCom in indeed your hotkey ID and handle it.
}, 1, &eventType, nil, nil)
// Register hotkey.
let status = RegisterEventHotKey(UInt32(keyCode),
modifierFlags,
gMyHotKeyID,
GetApplicationEventTarget(),
0,
&hotKeyRef)
assert(status == noErr)
}
}

- 11,044
- 8
- 69
- 129
-
1This still works great with Swift 5.7 unlike other solutions like https://github.com/soffes/HotKey. Unfortunately, there is a complete black hole when it comes to official documentation on this matter (global hotkeys). Thank you very much! – AlexeyGy Dec 25 '22 at 22:20
-
-
Awesome solution! @snake302 you can simply register it in `applicationDidFinishLaunching` for instance. `HotkeySolution.register()` – Nash Equilibrium May 03 '23 at 02:33
-
how to make this work for cmd + tab, I have tested this for different characters its working – jan_kiran Jun 16 '23 at 11:31
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.

- 286,113
- 34
- 456
- 610
-
While it's usable, it is important to remember that the user is granting your app the ability to monitor every single key stroke, which is potentially dangerous, mainly if your app is not sandboxed and can be injected. The old Carbon API will do this just for the user-defined combinations, making them a "safer" solution. – Charlie Monroe Nov 03 '17 at 06:11
-
`InstallEventHandler()` works very well in Swift (at least since Swift 3). I'm using a Swift version of `DDHotKeyCenter` – vadian Jul 06 '19 at 16:41
A quick Swift 3 update for the setup:
let opts = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionary
guard AXIsProcessTrustedWithOptions(opts) == true else { return }
NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: self.handler)

- 33,281
- 23
- 160
- 191

- 1,082
- 14
- 24
-
Note: The Carbon-based Hotkey API does not need access privileges (can't be on the Mac App Store), although this sure is simpler to implement. – ctietze Feb 18 '17 at 08:16
-
There are apps like CopyLess 2 that listen for keyDown and don't require accessibility, do you know something about them? Maybe the apps from store don't require it? – Cristi Băluță Mar 03 '17 at 12:46
I maintain this Swift package that makes it easy to both add global keyboard shortcuts to your app and also let the user set their own.
import SwiftUI
import KeyboardShortcuts
// Declare the shortcut for strongly-typed access.
extension KeyboardShortcuts.Name {
static let toggleUnicornMode = Self("toggleUnicornMode")
}
@main
struct YourApp: App {
@StateObject private var appState = AppState()
var body: some Scene {
WindowGroup {
// …
}
Settings {
SettingsScreen()
}
}
}
@MainActor
final class AppState: ObservableObject {
init() {
// Register the listener.
KeyboardShortcuts.onKeyUp(for: .toggleUnicornMode) { [self] in
isUnicornMode.toggle()
}
}
}
// Present a view where the user can set the shortcut they want.
struct SettingsScreen: View {
var body: some View {
Form {
HStack(alignment: .firstTextBaseline) {
Text("Toggle Unicorn Mode:")
KeyboardShortcuts.Recorder(for: .toggleUnicornMode)
}
}
}
}
SwiftUI is used in this example, but it also supports Cocoa.

- 62,972
- 39
- 168
- 232
Take a look at the HotKey Library. You can simply use Carthage to implement it into your own app. HotKey Library

- 821
- 9
- 22

- 4,378
- 3
- 23
- 42
there is a pretty hacky, but also pretty simple workaround if your app has a Menu
:
- add a new
MenuItem
(maybe call it something like "Dummy for Hotkey") - in the attributes inspector, conveniently enter your hotkey in the
Key Equivalent
field - set
Allowed when Hidden
,Enabled
andHidden
to true - link it with an IBAction to do whatever your hotkey is supposed to do
done!

- 1,234
- 12
- 15
-
Interesting approach, but it does not work for apps that live only in the menu bar: for the keyboard shortcut to be detected, the app must be activated somehow, for instance by clicking on it in the menu bar. – cdf1982 Nov 25 '20 at 14:53