362 lines
17 KiB
Swift
362 lines
17 KiB
Swift
//
|
|
// 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: 0x5c, up: 0xdc)),
|
|
kVK_JIS_Eisu: .t((down: 0x73, up: 0xf3)),
|
|
kVK_JIS_Kana: .t((down: 0x70, up: 0xf0)),
|
|
/* 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
|
|
}
|
|
}
|