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

Jef*_*f J 5 macos-carbon keyboard-events swift

我知道其他人也问了类似的问题,但我还没有看到明确的答案,我仍然被困住了.我正在尝试编写一个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   }
  }
Run Code Online (Sandbox Code Playgroud)

它不会崩溃,在这一点上我几乎认为游戏成就本身,但它也不起作用.UCKeyTranslate总是返回-50的状态,我理解这意味着有一个参数错误.我怀疑"keyboardLayout",因为它是最复杂的设置.谁能看到参数问题?或者是否有更新的框架来处理这类事情?

Mar*_*n R 9

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

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

var deadKeys : UInt32 = 0
// ...
let osStatus = UCKeyTranslate(..., &deadKeys, ...)
Run Code Online (Sandbox Code Playgroud)

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

unsafeBitCast()可以通过使用来避免Unmanaged类型,比较斯威夫特:CFArray:获得价值为UTF字符串了类似的问题,更详细的解释.

您还可以利用的免费桥接的优势 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)
}
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)