在Swift中获取键盘代码的键名 [英] Getting key names for keyboard codes in Swift

查看:104
本文介绍了在Swift中获取键盘代码的键名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道其他人也曾问过类似的问题,但是我没有找到确切的答案,但我仍然很困惑.我正在尝试编写一个Swift函数,该函数接受硬件生成的键盘扫描代码(例如来自NSEvent的代码),并针对特定的键排列(Dvorak,Qwerty等)返回键的Alpha大小写锁定名称. )目前在操作系统中有效(可能与生成代码时有效的安排不同).

I know others have asked similar questions, but I haven’t seen a definitive answer, and I’m still stuck. I’m trying to write a Swift function that takes a hardware-generated keyboard scan code, such as from an NSEvent, and returns the alpha-caps-locked name of the key, for the particular key arrangement (Dvorak, Qwerty, etc.) currently in effect in the OS (which might be different from the arrangement in effect when the code was generated).

据我了解,唯一的方法就是调用一些非常古老的Carbon函数,从而避开了许多Swift的极端类型安全性,这让我感到不舒服.这是到目前为止的演出:

It’s my understanding that the only way to do this is to invoke some very old Carbon functions, skirting a lot of the Swift’s extreme type-safety, something I don’t feel comfortable doing. Here is 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"是最复杂的设置.谁能看到参数问题?还是针对此类问题有更新的框架?

It doesn’t crash, which at this point I almost consider a game achievement in itself, but neither does it work. UCKeyTranslate always returns a status of -50, which I understand means there’s a parameter wrong. I suspect "keyboardLayout," as it is the most complicated to set up. Can anyone see the parameter problem? Or is there a more up-to-date framework for this sort of thing?

推荐答案

您已经发现,必须传递UInt32地址 变量作为deadKeyState参数.分配内存是一个 解决问题的方法,但您一定不要忘记释放内存 最终,否则程序将泄漏内存.

As you already found out, you have to pass the address of a UInt32 variable as the deadKeyState argument. Allocating memory is one way to solve that problem, but you must not forget to free the memory eventually, otherwise the program will leak memory.

另一种可能的解决方案是将变量的地址传递为 &的inout参数:

Another possible solution is to pass the address of a variable as an inout-argument with &:

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

这有点短和简单,您不需要释放 记忆.相同的内容可以应用于nameBuffernameLength.

This is a bit shorter and simpler, and you don't need to release the memory. The same can be applied to nameBuffer and nameLength.

通过使用Unmanaged类型可以避免使用unsafeBitCast(), 比较 Swift:CFArray:将值作为UTF字符串获取对于类似的问题, 更详细的解释.

The unsafeBitCast() can be avoided by using the Unmanaged type, compare Swift: CFArray : get values as UTF Strings for a similar problem and more detailed explanations.

您还可以利用免费电话之间的免费桥接 CFDataNSData.

Also you can take advantage of the toll-free bridging between CFData and NSData.

然后您的功能应如下所示( Swift 2 ):

Then your function could look like this (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)
}


针对Swift 4的更新:

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

As of Swift 4, Data.withUnsafeBytes calls the closure with a UnsafeRawBufferPointer which has to be bound a pointer to UCKeyboardLayout:

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.bindMemory(to: UCKeyboardLayout.self).baseAddress, 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中获取键盘代码的键名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆