2015-01-01 14 views
5

Dato un codice chiave per un tasto premuto senza modificatori, voglio produrre il risultato della pressione del tasto MAIUSC +. Esempio: per una tastiera standard USA < maiuscole> + < periodo> indica>.Come utilizzare UCKeyTranslate

La funzione rilevante è UCKeytranslate, ma ho bisogno di un po 'di aiuto per ottenere i dettagli corretti. Lo snippet seguente è un programma completo pronto per essere eseguito in XCode. L'intento del programma è dato da < periodo> per produrre il carattere>.

Il risultato del programma è:

Keyboard: <TSMInputSource 0x10051a930> KB Layout: U.S. (id=0) 
Layout: 0x0000000102802000 
Status: -50 
UnicodeString: 97 
String: a 
Done 
Program ended with exit code: 0 

La parte che ottiene il layout sembra funzionare, ma il codice di stato rivela che qualcosa è andato storto. Ma cosa?

import Foundation 
import Cocoa 
import Carbon 
import AppKit 

// The current text input source (read keyboard) has a layout in which 
// we can lookup how key-codes are resolved. 

// Get the a reference keyboard using the current layout. 
var unmanagedKeyboard = TISCopyCurrentKeyboardLayoutInputSource() 
var keyboard = unmanagedKeyboard.takeUnretainedValue() as TISInputSource 
print("Keyboard: ") ; println(keyboard) 

// Get the layout 
var ptrLayout = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData) 
var layout = UnsafeMutablePointer<UCKeyboardLayout>(ptrLayout) 
print("Layout: "); println(layout) 

// Let's see what the result of pressing <shift> and <period> (hopefully the result is >) 
var keycode    = UInt16(kVK_ANSI_Period)       // Keycode for <period> 
var keyaction   = UInt16(kUCKeyActionDisplay)      // The user is requesting information for key display 
var modifierKeyState = UInt32(1 << 17)         // Shift 
var keyboardType  = UInt32(LMGetKbdType()) 
var keyTranslateOptions = UInt32(1 << kUCKeyTranslateNoDeadKeysBit) 
var deadKeyState  = UnsafeMutablePointer<UInt32>(bitPattern: 0)  // Is 0 the correct value? 
var maxStringLength  = UniCharCount(4)         // uint32 
var actualStringLength = UnsafeMutablePointer<UniCharCount>.alloc(1)  // 
actualStringLength[0]=16 
var unicodeString  = UnsafeMutablePointer<UniChar>.alloc(255) 
unicodeString[0] = 97 // a (this value is meant to be overwritten by UCKeyTranslate) 
var str = NSString(characters: unicodeString, length: 1) 
var result = UCKeyTranslate(layout, keycode, keyaction, modifierKeyState, keyboardType, keyTranslateOptions, 
          deadKeyState, maxStringLength, actualStringLength, unicodeString) 

// Print the results 
print("Status: "); println(result) 
var unichar = unicodeString[0]; 
print("UnicodeString: "); println(String(unichar)) 
print("String: "); println(str) 
println("Done") 

EDIT

ho riscritto il frammento seguendo i consigli di Ken Tommaso. Alcuni trucchi da: Graphite è stato utilizzato anche un programma Swift che utilizza i keycode.

import Foundation 
import Cocoa 
import Carbon 
import AppKit 

// The current text input source (read keyboard) has a layout in which 
// we can lookup how key-codes are resolved. 

// Get the a reference keyboard using the current layout. 
let keyboard = TISCopyCurrentKeyboardInputSource().takeRetainedValue() 
let rawLayoutData = TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData) 
print("Keyboard: ") ; println(keyboard) 

// Get the layout 
var layoutData  = unsafeBitCast(rawLayoutData, CFDataRef.self) 
var layout: UnsafePointer<UCKeyboardLayout> = unsafeBitCast(CFDataGetBytePtr(layoutData), UnsafePointer<UCKeyboardLayout>.self) 
print("Layout: "); println(layout) 

print("KbdType "); println(LMGetKbdType()) // Sanity check (prints 44) 

var keycode    = UInt16(kVK_ANSI_Period)       // Keycode for a 
var keyaction   = UInt16(kUCKeyActionDisplay) 
var modifierKeyState = UInt32(1 << 1)         // Shift 
var keyboardType  = UInt32(LMGetKbdType()) 
var keyTranslateOptions = OptionBits(kUCKeyTranslateNoDeadKeysBit) 
var deadKeyState  = UInt32(0)          // Is 0 the correct value? 
var maxStringLength  = UniCharCount(4)         // uint32 
var chars: [UniChar] = [0,0,0,0] 
var actualStringLength = UniCharCount(1) 
var result = UCKeyTranslate(layout, keycode, keyaction, modifierKeyState, keyboardType, keyTranslateOptions, 
          &deadKeyState, maxStringLength, &actualStringLength, &chars) 
// Print the results 
print("Status: "); println(result) 
print("Out:"); println(UnicodeScalar(chars[0])) 
println("Done") 
+1

Errore "L'uso del tipo non dichiarato 'UCKeyboardLayout'' con questo codice. hai qualche idea su come aggiustarlo? –

+1

I documenti per 10.10.3 dicono che la struttura 'UCKeyboardLayout' è stata rimossa. Hai bisogno di guardare cosa ha sostituito. https://developer.apple.com/library/mac/releasenotes/General/APIDiffsMacOSX10_10_3/modules/CoreServices.html – soegaard

+1

PS: ho provato a cercarlo, ma i documenti per UCKeyboardLayout non menzionano affatto Swift: https: //developer.apple.com/library/mac/documentation/Carbon/Reference/Unicode_Utilities_Ref/#//apple_ref/doc/c_ref/UCKeyboardLayout – soegaard

risposta

5

Per kTISPropertyUnicodeKeyLayoutData, TISGetInputSourceProperty() restituisce un CFDataRef. È necessario ottenere il puntatore byte e trattarlo come puntatore su UCKeyboardLayout. Non credo che sia quello che stai facendo con questa linea:

var layout = UnsafeMutablePointer<UCKeyboardLayout>(ptrLayout) 

io non so davvero Swift, ma sarebbe probabilmente funzionerà come:

var layout = UnsafePointer<UCKeyboardLayout>(CFDataGetBytePtr(ptrLayout)) 

o forse:

var layout = CFDataGetBytePtr(ptrLayout) as UnsafePointer<UCKeyboardLayout> 

Inoltre, kUCKeyActionDisplay è in gran parte inutile. Il suo intento è di restituire l'etichetta della chiave, ma non lo fa nemmeno in modo affidabile. Probabilmente vuoi usare kUCKeyActionDown.

Per i modificatori, si desidera utilizzare la maschera bit di Carbonio shiftKey spostata a destra di 8 bit (come mostrato nella documentazione per UCKeyTranslate()). shiftKey è 1 << 9, quindi shiftKey >> 8 è 1 << 1.

Per le opzioni, dovresti essere in grado di utilizzare kUCKeyTranslateNoDeadKeysMask per semplicità. È equivalente a 1 << kUCKeyTranslateNoDeadKeysBit.

Sì, 0 è il valore corretto per deadKeyState per una sequenza di tasti iniziale o per uno in cui non si desidera applicare alcun stato di chiave morta precedente.

Non sono sicuro del motivo per cui hai commentato la riga maxStringLength con uint32. Quel tipo è completamente non correlato alla lunghezza massima della stringa. maxStringLength è il numero massimo di unità di codice UTF-16 (ciò che le API chiamano erroneamente "caratteri") UCKeyTranslate() dovrebbe scrivere nel buffer fornito. È fondamentalmente la dimensione del buffer, misurata in UniChar s (non in byte). Nel tuo caso dovrebbe essere 255. Oppure, dato che probabilmente non ti aspetti di ottenere 255 "caratteri" da una singola sequenza di tasti, potresti ridurre la dimensione del buffer e impostare maxStringLength in modo che corrisponda a qualsiasi cosa sia.

La gestione di str è strana.Lo si costruisce dal buffer unicodeString prima di chiamare UCKeyTranslate(). Si prevede che il valore dell'oggetto stringa venga modificato perché UCKeyTranslate() modifica il contenuto di unicodeString? Non è così. Se lo facesse, sarebbe un bug molto brutto in NSString. È necessario creare NSString dal buffer dopo cheUCKeyTranslate() ha inserito correttamente il buffer. Naturalmente, è necessario passare actualStringLength come parametro di lunghezza durante la costruzione di NSString.

+0

Grazie mille per i vostri suggerimenti. Sento che mi sto avvicinando a una soluzione: ho solo bisogno di rompere il problema CFDataGetBytePtr. Il motivo del commento uint32 è che sto scrivendo binding per UCKeyTranslate e ho bisogno del tipo C sottostante. Il programma Swift è quello di ottenere un esempio funzionante che posso imitare con l'FFI. – soegaard

+0

Grazie per la tua risposta completa. – soegaard