在Swift中获取键盘代码的键名(Getting key names for keyboard codes in Swift)

编程入门 行业动态 更新时间:2024-10-28 17:17:38
在Swift中获取键盘代码的键名(Getting key names for keyboard codes in 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 } }

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

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).

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 } }

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参数deadKeyState 。 分配内存是解决该问题的一种方法,但是你最好不要忘记释放内存,否则程序会泄漏内存。

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

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

这样更短更简单,您无需释放内存。 同样可以应用于nameBuffer和nameLength 。

使用Unmanaged类型可以避免使用unsafeBitCast() ,比较Swift:CFArray:获取类似问题的UTF字符串值和更详细的解释。

您还可以利用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) }

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.

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

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

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.

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.

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

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) }

Update for 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) }

更多推荐

本文发布于:2023-08-04 05:29:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1411769.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:键盘   代码   键名   Swift   codes

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!