ОС:Обнаруживать общесистемные события keyDown?
-
13-10-2019 - |
Вопрос
Я работаю над приложением для обучения набору текста для Mac OSX, которому необходимо перенаправлять нажатия клавиш, даже если приложение не в фокусе.
Есть ли способ заставить систему перенаправлять нажатия клавиш в приложение, возможно, через NSDistributedNotificationCenter?Я тупо гуглил и не нашел ответа...
РЕДАКТИРОВАТЬ: Пример кода ниже.
Спасибо @NSGod за то, что указали мне правильное направление. В итоге я добавил монитор глобальных событий используя метод addGlobalMonitorForEventsMatchingMask:handler:
, который прекрасно работает.Для полноты моя реализация выглядит так:
// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSString *chars = [[event characters] lowercaseString];
unichar character = [chars characterAtIndex:0];
NSLog(@"keydown globally! Which key? This key: %c", character);
}];
Для меня самой сложной частью было использование блоков, поэтому я дам небольшое описание, если это кому-то поможет:
В приведенном выше коде следует отметить, что все это один единственный вызов метода на NSEvent.Блок предоставляется в качестве аргумента, напрямую к функция.Вы можете думать об этом как о встроенном методе делегата.Просто потому, что мне потребовалось некоторое время, чтобы это осознать, я собираюсь пройти через это шаг за шагом здесь:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
Этот первый бит не является проблемой.Вы вызываете метод класса NSEvent и сообщаете ему, какое событие вы хотите отслеживать, в данном случае NSKeyDownMask.А список масок для поддерживаемых типов событий может быть найден здесь.
Теперь мы подошли к самой сложной части:обработчик, который ожидает блок:
handler:^(NSEvent *event){
Мне потребовалось несколько ошибок компиляции, чтобы сделать это правильно, но (спасибо Apple) это были очень конструктивные сообщения об ошибках.Первое, на что следует обратить внимание, это карат ^.Это сигнализирует о начале блока.После этого в скобках
NSEvent *event
В нем объявляется переменная, которую вы будете использовать внутри блока для захвата события.Вы могли бы назвать это
NSEvent *someCustomNameForAnEvent
не имеет значения, вы просто будете использовать это имя внутри блока.Тогда это почти все, что нужно.Обязательно закройте фигурную скобку и скобку, чтобы завершить вызов метода:
}];
И вы сделали!Это действительно что-то вроде «однострочника».Не имеет значения, где вы выполняете этот вызов в своем приложении — я делаю это в методе applicationDidFinishLaunching AppDelegate.Затем внутри блока вы можете вызывать другие методы из вашего приложения.
Решение
Если вас устраивают минимальные требования OS X 10.6+ и вам достаточно доступа «только для чтения» к потоку событий, вы можете установить глобальный монитор событий в Cocoa:Руководство по обработке событий Cocoa:Мониторинг событий.
Если вам нужна поддержка OS X 10.5 и более ранних версий, и доступ только для чтения в порядке, и вы не против работать с Carbon Event Manager, вы можете в основном выполнить эквивалент Carbon, используя GetEventMonitorTarget()
.(Однако вам будет сложно найти какую-либо (официальную) документацию по этому методу).Я полагаю, что этот API впервые был доступен в OS X 10.3.
Если вам нужен доступ для чтения и записи к потоку событий, вам нужно будет взглянуть на API немного более низкого уровня, который является частью ApplicationServices > CoreGraphics:CGEventTapCreate()
и друзья.Впервые это было доступно в версии 10.4.
Обратите внимание, что для всех трех методов потребуется, чтобы у пользователя был включен параметр «Включить доступ для вспомогательных устройств» на панели предпочтений «Системные настройки» > «Универсальный доступ» (по крайней мере, для ключевых событий).
Другие советы
Я публикую код, который работал в моем случае.
Я добавляю глобальный обработчик событий после запуска приложения.Мой ярлык позволяет ctrl+alt+cmd+T открыть мое приложение.
- (void) applicationWillFinishLaunching:(NSNotification *)aNotification
{
// Register global key handler, passing a block as a callback function
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
// Activate app when pressing cmd+ctrl+alt+T
if([event modifierFlags] == 1835305 && [[event charactersIgnoringModifiers] compare:@"t"] == 0) {
[NSApp activateIgnoringOtherApps:YES];
}
}];
}
Проблема, с которой я столкнулся, заключается в том, что любой ключ, зарегистрированный глобально другим приложением, не будет перехвачен...или, по крайней мере, в моем случае, возможно, я делаю что-то неправильно.
Если вашей программе необходимо отображать все клавиши, например «Command-Shift-3», она не увидит, что нужно отобразить их...поскольку он занят ОС.
Или кто-то это догадался?Я хотел бы знать...
Как уже указывал NSGod, вы также можете использовать CoreGraphics.
В вашем классе (напр.в -инит):
CFRunLoopRef runloop = (CFRunLoopRef)CFRunLoopGetCurrent();
CGEventMask interestedEvents = NSKeyDown;
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
0, interestedEvents, myCGEventCallback, self);
// by passing self as last argument, you can later send events to this class instance
CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
eventTap, 0);
CFRunLoopAddSource((CFRunLoopRef)runloop, source, kCFRunLoopCommonModes);
CFRunLoopRun();
Вне класса, но в том же файле .m:
CGEventRef myCGEventCallback(CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
void *refcon)
{
if(type == NX_KEYDOWN)
{
// we convert our event into plain unicode
UniChar myUnichar[2];
UniCharCount actualLength;
UniCharCount outputLength = 1;
CGEventKeyboardGetUnicodeString(event, outputLength, &actualLength, myUnichar);
// do something with the key
NSLog(@"Character: %c", *myUnichar);
NSLog(@"Int Value: %i", *myUnichar);
// you can now also call your class instance with refcon
[(id)refcon sendUniChar:*myUnichar];
}
// send event to next application
return event;
}