2011-01-20 7 views
54

Sto lavorando a un'applicazione di dattilografia per Mac OSX che deve essere preceduta da sequenze di tasti, anche quando l'applicazione non è a fuoco.OS X: Rileva eventi keyDown a livello di sistema?

C'è un modo per fare in modo che il sistema trasmetta le sequenze di tasti all'app, possibilmente attraverso NSDistributedNotificationCenter? Googled me stesso stupido, e non sono stati in grado di trovare una risposta ...

EDIT:codice di esempio sotto.

Grazie a @NSGod per avermi indirizzato nella giusta direzione - ho finito per aggiungere uno global events monitor usando il metodo addGlobalMonitorForEventsMatchingMask: handler :, che funziona magnificamente. Per completezza, la mia applicazione si presenta così:

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

}]; 

Per me, la parte difficile stava usando blocchi, quindi mi danno un po 'di descrizione in caso aiuta chiunque:

La cosa da notare circa il sopra il codice è che è tutto un singolo metodo chiamata su NSEvent. Il blocco viene fornito come argomento, direttamente da a la funzione. Potresti pensare ad un metodo di delegazione in linea. Solo perché questo ha preso un po 'per affondare dentro per me, ho intenzione di lavorare attraverso passo per passo qui:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask 

Questo primo bit non è un problema. Stai chiamando un metodo di classe su NSEvent e dicendogli quale evento stai cercando di monitorare, in questo caso NSKeyDownMask. A list of masks for supported event types può essere trovato here.

Ora, veniamo alla parte difficile: gestore, che si aspetta un blocco:

handler:^(NSEvent *event){ 

Mi ci sono voluti un paio di errori di compilazione per ottenere questo diritto, ma (grazie Apple) sono stati errori molto costruttivo messaggi. La prima cosa da notare è il carato ^. Questo segnala l'inizio del blocco. Dopo di che, tra parentesi,

NSEvent *event 

che dichiara la variabile che userete all'interno del blocco per catturare l'evento. Potresti chiamarlo

NSEvent *someCustomNameForAnEvent 

non importa, starai usando quel nome all'interno del blocco. Quindi, questo è tutto quello che c'è da fare. Assicurati di chiudere la parentesi graffa e la staffa per terminare la chiamata al metodo:

}]; 

E il gioco è fatto! Questo è davvero un "one-liner". Non importa dove esegui questa chiamata all'interno della tua app - lo faccio nel metodo applicationDidFinishLaunching di AppDelegate. Quindi, all'interno del blocco, puoi chiamare altri metodi dall'app.

+1

Hey @Pirripli grazie tanto per spiegare il blocco sintassi per me VERAMENTE aiuta per coloro che imparano! A proposito, ottimo :) – lab12

+1

Nessun problema. ;) Sono sempre grato quando gli altri lo fanno per me, quindi cerco di dare una mano quando posso. –

+0

Quando provo questo blocco di codice, ottengo l'errore: 'Tipi di puntatori del blocco incompatibili che inviano 'void (^) (struct NSEvent *)'' al parametro di tipo void (^) (NSEvent *) 'nella dichiarazione - @Pirripli , Ho dimenticato di aggiungere qualcosa? Hai dovuto includere qualcosa di speciale per supportare la sintassi del blocco? [Screenshot] (https://img.skitch.com/20111024-tnmnka59j9fqn6u145g2jppk5d.png) – Tronathan

risposta

24

Se stai bene con un requisito minimo di OS X 10.6+, e può bastare con "sola lettura" l'accesso al flusso degli eventi, è possibile installare un monitor evento globale a Cocoa: Cocoa Event-Handling Guide: Monitoring Events.

Se è necessario supportare OS X 10.5 e precedenti, e l'accesso in sola lettura va bene, e non importa lavorare con Carbon Event Manager, puoi fondamentalmente fare il Carbon equivalente usando GetEventMonitorTarget(). (Ti sarà difficile trovare comunque una documentazione (ufficiale) su quel metodo). Questa API era disponibile per la prima volta in OS X 10.3, credo.

Se è necessario l'accesso in lettura/scrittura al flusso di eventi, sarà necessario consultare un'API leggermente inferiore che fa parte di ApplicationServices> CoreGraphics: CGEventTapCreate() e amici. Questo è stato il primo disponibile in 10.4.

Si noti che tutti e 3 i metodi richiederanno che l'utente abbia "Abilita accesso per dispositivi di assistenza" abilitato nel pannello delle preferenze di Sistema> Accesso Universale (almeno per gli eventi chiave).

+0

Fantastico, sembra fantastico! Vedrò se riesco a far funzionare le cose domani, e poi accetto! Una domanda: c'è un modo per testare se l'utente ha accesso abilitato, ad es. per permettermi di spingerli a farlo? –

+1

Puoi farlo usando uno script Apple. Prova lo script qui sotto. tell application "Eventi di sistema" per impostare isUIScriptingEnabled di elementi dell'interfaccia utente abilitato se isUIScriptingEnabled = false allora \t tell application "Preferenze di Sistema" \t \t attivano \t \t impostare riquadro corrente al riquadro "com.apple.preference.universalaccess" \t \t visualizzazione finestra "seleziona il \" Abilita l'accesso a dispositivi d'assistenza \ "casella di controllo e riavviare" \t \t ritorno \t end tell fine se –

3

Il problema che ho riscontrato è che qualsiasi chiave registrata globalmente da un'altra app non verrà utilizzata ... o almeno nel mio caso, forse sto facendo qualcosa di sbagliato.

Se il tuo programma deve visualizzare tutti i tasti, ad esempio "Command-Shift-3", ad esempio, non vedrà che passa per visualizzarlo ... poiché è occupato dal sistema operativo.

Oppure qualcuno lo ha capito? Mi piacerebbe sapere ...

+2

Forse hai trovato il solut a questo? Sono abbastanza interessato anche a questo. –

14

Sto postando il codice che ha funzionato per il mio caso.

Aggiungo il gestore eventi globale dopo l'avvio dell'app. La mia scorciatoia fa ctrl + alt + cmd + T apri la mia app.

- (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

Come già sottolineato da NSGod, è possibile utilizzare CoreGraphics.

Nella classe (ad esempio 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(); 

Al di fuori della classe, ma nello stesso file .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; 
} 
+0

Grazie per il post dettagliato! Ha funzionato alla grande – vaughan

+0

Chiama '[NSEvent eventWithCGEvent: event]' per ottenere un 'NSEvent' con un'API migliore per lavorare con il codice ObjC. In ARC devi fare un ponte. Esempio: '[(__bridge MyClass *) refcon myHandlerInstanceMethod: e];' – vaughan