Вопрос

Я работаю над приложением для обучения набору текста для 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;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top