Как контролировать состояние ключа глобального модификатора (в любом приложении)?

Я использую код Carbon в моем проекте Cocoa для обработки событий глобального ключа (ярлыков) из других приложений. В настоящее время я установил обработчик событий kEventHotKeyReleased, и я могу успешно получить горячие клавиши, когда мое приложение неактивно. Это вызывает некоторую операцию в моем приложении.

Проблема с поведением kEventHotKeyReleased заключается в следующем:

Скажем, например, я нажимаю комбинацию клавиш Cmd-Shift-P. Как только я отпускаю клавишу "P", запускается событие "горячая клавиша". Мне нужно иметь возможность запускать событие (или вручную запускать его), когда все клавиши не нажаты (то есть: клавиши Cmd и Shift также отпущены).

Легко контролировать горячие клавиши, но я ничего не видел для контроля отдельных нажатий клавиш. Если бы я мог контролировать состояния ключа-модификатора, я бы был в бизнесе.

Любые подсказки о том, как это сделать?

Спасибо заранее!


UPDATE:

Я пробовал использовать kEventRawKeyUp и kEventRawKeyModifiersChanged, но в то время как kEventHotKeyReleased работает эти два, даже если я настроил их точно так же, как 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

Метод globalHotKeyHandler вызывается для kEventHotKeyReleased, но не для kEventRawKeyUp по какой-то причине я не могу понять. Вот как выглядит мой метод globalHotKeyHandler:

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

Есть ли дополнительный вызов, который нужно сделать, или что-то еще, что я забыл?

N.B: На первый взгляд кажется, что Access для вспомогательных устройств отключен, но он не. Поэтому я довольно невежественный.


ОБНОВЛЕНИЕ 2:

Я немного исследовал предложение CGEventTap Лейбовицна, и я придумал эту настройку:

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

... и обратный вызов:

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

Как вы можете видеть, я использую kCGEventKeyUp в качестве маски для события, но каким-то образом я получаю события мыши вниз??!??


ОБНОВЛЕНИЕ 3:

Хорошо, забыл, что я пропустил строку в документе, в которой сказано, что для этого параметра используется CGEventMaskBit (kCGEventKeyUp), поэтому правильный вызов:

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

У меня все еще проблема: ключи-модификаторы не запускают kCGEventKeyUp...


ОБНОВЛЕНИЕ 4:

Хорошо, забывай об этом снова... Я обязан ответить на свои вопросы через 5 минут после того, как вы спросите их сегодня, да!

Чтобы перехватить ключи-модификаторы, используйте kCGEventFlagsChanged:

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

Таким образом, по сути, я получил ключевое состояние ключа и модификатора ключевого состояния, но Мне все еще интересно узнать, почему kEventRawKeyUp не работает...


N.B: Также обратите внимание, что я разрабатываю Tiger с целью поддержки как можно более новых и более старых версий ОС. CGEventTap только 10.4+, поэтому я буду использовать это сейчас, но приветствуется обратное совместимое решение.

Ответ 1

Один из вариантов - использовать EventTaps. Это позволяет контролировать все события клавиатуры. Видеть: http://developer.apple.com/mac/library/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple_ref/c/func/CGEventTapCreate

К сожалению, события событий перестают работать, если приложение запрашивает безопасный ввод. Например, Quicken.

Ответ 2

OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);

Это не глобально. Это устанавливает обработчик только тогда, когда ваше собственное приложение активно, и (я полагаю) после того, как собственные фильтры событий в Carbon Event Manager.

Вам нужно использовать InstallEventHandler, который принимает целевой объект в качестве своего первого параметра (InstallApplicationEventHandler - это макрос, который передает цель приложения).

Для событий, которые происходят, пока ваше приложение неактивно, целевым пользователем является GetEventMonitorTarget(). Для событий, которые происходят во время вашего приложения, целевой объект GetEventDispatcherTarget(). Чтобы захватывать события независимо от того, какое приложение активно, установите обработчик на обе цели.

В наши дни я бы просто использовал CGEventTaps, как предположил Лейбовицн.