Given a key-code for a key pressed without modifiers, I want to produce the result of pressing the shift+key. Example: For a standard US keyboard <shift>+<period> gives >.
The relevant function is UCKeytranslate, but I need a bit of help getting the details right. The snippet below is a full program ready to run in XCode. The intent of the program is given <period> to produce the character >.
The result of the program is:
Keyboard: <TSMInputSource 0x10051a930> KB Layout: U.S. (id=0)
Layout: 0x0000000102802000
Status: -50
UnicodeString: 97
String: a
Done
Program ended with exit code: 0
The part that gets the layout seems to be working, but the status code reveals that something went wrong. But what?
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
I have rewritten the snippet following the suggestions of Ken Thomases. A few tricks from: Graphite a Swift program using keycodes was also used.
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")
For
kTISPropertyUnicodeKeyLayoutData
,TISGetInputSourceProperty()
returns aCFDataRef
. You need to get its bytes pointer and treat that as the pointer toUCKeyboardLayout
. I don't think that's what you're doing with this line:I don't really know Swift, but it would probably work as:
or maybe:
Also,
kUCKeyActionDisplay
is mostly useless. Its intent is to return the label of the key, but it doesn't even do that reliably. You probably want to usekUCKeyActionDown
.For the modifiers, you want to use the Carbon-era
shiftKey
bit mask shifted right 8 bits (as shown in the documentation forUCKeyTranslate()
).shiftKey
is1 << 9
, soshiftKey >> 8
is1 << 1
.For the options, you should be able to use
kUCKeyTranslateNoDeadKeysMask
for simplicity. It's equivalent to1 << kUCKeyTranslateNoDeadKeysBit
.Yes, 0 is the proper value for
deadKeyState
for an initial keystroke or one where you don't want to apply any previous dead-key state.I'm not sure why you've commented the
maxStringLength
line withuint32
. That type is completely unrelated to the maximum string length.maxStringLength
is the maximum number of UTF-16 code units (what the APIs incorrectly call "characters") theUCKeyTranslate()
should write to the provided buffer. It's basically the buffer size, measured inUniChar
s (not bytes). In your case it should be 255. Or, since you probably don't expect to get 255 "characters" from a single keystroke, you could reduce the size of the buffer and setmaxStringLength
to match whatever it is.Your handling of
str
is strange. You construct it from theunicodeString
buffer before callingUCKeyTranslate()
. Do you expect that string object's value to be changed becauseUCKeyTranslate()
changes the contents ofunicodeString
? It does not. If it did, that would be a very bad bug inNSString
. You should construct theNSString
from the buffer afterUCKeyTranslate()
has successfully populated that buffer. Of course, you should passactualStringLength
as the length parameter when constructing theNSString
.