OSX: обнаруживать общесистемные события keyDown?
Я работаю над приложением-репетитором для 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. Затем внутри блока вы можете вызывать другие методы из вашего приложения.
4 ответа
Если вы согласны с минимальным требованием OS X 10.6+ и вам может быть достаточно доступа "только для чтения" к потоку событий, вы можете установить глобальный монитор событий в Какао: Руководство по обработке событий в Какао : Мониторинг событий.
Если вам требуется поддержка 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.
В вашем классе (например, в -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;
}