64 votes

OSX : Détecter les événements keyDown à l'échelle du système ? (Résolu, avec un exemple de code)

Je travaille sur une application d'apprentissage de la dactylographie pour Mac OSX qui a besoin que les frappes lui soient transmises, même lorsque l'application n'est pas en cours.

Existe-t-il un moyen de faire en sorte que le système transmette les frappes de touches à l'application, éventuellement par le biais du NSDistributedNotificationCenter ? J'ai cherché sur Google et je n'ai pas trouvé de réponse...

EDIT : Exemple de code ci-dessous .

Merci @NSGod de m'avoir orienté dans la bonne direction - j'ai fini par ajouter un moniteur d'événements mondiaux en utilisant la méthode addGlobalMonitorForEventsMatchingMask:handler :, qui fonctionne à merveille. Pour être complet, ma mise en œuvre 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 la plus délicate était l'utilisation des blocs, je vais donc donner une petite description au cas où cela aiderait quelqu'un :

Ce qu'il faut remarquer dans le code ci-dessus, c'est qu'il s'agit uniquement de un seul appel de méthode sur NSEvent. Le bloc est fourni comme argument, directement à la fonction. On peut considérer cela comme une méthode de délégation en ligne. Comme il m'a fallu un certain temps pour comprendre, je vais le faire étape par étape :

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask

La première partie ne pose aucun problème. Vous appelez une méthode de classe sur NSEvent, et vous lui indiquez quel événement vous voulez surveiller, dans ce cas NSKeyDownMask. A liste des masques pour les types d'événements pris en charge peuvent être trouvés ici .

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

handler:^(NSEvent *event){

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

NSEvent *event

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

NSEvent *someCustomNameForAnEvent

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

}];

Et vous avez terminé ! Il s'agit vraiment d'une sorte de "one-liner". Peu importe où vous exécutez cet appel dans votre application - je le fais dans la méthode applicationDidFinishLaunching de l'AppDelegate. Ensuite, à l'intérieur du bloc, vous pouvez appeler d'autres méthodes depuis votre application.

J'espère que cela aidera quelqu'un ! C'est vraiment très simple une fois que vous avez compris la syntaxe des blocs.

27voto

NSGod Points 14652

Si la configuration minimale requise est OS X 10.6+, et si vous pouvez vous contenter d'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 avez besoin de prendre en charge OS X 10.5 et antérieur, que l'accès en lecture seule est acceptable et que cela ne vous dérange pas de travailler avec le gestionnaire d'événements de Carbon, vous pouvez essentiellement faire l'équivalent de Carbon en utilisant GetEventMonitorTarget() . (Vous aurez cependant du mal à trouver une documentation (officielle) sur cette méthode). Cette API était disponible pour la première fois 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 vous tourner vers une API de niveau légèrement inférieur qui fait partie de ApplicationServices > CoreGraphics : CGEventTapCreate() et ses amis. Cette fonction était disponible pour la première fois dans la 10.4.

Notez que ces trois méthodes exigent que l'utilisateur ait activé l'option "Activer l'accès pour les dispositifs d'assistance" dans le panneau de préférences Préférences Système > Accès universel (au moins pour les événements clés).

15voto

micho Points 710

Je poste le code qui a fonctionné dans mon cas.

J'ajoute le gestionnaire d'événement global après le lancement de l'application. Mon raccourci fait que ctrl+alt+cmd+T ouvre 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];
        }
    }];

}

4voto

FabianKelschotz Points 139

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

Dans votre classe (par exemple dans -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 actualLenght;
       UniCharCount outputLenght = 1;
       CGEventKeyboardGetUnicodeString(event, outputLenght, &actualLenght, 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;
}

4voto

Duane Points 26

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

Si votre programme a besoin d'afficher toutes les touches, comme "Command-Shift-3" par exemple, il ne verra pas ce passage pour l'afficher... puisqu'il est occupé par l'OS.

Ou quelqu'un l'a compris ? J'aimerais bien le savoir...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X