web-dev-qa-db-fra.com

OSX: Détecter les événements keyDown à l'échelle du système?

Je travaille sur une application de dactylographie pour Mac OSX qui doit lui être transférée, même lorsque l'application n'est pas mise au point.

Existe-t-il un moyen de faire en sorte que le système transmette les frappes à l'application, éventuellement via NSDistributedNotificationCenter? Je me suis idiot sur Google et je n'ai pas pu trouver de réponse ...

EDIT: Exemple de code ci-dessous.

Merci @NSGod de m'avoir pointé dans la bonne direction - j'ai fini par ajouter un moniteur d'événements global en utilisant la méthode addGlobalMonitorForEventsMatchingMask:handler:, qui fonctionne à merveille. Pour être complet, mon implémentation ressemble à ceci:

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

}];

Pour moi, la partie délicate était d'utiliser des blocs, donc je vais donner une petite description au cas où cela aiderait quelqu'un:

La chose à noter sur le code ci-dessus est que c'est tout un seul appel de méthode sur NSEvent. Le bloc est fourni en argument, directement à la fonction. Vous pourriez y penser un peu comme une méthode de délégué en ligne. Tout simplement parce que cela a pris du temps pour moi, je vais y travailler étape par étape ici:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask

Ce premier bit n'est pas un problème. Vous appelez une méthode de classe sur NSEvent et lui dites quel événement vous souhaitez surveiller, dans ce cas NSKeyDownMask. Un liste des masques pour les types d'événements pris en charge peut être trouvé ici .

Maintenant, nous arrivons à la partie délicate: le gestionnaire, qui attend un bloc:

handler:^(NSEvent *event){

Il m'a fallu quelques erreurs de compilation pour bien comprendre, mais (merci Apple), il s'agissait de messages d'erreur très constructifs. La première chose à remarquer est le carat ^. Cela signale le début du bloc. Après cela, entre parenthèses,

NSEvent *event

Qui déclare la variable que vous utiliserez dans le bloc pour capturer l'événement. Vous pourriez l'appeler

NSEvent *someCustomNameForAnEvent

n'a pas d'importance, vous allez simplement utiliser ce nom dans le bloc. Ensuite, c'est à peu près tout ce qu'il y a à faire. Assurez-vous de fermer votre accolade et votre support pour terminer l'appel de méthode:

}];

Et tu as fini! C'est vraiment une sorte de "one-liner". Peu importe où vous exécutez cet appel dans votre application - je le fais dans la méthode applicationDidFinishLaunching d'AppDelegate. Ensuite, dans le bloc, vous pouvez appeler d'autres méthodes à partir de votre application.

57
Chris Ladd

Si vous êtes d'accord avec une exigence minimale d'OS X 10.6+ et pouvez suffire avec un accès "en lecture seule" au flux d'événements, vous pouvez installer un moniteur d'événements global dans Cocoa: Guide de gestion des événements Cocoa: Surveillance des événements .

Si vous devez prendre en charge OS X 10.5 et versions antérieures, et que l'accès en lecture seule est correct, et que cela ne vous dérange pas de travailler avec Carbon Event Manager, vous pouvez faire l'équivalent de Carbon en utilisant GetEventMonitorTarget(). (Vous aurez cependant du mal à trouver de la documentation (officielle) sur cette méthode). Cette API était d'abord disponible dans OS X 10.3, je crois.

Si vous avez besoin d'un accès en lecture-écriture au flux d'événements, vous devrez regarder une API de niveau légèrement inférieur qui fait partie d'ApplicationServices> CoreGraphics: CGEventTapCreate() et amis. Ce fut d'abord disponible en 10.4.

Notez que les 3 méthodes nécessitent que l'utilisateur ait activé "Activer l'accès pour les appareils et accessoires fonctionnels" dans le volet Préférences Système> Accès universel (au moins pour les événements clés).

24
NSGod

Je publie le code qui a fonctionné pour mon cas.

J'ajoute le gestionnaire d'événements global après le lancement de l'application. Mon raccourci fait ctrl + alt + cmd + T ouvrir mon application.

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

}
14
micho

Le problème que je trouve avec cela est que toute clé enregistrée globalement par une autre application ne sera pas achetée ... ou du moins dans mon cas, je fais peut-être quelque chose de mal.

Si votre programme a besoin d'afficher toutes les touches, comme "Command-Shift-3" par exemple, alors il ne verra pas passer pour l'afficher ... car il est repris par l'OS.

Ou quelqu'un a-t-il compris cela? J'adorerais savoir ...

4
Duane

Comme NSGod l'a déjà souligné, vous pouvez également utiliser CoreGraphics.

Dans votre classe (par exemple 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();

En dehors de la classe, mais dans le même fichier .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;
}
3
Sitses