在Swift中获取键盘代码的键名

问题描述 投票:5回答:2

我知道其他人也问了类似的问题,但我还没有看到明确的答案,我仍然被困住了。我正在尝试编写一个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”,因为它是最复杂的设置。谁能看到参数问题?或者是否有更新的框架来处理这类事情?

swift keyboard-events macos-carbon
2个回答
9
投票

正如您已经发现的那样,您必须将UInt32变量的地址作为deadKeyState参数传递。分配内存是解决该问题的一种方法,但是你最好不要忘记释放内存,否则程序会泄漏内存。

另一种可能的解决方案是使用&将变量的地址作为inout-argument传递:

var deadKeys : UInt32 = 0
// ...
let osStatus = UCKeyTranslate(..., &deadKeys, ...)

这样更短更简单,您无需释放内存。这同样适用于nameBuffernameLength

使用unsafeBitCast()类型可以避免Unmanaged,比较Swift: CFArray : get values as UTF Strings类似的问题和更详细的解释。

您还可以利用CFDataNSData之间的免费桥接。

然后你的函数看起来像这样(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)
}

1
投票

好的,我相信我发现了问题。虽然回答我自己的问题很奇怪,但我知道在这样的情况下这是正确的做法。

违规参数似乎已经死了。在我关注的模型代码中,这被定义为一个位模式。虽然据说是指向可变的东西的指针,但我不确定它是不是真的那样,因为当我决定重新定义它以匹配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的原始定义。我希望这对某些人有用,至少在有非碳替代方案之前。 (会发生这种情况吗?)

© www.soinside.com 2019 - 2024. All rights reserved.