Reputation: 4465
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
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
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:
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