Подтвердить что ты не робот

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. Затем внутри блока вы можете вызывать другие методы из вашего приложения.

4b9b3361

Ответ 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;
}