CGEventTapCreate breaks down mysteriously with "key down" events
I'm using CGEventTapCreate
to "steal" media keys from iTunes when my app is running. The code inside of the callback that I pass to CGEv开发者_Go百科entTapCreate
examines the event, and if it finds that it's one of the media keys, posts an appropriate notification to the default notification center.
Now, this works fine if I post a notification for the "key up" event. If I do that for "key down" events, eventually my app stops getting media key events and iTunes takes over. Any ideas on what can be causing this? The relevant part of the code is below
enum {
...
PlayPauseKeyDown = 0x100A00,
PlayPauseKeyUp = 0x100B00,
...
};
static CGEventRef event_tap_callback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
if (!(type == NX_SYSDEFINED) || (type == NX_KEYUP) || (type == NX_KEYDOWN))
return event;
NSEvent* keyEvent = [NSEvent eventWithCGEvent: event];
if (keyEvent.type != NSSystemDefined) return event;
switch(keyEvent.data1)
{
case PlayPauseKeyUp: // <--- this works reliably
//case PlayPauseKeyDown: // <--- this will break eventually
post_notification(@"PlayPauseMediaKeyPressed", nil, nil);
return NULL;
... and so on ...
Does something kill my event tap if the callback takes too long?
Some people suspect that Snow Leopard has a bug that sometimes disables event taps even if they don't take too long. To handle that, you can watch for the event type kCGEventTapDisabledByTimeout
, and respond by re-enabling your tap with CGEventTapEnable
.
First of all, why is your first "if" allowing key-down and key-up events to pass? Your second "if" only lets system events pass through anyway. So for all key-down/-up events you create a NSEvent, just to drop the event one line further downwards. That makes little sense. An Event Tap should always be as fast as possible, otherwise it will slow down all event processing of the whole system. Your callback should not even be called for key-down/-up events, since system events are not key-down/-up events, they are system events. If they were key events, you would for sure never access data1, but instead use the "type" and "keyCode" methods to get the relevant information from them.
static CGEventRef event_tap_callback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
NSEvent * sysEvent;
// No event we care for? return ASAP
if (type != NX_SYSDEFINED) return event;
sysEvent = [NSEvent eventWithCGEvent:event];
// No need to test event type, we know it is NSSystemDefined,
// becuase that is the same as NX_SYSDEFINED
Also you cannot determine if that is the right kind of event by just looking at the data, you must also verify the subtype, that must be 8 for this kind of event:
if ([sysEvent subtype] != 8) return event;
The next logical step is to split the data up into its components:
int data = [sysEvent data1];
int keyCode = (data & 0xFFFF0000) >> 16;
int keyFlags = (data & 0xFFFF);
int keyState = (keyFlags & 0xFF00) >> 8;
BOOL keyIsRepeat = (keyFlags & 0x1) > 0;
And you probably don't care for repeating key events (that is when I keep the key pressed and it keeps sending the same event over and over again).
// You probably won't care for repeating events
if (keyIsRepeat) return event;
Finally you should not define any own constant, the system has ready to use constants for those keys:
// Analyze the key
switch (keyCode) {
case NX_KEYTYPE_PLAY:
// Play/Pause key
if (keyState == 0x0A) {
// Key down
// ...do your stuff here...
return NULL;
} else if (keyState == 0x0B) {
// Key Up
// ...do your stuff here...
return NULL;
}
// If neither down nor up, we don't know
// what it is and better ignore it
break;
case NX_KEYTYPE_FAST:
// (Fast) Forward
break;
case NX_KEYTYPE_REWIND:
// Rewind key
break;
}
// If we get here, we have not handled
// the event and want system to handle it
return event;
}
And if this still not works, my next question would be what your post_notification function looks like and do you also see the described problem if you don't call post_notification there, but just make a NSLog call about the event you just saw?
In your handler, check for the following type, and just re-enable the listener.
if (type == kCGEventTapDisabledByTimeout) {
NSLog(@"Event Taps Disabled! Re-enabling");
CGEventTapEnable(eventTap, true);
return event;
}
精彩评论