Getting Key Names for Key Codes in Swift

I know others asked similar questions, but I did not see the final answer, and I was still stuck. I am trying to write a Swift function that uses a keyboard scan code, for example, from NSEvent, and returns the key name locked with a letter for a specific key combination (Dvorak, Qwerty, etc.) Currently in reality in the OS (which may differ from what was created when creating the code).

In my understanding, the only way to do this is to call some very old Carbon functions, bypassing the many ultra-fast Swifts security, something I don't like. 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", . - ? ?

+4
2

, UInt32 deadKeyState. - , , .

- inout-argument &:

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

, . nameBuffer nameLength.

unsafeBitCast() , Unmanaged 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)
}
+6

, , . , , , , .

"", , deadKeys. , , -. , , - , , , , , - UCKeyTranslate, . .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. ( "" Swiftian: , .) deadKeys. , -, , , Carbon. ( ?)

+1

Source: https://habr.com/ru/post/1626719/


All Articles