macos - traducción - OSX: detectar eventos de KeyDown en todo el sistema?
keypress windows forms (4)
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 compilació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.
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;
}
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 programa 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 ...
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];
}
}];
}
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).