OSX: detectar eventos de KeyDown en todo el sistema?

Estoy trabajando en una aplicación de tipeo-tutor para Mac OSX que necesita tener presionadas las teclas, incluso cuando la aplicación no está enfocada.

¿Hay alguna manera de que el sistema reenvíe las teclas a la aplicación, posiblemente a través de NSDistributedNotificationCenter? Busqué en Google y soy tonto, y no he podido encontrar una respuesta …

EDITAR: código de ejemplo a continuación .

Gracias a @NSGod por apuntarme en la dirección correcta: terminé agregando un monitor de eventos global usando el método addGlobalMonitorForEventsMatchingMask: handler :, que funciona muy bien. Para completar, mi implementación se ve así:

// 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); }]; 

Para mí, la parte difícil fue usar bloques, así que daré una pequeña descripción en caso de que ayude a alguien:

Lo que hay que tener en cuenta sobre el código anterior es que se trata de un único método de llamada en NSEvent. El bloque se proporciona como argumento, directamente a la función. Se podría pensar que es como un método de delegado en línea. El hecho de que esto tomó un tiempo para mí, voy a trabajar paso a paso aquí:

 [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask 

Este primer bit no es problema. Está llamando a un método de clase en NSEvent y diciéndole qué evento desea supervisar, en este caso NSKeyDownMask. Aquí puede encontrar una lista de máscaras para tipos de eventos compatibles .

Ahora, llegamos a la parte difícil: handler, que espera un bloque:

 handler:^(NSEvent *event){ 

Me tomó algunos errores de comstackción para hacerlo bien, pero (gracias Apple) fueron mensajes de error muy constructivos. Lo primero que debe notar es el quilate ^. Eso señala el comienzo del bloque. Después de eso, entre paréntesis,

 NSEvent *event 

Que declara la variable que usará dentro del bloque para capturar el evento. Podrías llamarlo

 NSEvent *someCustomNameForAnEvent 

no importa, solo usarás ese nombre dentro del bloque. Entonces, eso es todo lo que hay que hacer. Asegúrese de cerrar su llave y su soporte para finalizar la llamada al método:

 }]; 

¡Y tu estas listo! Esto realmente es una especie de ‘one-liner’. No importa dónde ejecute esta llamada dentro de su aplicación, lo hago en el método applicationDidFinishLaunching de AppDelegate. Luego, dentro del bloque, puede llamar a otros métodos desde su aplicación.

Si está de acuerdo con un requisito mínimo de OS X 10.6+, y puede ser suficiente con el acceso de “solo lectura” a la secuencia de eventos, puede instalar un monitor de eventos global en Cocoa: Guía de manejo de eventos de Cocoa: Monitoreo de eventos .

Si necesita soportar OS X 10.5 y versiones anteriores, y el acceso de solo lectura está bien, y no le importa trabajar con el Carbon Event Manager, básicamente puede hacer el equivalente de carbono usando GetEventMonitorTarget() . (Sin embargo, será muy difícil encontrar documentación (oficial) sobre ese método). Esa API estaba primero disponible en OS X 10.3, creo.

Si necesita acceso de lectura y escritura a la secuencia de eventos, necesitará buscar una API de nivel ligeramente inferior que sea parte de ApplicationServices> CGEventTapCreate() : CGEventTapCreate() y sus amigos. Esto estaba primero disponible en 10.4.

Tenga en cuenta que los 3 métodos requerirán que el usuario tenga habilitado “Habilitar acceso para dispositivos de asistencia” en Preferencias del Sistema> panel de preferencias de Acceso Universal (al menos para eventos clave).

Estoy publicando el código que funcionó para mi caso.

Estoy agregando el controlador de eventos global después de que se inicia la aplicación. Mi atajo hace que ctrl + alt + cmd + T abra mi aplicación.

 - (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]; } }]; } 

El problema que encuentro con esto es que cualquier clave registrada globalmente por otra aplicación no se considerará … o al menos en mi caso, tal vez estoy haciendo algo mal.

Si su progtwig necesita mostrar todas las teclas, como “Command-Shift-3” por ejemplo, entonces no verá que pasan para mostrarlo … ya que el SO lo utiliza.

¿O alguien lo descubrió? Me encantaría saber …

Como NSGod ya señaló, también puedes usar CoreGraphics.

En su clase (por ejemplo, en -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(); 

Fuera de la clase, pero en el mismo archivo .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; }