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

标签 swift keyboard-events macos-carbon

我知道其他人也问过类似的问题,但我还没有看到明确的答案,我仍然陷入困境。我正在尝试编写一个 Swift 函数,它接受硬件生成的键盘扫描代码(例如来自 NSEvent),并返回该键的字母大写锁定名称,用于特定的按键排列(Dvorak、Qwerty 等)。 )当前在操作系统中有效(可能与生成代码时有效的安排不同)。

据我所知,做到这一点的唯一方法是调用一些非常古老的 Carbon 函数,绕过许多 Swift 的极端类型安全性,这是我觉得不舒服的做法。这是到目前为止的节目:

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 参数:

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

这有点短和简单,并且您不需要释放 内存。同样的情况也适用于 nameBuffernameLength

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

您还可以利用免费电话桥接服务 CFDataNSData

那么你的函数可能如下所示(Swift 2):

import Carbon

func keyName(virtualKeyCode: 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, virtualKeyCode, UInt16(kUCKeyActionDown),
        modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
        &deadKeys, maxNameLength, &nameLength, &nameBuffer)
    guard osStatus == noErr else {
        NSLog("Code: 0x%04X  Status: %+i", virtualKeyCode, osStatus);
        return nil
    }
    
    return  String(utf16CodeUnits: nameBuffer, count: nameLength)
}
<小时/>

Swift 3 更新:

import Carbon

func keyName(virtualKeyCode: 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, virtualKeyCode, UInt16(kUCKeyActionDown),
                       modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
                       &deadKeys, maxNameLength, &nameLength, &nameBuffer)
    }
    guard osStatus == noErr else {
        NSLog("Code: 0x%04X  Status: %+i", virtualKeyCode, osStatus);
        return nil
    }
    
    return  String(utf16CodeUnits: nameBuffer, count: nameLength)
}
<小时/>

Swift 4 更新:

从 Swift 4 开始,Data.withUnsafeBytes 使用 UnsafeRawBufferPointer 调用闭包,该闭包必须绑定(bind)指向 UCKeyboardLayout 的指针:

import Carbon

func keyName(virtualKeyCode: 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.bindMemory(to: UCKeyboardLayout.self).baseAddress, virtualKeyCode, UInt16(kUCKeyActionDown),
                       modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
                       &deadKeys, maxNameLength, &nameLength, &nameBuffer)
    }
    guard osStatus == noErr else {
        NSLog("Code: 0x%04X  Status: %+i", virtualKeyCode, osStatus);
        return nil
    }

    return  String(utf16CodeUnits: nameBuffer, count: nameLength)
}

关于swift - 在 Swift 中获取键盘代码的键名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58578125/

相关文章:

ios - 我应该做什么来设置 UICollectionView 的约束

javascript - 在 JavaScript 中模拟输入

swift - Swift 中的全局修改键检测

cocoa - 如何在 OS X 上使用 Cocoa 或 Carbon 截取单个窗口的屏幕截图?

ios - 进入后台状态时防止websocket连接断开ios swift

swift - 从没有 switch/case 的枚举中获取关联值

ios - 从 UITableView 转到 UITabBarController

linux - 在没有管理权限的情况下覆盖 linux 中的 "Print Screen"操作

javascript - Google 键盘剪贴板不会触发粘贴事件

cocoa - 粘贴板管理器引用