OSX: Обнаруживать общесистемные события KeyDown?
Я работаю над приложением для ввода текста для Mac OSX, которое должно иметь нажатые клавиши, даже если приложение не в фокусе.
Есть ли способ, чтобы система нажала клавиши в приложении, возможно, через NSDistributedNotificationCenter? Я глупо искал себя и не смог найти ответ...
EDIT: Пример кода ниже.
Спасибо @NSGod за то, что указали мне в правильном направлении - в итоге я добавил глобальный монитор событий, используя метод addGlobalMonitorForEventsMatchingMask: обработчик:, который прекрасно работает. Для полноты моя реализация выглядит так:
// 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. A список масок для поддерживаемых типов событий можно найти здесь.
Теперь мы перейдем к сложной части: обработчик, ожидающий блок:
handler:^(NSEvent *event){
Мне потребовалось несколько компиляционных ошибок, чтобы получить это право, но (спасибо Apple) они были очень конструктивными сообщениями об ошибках. Первое, что нужно заметить, это карата. Это сигнализирует о начале блока. После этого в круглых скобках
NSEvent *event
Что объявляет переменную, которую вы будете использовать в блоке для захвата события. Вы могли бы назвать это
NSEvent *someCustomNameForAnEvent
не имеет значения, вы просто будете использовать это имя внутри блока. Тогда, что почти все есть. Не забудьте закрыть фигурные скобки и скобку, чтобы завершить вызов метода:
}];
И все готово! Это действительно "однострочный". Неважно, где вы выполняете этот вызов в своем приложении - я делаю это в методе appDelegate applicationDidFinishLaunching. Затем внутри блока вы можете вызывать другие методы из вашего приложения.
Ответы
Ответ 1
Если вы согласны с минимальным требованием к OS X 10.6+ и могут быть достаточны для доступа к потоку событий только для чтения, вы можете установить глобальный монитор событий в Cocoa:
Cocoa Руководство по управлению событиями: мониторинг событий.
Если вам нужна поддержка OS X 10.5 и более ранних версий, и доступ только для чтения в порядке, и не против работать с менеджером событий Carbon, вы можете в основном использовать эквивалент Carbon с помощью GetEventMonitorTarget()
. (Вам будет трудно найти какую-либо (официальную) документацию по этому методу). Я полагаю, этот API был впервые доступен в OS X 10.3.
Если вам нужен доступ для чтения и записи к потоку событий, вам нужно будет посмотреть немного более низкий уровень API, который является частью ApplicationServices > CoreGraphics: CGEventTapCreate()
и друзей. Это было впервые доступно в 10.4.
Обратите внимание, что все 3 метода потребуют, чтобы у пользователя был включен "Включить доступ для вспомогательных устройств" в "Предпочтения системы" > "Настройки доступа к универсальному доступу" (по крайней мере, для ключевых событий).
Ответ 2
Я отправляю код, который работал для моего дела.
Я добавляю глобальный обработчик событий после запуска приложения. Мой ярлык позволяет 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];
}
}];
}
Ответ 3
Проблема с этим заключается в том, что любой ключ, зарегистрированный глобально другим приложением, не будет рассматриваться... или, по крайней мере, в моем случае, возможно, я что-то делаю неправильно.
Если ваша программа должна отображать все клавиши, например "Command-Shift-3", то она не увидит, что ее можно отобразить... поскольку она занята ОС.
Или кто-то понял это? Мне бы очень хотелось узнать...
Ответ 4
Как уже указывал NSGod, вы также можете использовать CoreGraphics.
В вашем классе (например, in -init):
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;
}