123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- //
- // Copyright © 2021 osy. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- import Foundation
- import Carbon.HIToolbox
- /// Based on https://stackoverflow.com/a/64344453/4236245
- /// Translated to Swift by conath
- class KeyCodeMap {
- private static var keyMapDict: Dictionary<String, Dictionary<String, Int>>!
- private static var modFlagDict: Dictionary<String, UInt>!
- private static var modFlags: [UInt]!
-
- /// Creates the internal key map if needed. Must be called on the main queue!
- static func createKeyMapIfNeeded() {
- if keyMapDict == nil {
- keyMapDict = makeKeyMap()
- }
- }
-
- static func characterToKeyCode(character: Character) -> Dictionary<String, Int>? {
- createKeyMapIfNeeded()
-
- /*
- The returned dictionary contains entries for the virtual key code and boolean flags
- for modifier keys used for the character.
- */
- if let keyCodeDict = keyMapDict[String(character)] {
- return keyCodeDict
- } else {
- return tryHandleSpecialChar(character)
- }
- }
-
- private static func makeKeyMap() -> Dictionary<String, Dictionary<String, Int>> {
- var modifiers: UInt = 0
-
- // create dictionary of modifier names and keys.
- if (modFlagDict == nil) {
- modFlagDict = ["option": NSEvent.ModifierFlags.option.rawValue,
- "shift": NSEvent.ModifierFlags.shift.rawValue,
- "function": NSEvent.ModifierFlags.function.rawValue,
- "control": NSEvent.ModifierFlags.control.rawValue,
- "command": NSEvent.ModifierFlags.command.rawValue]
- modFlags = Array(modFlagDict.values)
- }
- var keyMapDict = Dictionary<String, Dictionary<String, Int>>()
-
- // run through 128 base key codes to see what they produce
- for keyCode: UInt16 in 0..<128 {
- // create dummy NSEvent from a CGEvent for a keypress
- let coreEvent = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)!
- let keyEvent = NSEvent(cgEvent: coreEvent)!
-
- if (keyEvent.type == .keyDown) {
- // this repeat/while loop through every permutation of modifier keys for a given key code
- repeat {
- var subDict = Dictionary<String, Int>()
- // cerate dictionary containing current modifier keys and virtual key code
- for key: String in modFlagDict.keys {
- let modKeyIsUsed = ((modFlagDict[key]! & modifiers) != 0)
- subDict[key] = NSNumber(booleanLiteral: modKeyIsUsed).intValue
- }
- subDict["virtKeyCode"] = (keyCode as NSNumber).intValue
-
- // manipulate the NSEvent to get character produce by virtual key code and modifiers
- var character: String
- if modifiers == 0 {
- character = keyEvent.characters!
- } else {
- character = keyEvent.characters(byApplyingModifiers: NSEvent.ModifierFlags(rawValue: modifiers))!
- }
-
- // add sub-dictionary to main dictionary using character as key
- if keyMapDict[character] == nil {
- keyMapDict[character] = subDict
- }
-
- // permutate the modifiers
- modifiers = permutatateMods(modFlags: modFlags)
- } while (modifiers != 0)
- }
- }
-
- return keyMapDict
- }
-
- private static let idxSet = NSMutableIndexSet()
-
- private static func permutatateMods(modFlags: [UInt]) -> UInt {
- var modifiers: UInt = 0
- var idx: Int = 0
-
- /*
- Starting at 0, if the index exists, remove it and move up; if the index doesn't exist, add it. Will
- cycle through a standard binary progression. Indexes are then applied to the passed array, and the
- selected elements are 'OR'ed together
- */
- var done = false
- while !done {
- if idxSet.contains([idx]) {
- idxSet.remove([idx])
- idx += 1
- continue;
- }
- if idx < modFlags.count {
- idxSet.add([idx])
- } else {
- idxSet.removeAllIndexes()
- }
- done = true
- }
-
- let modArray = (modFlags as NSArray).objects(at: idxSet as IndexSet) as NSArray
-
- for modObj in modArray {
- modifiers |= (modObj as! NSNumber).uintValue
- }
-
- return modifiers
- }
-
- /// Keyboard scan code for key down and up (which is usually `down + 0x80`)
- struct ScanCodes {
- let down: UInt16
- let up: UInt8
-
- /// Construct a `ScanCodes` from a tuple of `Int`s
- static func t(_ tuple: (down: UInt16, up: UInt8)) -> ScanCodes {
- return ScanCodes(down: tuple.down, up: tuple.up)
- }
- }
-
- // Key Scan Codes mapping from https://www.cs.yale.edu/flint/cs422/doc/art-of-asm/pdf/CH20.PDF
- // Page 1154, Table 72: PC Keyboard Scan Codes (in hex)
- /// Converts macOS key code to IBM scan code for key up and down
- /// The "up" scan codes are currently unused in UTM due to SPICE:
- /// we instead send keyUp with the "down" scan code.
- /// (See also `CSInput.sendKey:type:code`)
- static let keyCodeToScanCodes: [Int:ScanCodes] = [
- kVK_Escape: .t((down: 0x01, up: 0x81)),
- kVK_ANSI_1: .t((down: 0x02, up: 0x82)),
- kVK_ANSI_2: .t((down: 0x03, up: 0x83)),
- kVK_ANSI_3: .t((down: 0x04, up: 0x84)),
- kVK_ANSI_4: .t((down: 0x05, up: 0x85)),
- kVK_ANSI_5: .t((down: 0x06, up: 0x86)),
- kVK_ANSI_6: .t((down: 0x07, up: 0x87)),
- kVK_ANSI_7: .t((down: 0x08, up: 0x88)),
- kVK_ANSI_8: .t((down: 0x09, up: 0x89)),
- kVK_ANSI_9: .t((down: 0x0a, up: 0x8a)),
- kVK_ANSI_0: .t((down: 0x0b, up: 0x8b)),
- kVK_ANSI_Minus: .t((down: 0x0c, up: 0x8c)),
- kVK_ANSI_Equal: .t((down: 0x0d, up: 0x8d)),
- kVK_Delete: .t((down: 0x0e, up: 0x8e)), /// IBM name is `backspace`
- kVK_Tab: .t((down: 0x0f, up: 0x8f)),
- kVK_ANSI_Q: .t((down: 0x10, up: 0x90)),
- kVK_ANSI_W: .t((down: 0x11, up: 0x91)),
- kVK_ANSI_E: .t((down: 0x12, up: 0x92)),
- kVK_ANSI_R: .t((down: 0x13, up: 0x93)),
- kVK_ANSI_T: .t((down: 0x14, up: 0x94)),
- kVK_ANSI_Y: .t((down: 0x15, up: 0x95)),
- kVK_ANSI_U: .t((down: 0x16, up: 0x96)),
- kVK_ANSI_I: .t((down: 0x17, up: 0x97)),
- kVK_ANSI_O: .t((down: 0x18, up: 0x98)),
- kVK_ANSI_P: .t((down: 0x19, up: 0x99)),
- kVK_ANSI_LeftBracket: .t((down: 0x1a, up: 0x9a)),
- kVK_ANSI_RightBracket: .t((down: 0x1b, up: 0x9b)),
- kVK_Return: .t((down: 0x1c, up: 0x9c)), /// IBM name is `enter`
- kVK_Control: .t((down: 0x1d, up: 0x9d)),
- kVK_ANSI_A: .t((down: 0x1e, up: 0x9e)),
- kVK_ANSI_S: .t((down: 0x1f, up: 0x9f)),
- kVK_ANSI_D: .t((down: 0x20, up: 0xa0)),
- kVK_ANSI_F: .t((down: 0x21, up: 0xa1)),
- kVK_ANSI_G: .t((down: 0x22, up: 0xa2)),
- kVK_ANSI_H: .t((down: 0x23, up: 0xa3)),
- kVK_ANSI_J: .t((down: 0x24, up: 0xa4)),
- kVK_ANSI_K: .t((down: 0x25, up: 0xa5)),
- kVK_ANSI_L: .t((down: 0x26, up: 0xa6)),
- kVK_ANSI_Semicolon: .t((down: 0x27, up: 0xa7)),
- kVK_ANSI_Quote: .t((down: 0x28, up: 0xa8)),
- kVK_ANSI_Grave: .t((down: 0x29, up: 0xa9)),
- kVK_Shift: .t((down: 0x2a, up: 0xaa)),
- kVK_ANSI_Backslash: .t((down: 0x2b, up: 0xab)),
- kVK_ANSI_Z: .t((down: 0x2c, up: 0xac)),
- kVK_ANSI_X: .t((down: 0x2d, up: 0xad)),
- kVK_ANSI_C: .t((down: 0x2e, up: 0xae)),
- kVK_ANSI_V: .t((down: 0x2f, up: 0xaf)),
- kVK_ANSI_B: .t((down: 0x30, up: 0xb0)),
- kVK_ANSI_N: .t((down: 0x31, up: 0xb1)),
- kVK_ANSI_M: .t((down: 0x32, up: 0xb2)),
- kVK_ANSI_Comma: .t((down: 0x33, up: 0xb3)),
- kVK_ANSI_Period: .t((down: 0x34, up: 0xb4)),
- kVK_ANSI_Slash: .t((down: 0x35, up: 0xb5)),
- kVK_RightShift: .t((down: 0x36, up: 0xb6)),
- // Print screen not available in Carbon
- kVK_Option: .t((down: 0x38, up: 0xb8)), /// IBM name is `alt`
- kVK_Space: .t((down: 0x39, up: 0xb9)),
- kVK_CapsLock: .t((down: 0x3a, up: 0xba)),
- kVK_F1: .t((down: 0x3b, up: 0xbb)),
- kVK_F2: .t((down: 0x3c, up: 0xbc)),
- kVK_F3: .t((down: 0x3d, up: 0xbd)),
- kVK_F4: .t((down: 0x3e, up: 0xbe)),
- kVK_F5: .t((down: 0x3f, up: 0xbf)),
- kVK_F6: .t((down: 0x40, up: 0xc0)),
- kVK_F7: .t((down: 0x41, up: 0xc1)),
- kVK_F8: .t((down: 0x42, up: 0xc2)),
- kVK_F9: .t((down: 0x43, up: 0xc3)),
- kVK_F10: .t((down: 0x44, up: 0xc4)),
- // Numlock not available in Carbon
- // Scroll lock not available in Carbon
- // Number pad Home, up, pgUp not available in Carbon
- kVK_ANSI_KeypadMinus: .t((down: 0x4a, up: 0xca)),
- // Number pad left, center, right not available in Carbon
- kVK_ANSI_KeypadPlus: .t((down: 0x4e, up: 0xce)),
- // Number pad end, down, pgDown, insert not available in Carbon
- kVK_ANSI_KeypadClear: .t((down: 0x45, up: 0xC5)), /// in IBM this is num lock, so we send that
- kVK_ANSI_KeypadDivide: .t((down: 0xe035, up: 0xb5)),
- kVK_ANSI_KeypadEnter: .t((down: 0xe01c, up: 0x9c)),
- kVK_ANSI_Keypad0: .t((down: 0x52, up: 0xD2)),
- kVK_ANSI_Keypad1: .t((down: 0x4F, up: 0xCF)),
- kVK_ANSI_Keypad2: .t((down: 0x50, up: 0xD0)),
- kVK_ANSI_Keypad3: .t((down: 0x51, up: 0xD1)),
- kVK_ANSI_Keypad4: .t((down: 0x4B, up: 0xCB)),
- kVK_ANSI_Keypad5: .t((down: 0x4C, up: 0xCC)),
- kVK_ANSI_Keypad6: .t((down: 0x4D, up: 0xCD)),
- kVK_ANSI_Keypad7: .t((down: 0x47, up: 0xC7)),
- kVK_ANSI_Keypad8: .t((down: 0x48, up: 0xC8)),
- kVK_ANSI_Keypad9: .t((down: 0x49, up: 0xC9)),
- kVK_ANSI_KeypadDecimal: .t((down: 0x53, up: 0xD3)),
- kVK_ANSI_KeypadEquals: .t((down: 0x00, up: 0x00)), /// Not found on IBM
- kVK_ANSI_KeypadMultiply:.t((down: 0x37, up: 0xB7)),
- kVK_F11: .t((down: 0x57, up: 0xd7)),
- kVK_F12: .t((down: 0x58, up: 0xd8)),
- // Insert not available in Carbon
- kVK_ForwardDelete: .t((down: 0xe053, up: 0xd3)), /// IBM name is `delete`
- kVK_Home: .t((down: 0xe047, up: 0xc7)),
- kVK_End: .t((down: 0xe04f, up: 0xcf)),
- kVK_PageUp: .t((down: 0xe049, up: 0xc9)),
- kVK_PageDown: .t((down: 0xe051, up: 0xd1)),
- kVK_LeftArrow: .t((down: 0xe04b, up: 0xcb)),
- kVK_RightArrow: .t((down: 0xe04d, up: 0xcd)),
- kVK_UpArrow: .t((down: 0xe048, up: 0xc8)),
- kVK_DownArrow: .t((down: 0xe050, up: 0xd0)),
- kVK_RightOption: .t((down: 0xe038, up: 0xb8)), /// IBM name is `right alt`
- kVK_RightControl: .t((down: 0xe01d, up: 0x9d)),
- // Pause not available in Carbon
- /* Additional non-IBM keys */
- kVK_Command: .t((down: 0xe05b, up: 0xdb)),
- kVK_RightCommand: .t((down: 0xe05c, up: 0xdc)),
- kVK_ISO_Section: .t((down: 0x56, up: 0xD6)),
- kVK_VolumeUp: .t((down: 0xe030, up: 0xb0)),
- kVK_VolumeDown: .t((down: 0xe02e, up: 0xae)),
- kVK_Mute: .t((down: 0xE020, up: 0xa0)),
- kVK_F13: .t((down: 0x64, up: 0xe4)),
- kVK_F14: .t((down: 0x65, up: 0xe5)),
- kVK_F15: .t((down: 0x66, up: 0xe6)),
- kVK_F16: .t((down: 0x67, up: 0xe7)),
- kVK_F17: .t((down: 0x68, up: 0xe8)),
- kVK_F18: .t((down: 0x69, up: 0xe9)),
- kVK_F19: .t((down: 0x6a, up: 0xea)),
- kVK_F20: .t((down: 0x6b, up: 0xeb)),
- kVK_JIS_Yen: .t((down: 0x7d, up: 0xfd)),
- kVK_JIS_Underscore: .t((down: 0x73, up: 0xf3)),
- kVK_JIS_KeypadComma: .t((down: 0x00, up: 0x00)), /// Not found on IBM
- kVK_JIS_Eisu: .t((down: 0x7b, up: 0xfb)), /// Fallback to `muhenkan`
- kVK_JIS_Kana: .t((down: 0x79, up: 0xf9)), /// Fallback to `henkan`
- /* The Function and help keys doesn't have a scan code */
- kVK_Function: .t((down: 0x00, up: 0x00)),
- kVK_Help: .t((down: 0x00, up: 0x00))
- ]
- }
- extension KeyCodeMap {
- /// Support ASCII control characters
- /// https://jkorpela.fi/chars/c0.html
- fileprivate static func tryHandleSpecialChar(_ character: Character) -> Dictionary<String, Int>? {
- if let ascii = character.asciiValue {
- var virtKeyCode: Int?
- if ascii <= 31 {
- /// Control held
- switch ascii {
- case 1: virtKeyCode = kVK_ANSI_A
- case 2: virtKeyCode = kVK_ANSI_B
- case 3: virtKeyCode = kVK_ANSI_C
- case 4: virtKeyCode = kVK_ANSI_D
- case 5: virtKeyCode = kVK_ANSI_E
- case 6: virtKeyCode = kVK_ANSI_F
- case 7: virtKeyCode = kVK_ANSI_G
- case 8: virtKeyCode = kVK_ANSI_H
- case 9: virtKeyCode = kVK_ANSI_I
- case 10: virtKeyCode = kVK_ANSI_J
- case 11: virtKeyCode = kVK_ANSI_K
- case 12: virtKeyCode = kVK_ANSI_L
- case 13: virtKeyCode = kVK_ANSI_M
- case 14: virtKeyCode = kVK_ANSI_N
- case 15: virtKeyCode = kVK_ANSI_O
- case 16: virtKeyCode = kVK_ANSI_P
- case 17: virtKeyCode = kVK_ANSI_Q
- case 18: virtKeyCode = kVK_ANSI_R
- case 19: virtKeyCode = kVK_ANSI_S
- case 20: virtKeyCode = kVK_ANSI_T
- case 21: virtKeyCode = kVK_ANSI_U
- case 22: virtKeyCode = kVK_ANSI_V
- case 23: virtKeyCode = kVK_ANSI_W
- case 24: virtKeyCode = kVK_ANSI_Y
- case 25: virtKeyCode = kVK_ANSI_X
- case 26: virtKeyCode = kVK_ANSI_Z
- case 27: virtKeyCode = kVK_ANSI_LeftBracket
- case 28: virtKeyCode = kVK_ANSI_Backslash
- case 29: virtKeyCode = kVK_ANSI_RightBracket
- case 30:
- if var dict = characterToKeyCode(character: "^") {
- dict["control"] = 1
- return dict
- } else { return nil }
- case 31:
- if var dict = characterToKeyCode(character: "_") {
- dict["control"] = 1
- return dict
- } else { return nil }
- default:
- virtKeyCode = nil
- }
- if let virtKeyCode = virtKeyCode {
- return [
- "option": 0,
- "shift": 0,
- "function": 0,
- "control": 1,
- "command": 0,
- "virtKeyCode": virtKeyCode
- ]
- }
- } else if ascii == 127 {
- /// Delete key
- return [
- "option": 0,
- "shift": 0,
- "function": 0,
- "control": 1,
- "command": 0,
- "virtKeyCode": kVK_Delete
- ]
- }
- }
- return nil
- }
- }
|