How to implement global hotkeys in your Mac OS X app using RubyMotion Mar 23 2014

Many apps can make use of global hotkeys, often to perform a background action (such as taking a screenshot or starting and stopping something), or to hide and show the window of an app. To implement them in your own app, the easiest way is to use the fairly old Carbon APIs for registering and responding to hotkeys being pressed - the good news is that this is quite straightforward, especially if you use a wrapper library.

DDHotKey

There is a very useful, easy to use project called DDHotKey that provides Cocoa classes that wraps the Carbon API calls to register system-wide hotkey event handlers. It's not (at the time of writing) on Cocoapods, however we can still use it in our project fairly easily, with a simple modification. Grab the code from here, and put it in vendor/DDHotKey. You'll need to remove the main.m file in the root of the folder, as RubyMotion will get confused with a vendored lib that contains an executable artifact. Alternatively, I've forked the lib and made the change, so you can grab the already working code from here. Once you have added the lib to the vendor folder, you can easily configure it to build with your app in the Rakefile:

app.frameworks += ["Carbon"]
app.vendor_project 'vendor/DDHotKey', :static, :cflags => "-fobjc-arc"

We're ensuring we include the Carbon.framework in the build, we're building DDHotKey as a static lib, and we're also explicitly enabling ARC when compiling the lib.

Listening in

Then, setting up a hotkey is quite straightforward - first of all we grab a reference to the hotkey center object exposed by DDHotKey:

center = DDHotKeyCenter.sharedHotKeyCenter

Then we just need to pick a key, pick our modifiers, and setup the event handler to point to a method of our choosing:

center.registerHotKeyWithKeyCode(KVK_ANSI_M, modifierFlags: NSCommandKeyMask | NSAlternateKeyMask,
  target: self, action: 'handleHotkey:', object: nil)

A note on keys and modifiers

In the case above, KVK_ANSI_M is a constant provided by the Carbon APIs that refers to the keycode representing the M key. You can replace this with the constant for whichever key you'd like to trigger on. Likewise, in addition to NSCommandKeyMask and NSAlternateKeyMask, there is also NSCommandKeyMask, NSShiftKeyMask, and NSFunctionKeyMask which you can factor into your key combinations for hotkeys.

Respond accordingly

And finally we just have to define our event handler to do something:

def handleHotkey(event)
  puts "hotkey pressed!"
end

And that's all there is to it! You can define multiple hotkey handlers to do different things in your app, so you're not just restricted to one.

Example app

Here is a sample app that is the default RubyMotion OS X app template, with the default window hidden by default and the app running in the background, and with Cmd+Alt+H setup to hide and show the window. Overall, global hotkeys are great for putting your app at your users fingertips at any time, and it doesn't take a very long time to implement it!

Further reading

Ideally you want to allow your users to choose the key combo for certain actions, so they can change the defaults if they have conflicts between apps, or if they just prefer different combinations - and DDHotKey provides a customised NSTextField control that allows you to capture and store a combination for use as a hotkey. I'll cover how to roll that into a settings screen for your app, persisting the results in app configuration, in my forthcoming book, Building Mac OS X apps with RubyMotion. Be sure to sign up below to be notified when it's ready for an exclusive discount!

If there are any questions on this article, please let me know in the comments below, or on Twitter!

technicalrubymotioncodekickcode