Arc676
Arc676

Reputation: 4465

Swift 2: Detect Function Key Presses

I'm writing a program in Swift 2 in Xcode 7 (for OS X 10.11) and I want it (this is the ViewController class) to be able to detect when the user presses a function key, especially when the app is in the background. I overrode acceptsFirstResponder:

override var acceptsFirstResponder: Bool { return true }

and I have implemented keyUp: and keyDown::

override func keyDown(theEvent: NSEvent) {
    print("keyDown:\n")
    let ch: NSString = theEvent.charactersIgnoringModifiers!
    if ch.length == 1 {
        let char: Int = Int(ch.characterAtIndex(0))
        if char == NSF7FunctionKey {
            prevSong(NSNull())
        }else if char == NSF8FunctionKey {
            playPause(NSNull())
        }else if char == NSF9FunctionKey {
            nextSong(NSNull())
        }else{
            self.nextResponder?.keyDown(theEvent)
        }
    }else{
        self.nextResponder?.keyDown(theEvent)
    }
}

override func keyUp(theEvent: NSEvent) {
    print("keyUp:\n")
    self.nextResponder?.keyUp(theEvent)
}

I originally had all the code in keyUp: instead but I never get "keyDown:" printed to the console. On a side note, if anyone knows a better way to detect function keys please say so.

Why doesn't it detect key presses?

Upvotes: 2

Views: 2674

Answers (2)

Arc676
Arc676

Reputation: 4465

Written by @Serneum (Swift or Swift 2) here

Also available in Objective-C by @Joshua Nozzi here

Note that when pressing F8 iTunes still launches even if your application uses the media keys. Read this post (using 3rd party app) or this post (using Terminal) (same question, different answers) if you want to disable the iTunes-launching.


You can capture media keys by subclassing NSApplication (in the AppKit framework for those unaware) and overriding sendEvent:NSEvent.

Swift code (taken from Serneum's answer):

import Cocoa

class Application: NSApplication {
    override func sendEvent(theEvent: NSEvent) {
        if theEvent.type == .SystemDefined && theEvent.subtype.rawValue == 8 {
            let keyCode = ((theEvent.data1 & 0xFFFF0000) >> 16)
            let keyFlags = (theEvent.data1 & 0x0000FFFF)
            // Get the key state. 0xA is KeyDown, OxB is KeyUp
            let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA
            let keyRepeat = (keyFlags & 0x1)
            mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(keyRepeat))
        }
        super.sendEvent(theEvent)
        //note that without the above line the app doesn't respond to clicks or any keyboard events
    }

    func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) {
        // Only send events on KeyDown. Without this check, these events will happen twice
        if (state) {
            switch(key) {
            case NX_KEYTYPE_PLAY:
                //F8 pressed
                break
            case NX_KEYTYPE_FAST:
                //F9 pressed
                break
            case NX_KEYTYPE_REWIND:
                //F7 pressed
                break
            default:
                break
            }
        }
    }
}

Other "hardware keys" include NX_KEYTYPE_BRIGHTNESS_DOWN, NX_KEYTYPE_BRIGHTNESS_UP, NX_KEYTYPE_CAPS_LOCK, NX_KEYTYPE_EJECT, NX_KEYTYPE_ILLUMINATION_UP, NX_KEYTYPE_ILLUMINATION_DOWN, NX_KEYTYPE_LAUNCH_PANEL, NX_KEYTYPE_MUTE, NX_KEYTYPE_SOUND_DOWN, NX_KEYTYPE_SOUND_UP, and some other ones that aren't on my keyboard so I can't confirm them.

Upvotes: 2

Ken Thomases
Ken Thomases

Reputation: 90601

It looks like you are trying to capture the media keys (Play/Pause, Forward, Back). These are not actually function keys, they are "hardware" keys. (If you hold down fn, then they are function keys. If you go into System Preferences > Keyboard pane > Keyboard tab and enable "Use all F1, F2, etc. keys as standard function keys", then they are function keys and holding down fn switches them to hardware keys.)

There are answers elsewhere about capturing those media keys, such as here and here. They both recommend using the SPMediaKeyTap code.

In case you're really interested in the function keys, some things you need to know:

  • Apps don't normally receive keyboard events when they are in the background. Keyboard events only go to the active app.
  • You can use a global event monitor or Quartz event tap to monitor key events from the background, but only if your app has been approved by the user for access to assistive devices. This is to prevent keyloggers and other malicious software.
  • You can use RegisterEventHotKey() to achieve a more limited way of responding to key events from the background without assistive access. It's a Carbon API but is not deprecated because it's still the right way to achieve hot key functionality. Still doesn't help for hardware keys like the media keys.

Upvotes: 2

Related Questions