OSX: обнаруживать общесистемные события keyDown? - PullRequest
56 голосов
/ 20 января 2011

Я работаю над приложением-репетитором для 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 ]

24 голосов
/ 21 января 2011

Если вы согласны с минимальным требованием OS X 10.6+ и вам может быть достаточно доступа «только для чтения» к потоку событий, вы можете установить глобальный монитор событий в Какао: Какао-обработка событийРуководство: Мониторинг событий .

Если вам нужна поддержка OS X 10.5 и более ранних версий, и доступ только для чтения в порядке, и вы не против работать с Carbon Event Manager, вы можете сделатьУглерод-эквивалент с использованием GetEventMonitorTarget().(Вам будет трудно найти любую (официальную) документацию по этому методу, хотя).Я полагаю, что этот API впервые был доступен в OS X 10.3.

Если вам нужен доступ на чтение и запись к потоку событий, вам нужно взглянуть на API более низкого уровня, который является частью ApplicationServices>CoreGraphics: CGEventTapCreate() и друзья.Впервые это было доступно в 10.4.

Обратите внимание, что для всех 3 методов потребуется, чтобы у пользователя было включено «Включить доступ для вспомогательных устройств» на панели «Системные настройки»> «Предпочтения универсального доступа» (по крайней мере для ключевых событий).1013 *

14 голосов
/ 04 июня 2012

Я публикую код, который работал для моего дела.

Я добавляю глобальный обработчик событий после запуска приложения. Мой ярлык заставляет 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];
        }
    }];

}
4 голосов
/ 17 февраля 2011

Проблема, с которой я сталкиваюсь, заключается в том, что любой ключ, зарегистрированный глобально другим приложением, не будет обнаружен ... или, по крайней мере, в моем случае, возможно, я делаю что-то не так.

Если вашей программе необходимо отобразить все клавиши, например «Command-Shift-3», то она не увидит, что она отображается, поскольку она используется ОС.

Или кто-то понял это? Я хотел бы знать ...

3 голосов
/ 20 февраля 2013

Как уже указывал 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;
}
...