2015-09-10 30 views

risposta

13

realtà ho risolto questo problema io stesso proprio l'altro giorno. Ho scritto un blog post su di esso, nonché un Gist

Inserirò il post del blog e il codice finale nel caso in cui il blog o Gist andassero mai via. Nota: Questo è un post molto lungo che entra nei dettagli su come viene costruita la classe e su cosa puoi fare per chiamare altri metodi nel delegato della tua app. Se tutto ciò che si desidera è il prodotto finito (la classe MediaApplication), dirigersi verso il basso. È appena sopra l'XML e le informazioni di Info.plist.


Per cominciare, per ottenere gli eventi chiave dei tasti multimediali necessari per creare una classe che estende NSApplication. Questo è così semplice come

import Cocoa 

class MediaApplication: NSApplication { 
} 

Avanti, abbiamo bisogno di ignorare la funzione sendEvent()

override func sendEvent(event: NSEvent) { 
    if (event.type == .SystemDefined && event.subtype.rawValue == 8) { 
     let keyCode = ((event.data1 & 0xFFFF0000) >> 16) 
     let keyFlags = (event.data1 & 0x0000FFFF) 
     // Get the key state. 0xA is KeyDown, OxB is KeyUp 
     let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA 
     let keyRepeat = (keyFlags & 0x1) 
     mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(keyRepeat)) 
    } 

    super.sendEvent(event) 
} 

Ora, io non pretendo di capire tutto ciò che sta succedendo qui, ma penso di avere un idea decente. Gli oggetti NSEvent contengono diverse proprietà chiave: type, subtype, data1 e data2. Type e subtype sono abbastanza auto-esplicativi, ma data1 e data2 sono estremamente vaghi. Dal momento che il codice utilizza solo data1, questo è ciò che vedremo. Da quello che posso dire, data1 contiene tutti i dati che circondano un evento chiave. Ciò significa che contiene il codice chiave e le eventuali bandiere chiave. Sembra che i flag dei tasti contengano informazioni sullo stato della chiave (il tasto è premuto verso il basso? La chiave è stata rilasciata?) Così come se il tasto viene premuto o meno e ripetendo il segnale. Immagino anche che il codice tasto e le flag delle chiavi assorbano entrambi la metà dei dati contenuti in data1 e che le operazioni bit a bit stiano separando i dati in variabili appropriate. Dopo aver ottenuto i valori di cui abbiamo bisogno, chiamiamo lo mediaKeyEvent() che otterrò tra un momento. Indipendentemente da quali eventi vengono inviati al nostro MediaApplication, vogliamo che il valore predefinito sia NSApplication per gestire anche tutti gli eventi. Per fare questo, chiamiamo super.sendEvent(event) alla fine della funzione. Ora, diamo un'occhiata a mediaKeyEvent().

func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) { 
    // Only send events on KeyDown. Without this check, these events will happen twice 
    if (state) { 
     switch(key) { 
     case NX_KEYTYPE_PLAY: 
      // Do work 
      break 
     case NX_KEYTYPE_FAST: 
      // Do work 
      break 
     case NX_KEYTYPE_REWIND: 
      // Do work 
      break 
     default: 
      break 
     } 
    } 
} 

Qui è dove le cose iniziano a divertirsi. Per prima cosa, vogliamo solo controllare quale tasto viene premuto se state è vero, che in questo caso è ogni volta che viene premuto il tasto. Una volta entrati nel controllo delle chiavi, cerchiamo NX_KEYTYPE_PLAY, NX_KEYTYPE_FAST e NX_KEYTYPE_REWIND. Se le loro funzioni non sono evidenti, NX_KEYTYPE_PLAY è il tasto play/pausa, NX_KEYTYPE_FAST è la chiave successiva e NX_KEYTYPE_REWIND è la chiave precedente. In questo momento, non succede nulla quando uno di questi tasti viene premuto, quindi lascia andare oltre alcune possibili logiche. Inizieremo con uno scenario semplice.

case NX_KEYTYPE_PLAY: 
    print("Play") 
    break 

Con questo codice in luogo, quando l'applicazione rileva che il tasto play/pausa è stato premuto si vedrà "PLAY" stampato fuori alla console. Semplice, vero? Facciamo il punto chiamando le funzioni nel NSApplicationDelegate dell'applicazione. Per prima cosa supporremo che il tuo NSApplicationDelegate abbia una funzione chiamata printMessage. Lo modificheremo mentre procediamo, quindi prestiamo molta attenzione ai cambiamenti. Saranno minori, ma i cambiamenti avranno un impatto su come li chiami da mediaEventKey.

func printMessage() { 
    print("Hello World") 
} 

Questo è il caso più semplice. Quando viene chiamato printMessage(), vedrai "Hello World" nella tua console. Puoi chiamare questo chiamando performSelector sul tuo NSApplicationDelegate che è accessibile attraverso il MediaApplication. performSelector prende in un Selector che è solo il nome della funzione nel tuo NSApplicationDelegate.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage") 
    break 

Ora, quando l'applicazione rileva che il tasto play/pausa è stato premuto, si vedrà "Ciao Mondo" stampata sulla console. Diamo un calcio d'inizio con una nuova versione di printMessage che accetta un parametro.

func printMessage(arg: String) { 
    print(arg) 
} 

L'idea è ora che se printMessage("Hello World") si chiama, si vedrà "Ciao Mondo" nella console. Ora possiamo modificare la chiamata performSelector per gestire il passaggio di un parametro.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage:", withObject: "Hello World") 
    break 

Ci sono alcune cose da notare su questo cambiamento. Innanzitutto, è importante notare che lo : è stato aggiunto allo Selector. Questo separa il nome della funzione dal parametro quando viene inviato al delegato. Come funziona non è troppo importante ricordare, ma è qualcosa sulla falsariga del delegato che chiama printMessage:"Hello World". Sono abbastanza sicuro che non è corretto al 100% in quanto probabilmente userebbe un ID oggetto di qualche tipo, ma non ho fatto nessuno scavo approfondito nello specifico. Ad ogni modo, la cosa importante da ricordare è aggiungere : quando si passa in un parametro. La seconda cosa da notare è che abbiamo aggiunto un parametro withObject. withObject prende un valore AnyObject?. In questo caso, passiamo appena in un String perché è ciò che sta cercando printMessage. Quando la tua applicazione rileva che il tasto play/pausa è stato premuto, dovresti comunque vedere "Hello World" nella console. Diamo un'occhiata a un caso d'uso finale: una versione di printMessage che contiene non uno, ma due parametri.

func printMessage(arg: String, _ arg2: String) { 
    print(arg) 
} 

Ora, se printMessage("Hello", "World") si chiama, si vedrà "Ciao Mondo" nella console. Ora possiamo modificare la chiamata performSelector per gestire il passaggio di due parametri.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage::", withObject: "Hello", withObject: "World") 
    break 

Come prima, ci sono due cose da notare qui. Innanzitutto, aggiungiamo duealla fine dello Selector. Come prima, è così che il delegato può passare le informazioni che contengono i parametri. A un livello molto semplice, assomiglierebbe a printMessage:"Hello":"World", ma ancora non so come sia a un livello più profondo. La seconda cosa da notare è che abbiamo aggiunto un secondo parametro withObject alla chiamata performSelector. Come prima, questo withObject prende un valore AnyObject? come valore e stiamo passando un String perché è ciò che vuole printMessage.Quando la tua applicazione rileva che il tasto play/pausa è stato premuto, dovresti comunque vedere "Hello World" nella console.

Un'ultima cosa da notare è che performSelector può accettare solo fino a due parametri. Mi piacerebbe davvero vedere Swift aggiungere concetti come splatting o varargs in modo che questa limitazione alla fine scompaia, ma per ora basta evitare di provare a chiamare funzioni che richiedono più di due parametri.

Questo è ciò che un molto semplice classe MediaApplication che stampa appena fuori una parte di testo potrebbe apparire come una volta che hai finito con tutto ciò di cui sopra:

import Cocoa 

class MediaApplication: NSApplication { 
    override func sendEvent(event: NSEvent) { 
     if (event.type == .SystemDefined && event.subtype.rawValue == 8) { 
      let keyCode = ((event.data1 & 0xFFFF0000) >> 16) 
      let keyFlags = (event.data1 & 0x0000FFFF) 
      // Get the key state. 0xA is KeyDown, OxB is KeyUp 
      let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA 
      let keyRepeat = (keyFlags & 0x1) 
      mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(keyRepeat)) 
     } 

     super.sendEvent(event) 
    } 

    func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) { 
     // Only send events on KeyDown. Without this check, these events will happen twice 
     if (state) { 
      switch(key) { 
      case NX_KEYTYPE_PLAY: 
       print("Play") 
       break 
      case NX_KEYTYPE_FAST: 
       print("Next") 
       break 
      case NX_KEYTYPE_REWIND: 
       print("Prev") 
       break 
      default: 
       break 
      } 
     } 
    } 
} 

Ora, devo anche aggiungere che, per impostazione predefinita, l'applicazione è intenzione di utilizzare lo standard NSApplication quando viene eseguito. Se si desidera utilizzare il numero MediaApplication di questo intero post, è necessario andare avanti e modificare il file Info.plist dell'applicazione. Se siete nella visualizzazione grafica, che sarà simile a questa:

Info.plist

In caso contrario, sarà simile a questa:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
<plist version="1.0"> 
<dict> 
    <key>CFBundleDevelopmentRegion</key> 
    <string>en</string> 
    <key>CFBundleExecutable</key> 
    <string>$(EXECUTABLE_NAME)</string> 
    <key>CFBundleIconFile</key> 
    <string></string> 
    <key>CFBundleIdentifier</key> 
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> 
    <key>CFBundleInfoDictionaryVersion</key> 
    <string>6.0</string> 
    <key>CFBundleName</key> 
    <string>$(PRODUCT_NAME)</string> 
    <key>CFBundlePackageType</key> 
    <string>APPL</string> 
    <key>CFBundleShortVersionString</key> 
    <string>1.0</string> 
    <key>CFBundleSignature</key> 
    <string>????</string> 
    <key>CFBundleVersion</key> 
    <string>1</string> 
    <key>LSApplicationCategoryType</key> 
    <string>public.app-category.utilities</string> 
    <key>LSMinimumSystemVersion</key> 
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string> 
    <key>LSUIElement</key> 
    <true/> 
    <key>NSHumanReadableCopyright</key> 
    <string>Copyright © 2015 Chris Rees. All rights reserved.</string> 
    <key>NSMainNibFile</key> 
    <string>MainMenu</string> 
    <key>NSPrincipalClass</key> 
    <string>NSApplication</string> 
</dict> 
</plist> 

In entrambi i casi, si vuole cambiare la proprietà NSPrincipalClass. Il nuovo valore includerà il nome del progetto, quindi sarà qualcosa come Notify.MediaApplication. Una volta apportata la modifica, esegui la tua applicazione e usa quelle chiavi multimediali!

+0

hey @Serneum grazie per i dettagli. Ecco il mio problema: possiamo bloccare questo evento per iTunes? in realtà ogni volta che si preme il tasto, avvia iTunes. – AJit

+0

Per gestire l'avvio di iTunes sulla pressione dei tasti multimediali. https://github.com/nevyn/SPMediaKeyTap – AJit

+0

Soluzione piacevole ma non funziona quando si crea l'applicazione in modo programmatico (utilizzando main.swift invece di usare storyboard o file xib) – ErwinGO