views:

841

answers:

2

I'm using some Carbon code in my Cocoa project for handling global key events (shortcuts) from other applications. Currently I have setup a kEventHotKeyReleased event handler and I can successfully obtain hot keys when my application is not active. That triggers some operation in my application.

The problem I have with the behavior of kEventHotKeyReleased is:

Say for example I press the Cmd-Shift-P key combination. As soon as I release the "P" key the hot key event is triggered. I need to be able to trigger the event (or manually trigger it) when all of the keys are unpressed (i.e: the Cmd and Shift keys are released too).

It is easy to monitor for hot keys but I have seen nothing for monitoring individual keystrokes. If I could monitor the modifier key states I would be in business.

Any hints on how to do this?

Thanks in advance!


UPDATE:

I've tried using kEventRawKeyUp and kEventRawKeyModifiersChanged but while kEventHotKeyReleased works those two don't even though I set them up in the exact same way as kEventHotKeyReleased.

EventTypeSpec eventTypes[] = {{kEventClassKeyboard, kEventHotKeyReleased}, {kEventClassKeyboard, kEventRawKeyUp}};
// Changing the order in the list does not help, nor does removing kEventHotKeyReleased
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);
// err == noErr after this line

The globalHotKeyHandler method is called for kEventHotKeyReleased, but not for kEventRawKeyUp for some reason I can't seem to grasp. Here's what my globalHotKeyHandler method looks like:

OSStatus globalHotkeyHandler(EventHandlerCallRef nextHandler, EventRef anEvent, void *userData) {
    NSLog(@"Something happened!");
}

Is there an additional call that needs to be made or something else I forgot?

N.B: At first glance, it seems like it could be that Access for Assistive Devices is disabled but it is not. So I'm pretty clueless.


UPDATE 2:

I investigated a bit on the CGEventTap Leibowitzn suggested and I came up with this setup:

CFMachPortRef keyUpEventTap = CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,kCGEventKeyUp,&keyUpCallback,NULL);
CFRunLoopSourceRef keyUpRunLoopSourceRef = CFMachPortCreateRunLoopSource(NULL, keyUpEventTap, 0);
CFRelease(keyUpEventTap);
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyUpRunLoopSourceRef, kCFRunLoopDefaultMode);
CFRelease(keyUpRunLoopSourceRef);

... and the callback:

CGEventRef keyUpCallback (CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
    NSLog(@"KeyUp event tapped!");
    return event;
}

As you can see I'm using kCGEventKeyUp as the mask for the event tap but somehow I'm receiving mouse down events ??!??


UPDATE 3:

Ok forget that, I overlooked the line in the doc that said to use CGEventMaskBit(kCGEventKeyUp) for this parameter, so the correct call is:

CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventKeyUp),&keyUpCallback,NULL);

I'm still having a problem though: modifier keys do not trigger the kCGEventKeyUp...


UPDATE 4:

Ok forget that again... I'm bound to answer to my own questions 5 minutes after asking them today huh!

To intercept modifier keys, use kCGEventFlagsChanged:

CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventFlagsChanged),&callbackFunction,NULL);

So in essence I got the key and modifier key state detection working, but I'm still interested in knowing why kEventRawKeyUp doesn't work...


N.B: Also note that I'm developing on Tiger with the goal of having support for new and older versions of the OS as much as possible. CGEventTap is 10.4+ only so I'll be using this for now but a backwards-compatible solution would be welcome.

+2  A: 

One option is to use EventTaps. This lets you monitor all keyboard events. See: http://developer.apple.com/mac/library/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple%5Fref/c/func/CGEventTapCreate

Unfortunately event taps will stop working if an application is requesting secure input. For example Quicken.

Leibowitzn
Leibowitzn
Ahh, they're just registering for the following carbon event: { kEventClassKeyboard, kEventRawKeyModifiersChanged }
Leibowitzn
I've noticed kEventRawKeyModifiersChanged and kEventRawKeyUp but I can't seem to make them work for the life of me. See the update in my question. (thanks for your help btw!)
Form
I'm not sure what the problem could be. Could the hot key be interfering? What happens if you disable the hot key?Also, try downloading Google's Quick Search Box. The code is very good.
Leibowitzn
Like I wrote in the code comments kEventRawKeyUp does not work either if I remove the hot key event type. The order of the event types do not change a thing, it looks like kEventRawKeyUp is just ignored. Why wouldn't one type of event work while the other works OK?
Form
+1  A: 
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);

This is not global. This installs the handler only when your own application is active, and (I believe) after the Carbon Event Manager's own event filters.

You need to use InstallEventHandler, which takes an event target as its first parameter (InstallApplicationEventHandler is a macro that passes the application event target).

For events that occur while your application is not active, the target you want is GetEventMonitorTarget(). For events that occur while your application is active, the target you want is GetEventDispatcherTarget(). To catch events no matter what application is active, install your handler on both targets.

Nowadays, though, I'd just use CGEventTaps, as Leibowitzn suggested.

Peter Hosey
It's weird because InstallApplicationEventHandler installs my hot key everywhere, not only while my application is active. I can trigger the hot keys from any application. Additionally when I try using InstallEventHandler with GetEventMonitorTarget() it doesn't seem to work, but I must be doing something wrong.What advantage is there to use CGEventTap instead of what I'm using right now?
Form
It's a lot easier.
Peter Hosey
Also, if you just want to register a hotkey, do that using [the RegisterEventHotKey function](http://developer.apple.com/legacy/mac/library/documentation/Carbon/Reference/Carbon_Event_Manager_Ref/Reference/reference.html#//apple_ref/c/func/RegisterEventHotKey), which exists for that very purpose.
Peter Hosey