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;
}
Другие вопросы по тегам