我知道其他人也问了类似的问题,但我还没有看到明确的答案,我仍然被困住了。我正在尝试编写一个Swift函数,它接受硬件生成的键盘扫描代码,例如来自NSEvent,并返回密钥的alpha-caps-locked名称,用于特定的键排列(Dvorak,Qwerty等)。 )当前在OS中有效(可能与生成代码时生效的安排不同)。
我的理解是,这样做的唯一方法就是调用一些非常古老的Carbon功能,避开Swift的极端类型安全性,这是我感觉不舒服的事情。这是The Show So Far:
import Cocoa
import Carbon
func keyName (scanCode: UInt16) -> String?
{ let maxNameLength = 4, modifierKeys: UInt32 = 0x00000004 // Caps Lock (Carbon Era)
let deadKeys = UnsafeMutablePointer<UInt32>(bitPattern: 0x00000000),
nameBuffer = UnsafeMutablePointer<UniChar>.alloc(maxNameLength),
nameLength = UnsafeMutablePointer<Int>.alloc(1),
keyboardType = UInt32(LMGetKbdType())
let source = TISGetInputSourceProperty ( TISCopyCurrentKeyboardLayoutInputSource()
.takeRetainedValue(),
kTISPropertyUnicodeKeyLayoutData )
let dataRef = unsafeBitCast(source, CFDataRef.self)
let dataBuffer = CFDataGetBytePtr(dataRef)
let keyboardLayout = unsafeBitCast(dataBuffer, UnsafePointer <UCKeyboardLayout>.self)
let osStatus = UCKeyTranslate (keyboardLayout, scanCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
deadKeys, maxNameLength, nameLength, nameBuffer)
switch osStatus
{ case 0: return NSString (characters: nameBuffer, length: nameLength[0]) as String
default: NSLog (“Code: 0x%04X Status: %+i", scanCode, osStatus); return nil }
}
它不会崩溃,在这一点上我几乎认为游戏成就本身,但它也不起作用。 UCKeyTranslate总是返回-50的状态,我理解这意味着有一个参数错误。我怀疑“keyboardLayout”,因为它是最复杂的设置。谁能看到参数问题?或者是否有更新的框架来处理这类事情?
正如您已经发现的那样,您必须将UInt32
变量的地址作为deadKeyState
参数传递。分配内存是解决该问题的一种方法,但是你最好不要忘记释放内存,否则程序会泄漏内存。
另一种可能的解决方案是使用&
将变量的地址作为inout-argument传递:
var deadKeys : UInt32 = 0
// ...
let osStatus = UCKeyTranslate(..., &deadKeys, ...)
这样更短更简单,您无需释放内存。这同样适用于nameBuffer
和nameLength
。
使用unsafeBitCast()
类型可以避免Unmanaged
,比较Swift: CFArray : get values as UTF Strings类似的问题和更详细的解释。
您还可以利用CFData
和NSData
之间的免费桥接。
然后你的函数看起来像这样(Swift 2):
import Carbon
func keyName(scanCode: UInt16) -> String?
{
let maxNameLength = 4
var nameBuffer = [UniChar](count : maxNameLength, repeatedValue: 0)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys : UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)
let layoutData = Unmanaged<CFData>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue() as NSData
let keyboardLayout = UnsafePointer<UCKeyboardLayout>(layoutData.bytes)
let osStatus = UCKeyTranslate(keyboardLayout, scanCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", scanCode, osStatus);
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
Swift 3更新:
import Carbon
func keyName(scanCode: UInt16) -> String? {
let maxNameLength = 4
var nameBuffer = [UniChar](repeating: 0, count : maxNameLength)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys: UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
NSLog("Could not get keyboard layout data")
return nil
}
let layoutData = Unmanaged<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data
let osStatus = layoutData.withUnsafeBytes {
UCKeyTranslate($0, scanCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
}
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", scanCode, osStatus);
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
好的,我相信我发现了问题。虽然回答我自己的问题很奇怪,但我知道在这样的情况下这是正确的做法。
违规参数似乎已经死了。在我关注的模型代码中,这被定义为一个位模式。虽然据说是指向可变的东西的指针,但我不确定它是不是真的那样,因为当我决定重新定义它以匹配UCKeyTranslate的其他两个call-by-reference参数时,一切都开始完美。解决方案是执行显式.alloc,然后显式调零引用值。这是我的功能更新:
func keyName ( scanCode: UInt16 ) -> String?
{ let maxNameLength = 4, modifierKeys: UInt32 = 0x00000004, // Caps Lock (Carbon Era Mask)
nameBuffer = UnsafeMutablePointer <UniChar> .alloc (maxNameLength),
nameLength = UnsafeMutablePointer <Int> .alloc (1),
deadKeys = UnsafeMutablePointer <UInt32> .alloc (1); deadKeys[0] = 0x00000000
let source = TISGetInputSourceProperty ( TISCopyCurrentKeyboardLayoutInputSource()
.takeRetainedValue(),
kTISPropertyUnicodeKeyLayoutData ),
keyboardLayout = unsafeBitCast ( CFDataGetBytePtr (unsafeBitCast (source, CFDataRef.self)),
UnsafePointer <UCKeyboardLayout>.self),
keyboardType = UInt32 (LMGetKbdType())
let osStatus = UCKeyTranslate (keyboardLayout, scanCode, UInt16 (kUCKeyActionDown),
modifierKeys, keyboardType, UInt32 (kUCKeyTranslateNoDeadKeysMask),
deadKeys, maxNameLength, nameLength, nameBuffer)
switch osStatus
{ case 0: return String.init (utf16CodeUnits: nameBuffer, count: nameLength[0])
default: NSLog ("Code: 0x%04X Status: %+i", scanCode, osStatus); return nil }
}
还有一些其他的变化,几乎是装饰性的:我消除了几个导致keyboardLayout定义的中间常量。 (“BitCasts”只是为了满足Swiftian类型的安全性:它们并没有真正做我能看到的任何其他事情。)但真正的问题是deadKeys的原始定义。我希望这对某些人有用,至少在有非碳替代方案之前。 (会发生这种情况吗?)