2
0

KeyCodeMap.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. //
  2. // Copyright © 2021 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. import Carbon.HIToolbox
  18. /// Based on https://stackoverflow.com/a/64344453/4236245
  19. /// Translated to Swift by conath
  20. class KeyCodeMap {
  21. private static var keyMapDict: Dictionary<String, Dictionary<String, Int>>!
  22. private static var modFlagDict: Dictionary<String, UInt>!
  23. private static var modFlags: [UInt]!
  24. /// Creates the internal key map if needed. Must be called on the main queue!
  25. static func createKeyMapIfNeeded() {
  26. if keyMapDict == nil {
  27. keyMapDict = makeKeyMap()
  28. }
  29. }
  30. static func characterToKeyCode(character: Character) -> Dictionary<String, Int>? {
  31. createKeyMapIfNeeded()
  32. /*
  33. The returned dictionary contains entries for the virtual key code and boolean flags
  34. for modifier keys used for the character.
  35. */
  36. if let keyCodeDict = keyMapDict[String(character)] {
  37. return keyCodeDict
  38. } else {
  39. return tryHandleSpecialChar(character)
  40. }
  41. }
  42. private static func makeKeyMap() -> Dictionary<String, Dictionary<String, Int>> {
  43. var modifiers: UInt = 0
  44. // create dictionary of modifier names and keys.
  45. if (modFlagDict == nil) {
  46. modFlagDict = ["option": NSEvent.ModifierFlags.option.rawValue,
  47. "shift": NSEvent.ModifierFlags.shift.rawValue,
  48. "function": NSEvent.ModifierFlags.function.rawValue,
  49. "control": NSEvent.ModifierFlags.control.rawValue,
  50. "command": NSEvent.ModifierFlags.command.rawValue]
  51. modFlags = Array(modFlagDict.values)
  52. }
  53. var keyMapDict = Dictionary<String, Dictionary<String, Int>>()
  54. // run through 128 base key codes to see what they produce
  55. for keyCode: UInt16 in 0..<128 {
  56. // create dummy NSEvent from a CGEvent for a keypress
  57. let coreEvent = CGEvent(keyboardEventSource: nil, virtualKey: keyCode, keyDown: true)!
  58. let keyEvent = NSEvent(cgEvent: coreEvent)!
  59. if (keyEvent.type == .keyDown) {
  60. // this repeat/while loop through every permutation of modifier keys for a given key code
  61. repeat {
  62. var subDict = Dictionary<String, Int>()
  63. // cerate dictionary containing current modifier keys and virtual key code
  64. for key: String in modFlagDict.keys {
  65. let modKeyIsUsed = ((modFlagDict[key]! & modifiers) != 0)
  66. subDict[key] = NSNumber(booleanLiteral: modKeyIsUsed).intValue
  67. }
  68. subDict["virtKeyCode"] = (keyCode as NSNumber).intValue
  69. // manipulate the NSEvent to get character produce by virtual key code and modifiers
  70. var character: String
  71. if modifiers == 0 {
  72. character = keyEvent.characters!
  73. } else {
  74. character = keyEvent.characters(byApplyingModifiers: NSEvent.ModifierFlags(rawValue: modifiers))!
  75. }
  76. // add sub-dictionary to main dictionary using character as key
  77. if keyMapDict[character] == nil {
  78. keyMapDict[character] = subDict
  79. }
  80. // permutate the modifiers
  81. modifiers = permutatateMods(modFlags: modFlags)
  82. } while (modifiers != 0)
  83. }
  84. }
  85. return keyMapDict
  86. }
  87. private static let idxSet = NSMutableIndexSet()
  88. private static func permutatateMods(modFlags: [UInt]) -> UInt {
  89. var modifiers: UInt = 0
  90. var idx: Int = 0
  91. /*
  92. Starting at 0, if the index exists, remove it and move up; if the index doesn't exist, add it. Will
  93. cycle through a standard binary progression. Indexes are then applied to the passed array, and the
  94. selected elements are 'OR'ed together
  95. */
  96. var done = false
  97. while !done {
  98. if idxSet.contains([idx]) {
  99. idxSet.remove([idx])
  100. idx += 1
  101. continue;
  102. }
  103. if idx < modFlags.count {
  104. idxSet.add([idx])
  105. } else {
  106. idxSet.removeAllIndexes()
  107. }
  108. done = true
  109. }
  110. let modArray = (modFlags as NSArray).objects(at: idxSet as IndexSet) as NSArray
  111. for modObj in modArray {
  112. modifiers |= (modObj as! NSNumber).uintValue
  113. }
  114. return modifiers
  115. }
  116. /// Keyboard scan code for key down and up (which is usually `down + 0x80`)
  117. struct ScanCodes {
  118. let down: UInt16
  119. let up: UInt8
  120. /// Construct a `ScanCodes` from a tuple of `Int`s
  121. static func t(_ tuple: (down: UInt16, up: UInt8)) -> ScanCodes {
  122. return ScanCodes(down: tuple.down, up: tuple.up)
  123. }
  124. }
  125. // Key Scan Codes mapping from https://www.cs.yale.edu/flint/cs422/doc/art-of-asm/pdf/CH20.PDF
  126. // Page 1154, Table 72: PC Keyboard Scan Codes (in hex)
  127. /// Converts macOS key code to IBM scan code for key up and down
  128. /// The "up" scan codes are currently unused in UTM due to SPICE:
  129. /// we instead send keyUp with the "down" scan code.
  130. /// (See also `CSInput.sendKey:type:code`)
  131. static let keyCodeToScanCodes: [Int:ScanCodes] = [
  132. kVK_Escape: .t((down: 0x01, up: 0x81)),
  133. kVK_ANSI_1: .t((down: 0x02, up: 0x82)),
  134. kVK_ANSI_2: .t((down: 0x03, up: 0x83)),
  135. kVK_ANSI_3: .t((down: 0x04, up: 0x84)),
  136. kVK_ANSI_4: .t((down: 0x05, up: 0x85)),
  137. kVK_ANSI_5: .t((down: 0x06, up: 0x86)),
  138. kVK_ANSI_6: .t((down: 0x07, up: 0x87)),
  139. kVK_ANSI_7: .t((down: 0x08, up: 0x88)),
  140. kVK_ANSI_8: .t((down: 0x09, up: 0x89)),
  141. kVK_ANSI_9: .t((down: 0x0a, up: 0x8a)),
  142. kVK_ANSI_0: .t((down: 0x0b, up: 0x8b)),
  143. kVK_ANSI_Minus: .t((down: 0x0c, up: 0x8c)),
  144. kVK_ANSI_Equal: .t((down: 0x0d, up: 0x8d)),
  145. kVK_Delete: .t((down: 0x0e, up: 0x8e)), /// IBM name is `backspace`
  146. kVK_Tab: .t((down: 0x0f, up: 0x8f)),
  147. kVK_ANSI_Q: .t((down: 0x10, up: 0x90)),
  148. kVK_ANSI_W: .t((down: 0x11, up: 0x91)),
  149. kVK_ANSI_E: .t((down: 0x12, up: 0x92)),
  150. kVK_ANSI_R: .t((down: 0x13, up: 0x93)),
  151. kVK_ANSI_T: .t((down: 0x14, up: 0x94)),
  152. kVK_ANSI_Y: .t((down: 0x15, up: 0x95)),
  153. kVK_ANSI_U: .t((down: 0x16, up: 0x96)),
  154. kVK_ANSI_I: .t((down: 0x17, up: 0x97)),
  155. kVK_ANSI_O: .t((down: 0x18, up: 0x98)),
  156. kVK_ANSI_P: .t((down: 0x19, up: 0x99)),
  157. kVK_ANSI_LeftBracket: .t((down: 0x1a, up: 0x9a)),
  158. kVK_ANSI_RightBracket: .t((down: 0x1b, up: 0x9b)),
  159. kVK_Return: .t((down: 0x1c, up: 0x9c)), /// IBM name is `enter`
  160. kVK_Control: .t((down: 0x1d, up: 0x9d)),
  161. kVK_ANSI_A: .t((down: 0x1e, up: 0x9e)),
  162. kVK_ANSI_S: .t((down: 0x1f, up: 0x9f)),
  163. kVK_ANSI_D: .t((down: 0x20, up: 0xa0)),
  164. kVK_ANSI_F: .t((down: 0x21, up: 0xa1)),
  165. kVK_ANSI_G: .t((down: 0x22, up: 0xa2)),
  166. kVK_ANSI_H: .t((down: 0x23, up: 0xa3)),
  167. kVK_ANSI_J: .t((down: 0x24, up: 0xa4)),
  168. kVK_ANSI_K: .t((down: 0x25, up: 0xa5)),
  169. kVK_ANSI_L: .t((down: 0x26, up: 0xa6)),
  170. kVK_ANSI_Semicolon: .t((down: 0x27, up: 0xa7)),
  171. kVK_ANSI_Quote: .t((down: 0x28, up: 0xa8)),
  172. kVK_ANSI_Grave: .t((down: 0x29, up: 0xa9)),
  173. kVK_Shift: .t((down: 0x2a, up: 0xaa)),
  174. kVK_ANSI_Backslash: .t((down: 0x2b, up: 0xab)),
  175. kVK_ANSI_Z: .t((down: 0x2c, up: 0xac)),
  176. kVK_ANSI_X: .t((down: 0x2d, up: 0xad)),
  177. kVK_ANSI_C: .t((down: 0x2e, up: 0xae)),
  178. kVK_ANSI_V: .t((down: 0x2f, up: 0xaf)),
  179. kVK_ANSI_B: .t((down: 0x30, up: 0xb0)),
  180. kVK_ANSI_N: .t((down: 0x31, up: 0xb1)),
  181. kVK_ANSI_M: .t((down: 0x32, up: 0xb2)),
  182. kVK_ANSI_Comma: .t((down: 0x33, up: 0xb3)),
  183. kVK_ANSI_Period: .t((down: 0x34, up: 0xb4)),
  184. kVK_ANSI_Slash: .t((down: 0x35, up: 0xb5)),
  185. kVK_RightShift: .t((down: 0x36, up: 0xb6)),
  186. // Print screen not available in Carbon
  187. kVK_Option: .t((down: 0x38, up: 0xb8)), /// IBM name is `alt`
  188. kVK_Space: .t((down: 0x39, up: 0xb9)),
  189. kVK_CapsLock: .t((down: 0x3a, up: 0xba)),
  190. kVK_F1: .t((down: 0x3b, up: 0xbb)),
  191. kVK_F2: .t((down: 0x3c, up: 0xbc)),
  192. kVK_F3: .t((down: 0x3d, up: 0xbd)),
  193. kVK_F4: .t((down: 0x3e, up: 0xbe)),
  194. kVK_F5: .t((down: 0x3f, up: 0xbf)),
  195. kVK_F6: .t((down: 0x40, up: 0xc0)),
  196. kVK_F7: .t((down: 0x41, up: 0xc1)),
  197. kVK_F8: .t((down: 0x42, up: 0xc2)),
  198. kVK_F9: .t((down: 0x43, up: 0xc3)),
  199. kVK_F10: .t((down: 0x44, up: 0xc4)),
  200. // Numlock not available in Carbon
  201. // Scroll lock not available in Carbon
  202. // Number pad Home, up, pgUp not available in Carbon
  203. kVK_ANSI_KeypadMinus: .t((down: 0x4a, up: 0xca)),
  204. // Number pad left, center, right not available in Carbon
  205. kVK_ANSI_KeypadPlus: .t((down: 0x4e, up: 0xce)),
  206. // Number pad end, down, pgDown, insert not available in Carbon
  207. kVK_ANSI_KeypadClear: .t((down: 0x45, up: 0xC5)), /// in IBM this is num lock, so we send that
  208. kVK_ANSI_KeypadDivide: .t((down: 0xe035, up: 0xb5)),
  209. kVK_ANSI_KeypadEnter: .t((down: 0xe01c, up: 0x9c)),
  210. kVK_ANSI_Keypad0: .t((down: 0x52, up: 0xD2)),
  211. kVK_ANSI_Keypad1: .t((down: 0x4F, up: 0xCF)),
  212. kVK_ANSI_Keypad2: .t((down: 0x50, up: 0xD0)),
  213. kVK_ANSI_Keypad3: .t((down: 0x51, up: 0xD1)),
  214. kVK_ANSI_Keypad4: .t((down: 0x4B, up: 0xCB)),
  215. kVK_ANSI_Keypad5: .t((down: 0x4C, up: 0xCC)),
  216. kVK_ANSI_Keypad6: .t((down: 0x4D, up: 0xCD)),
  217. kVK_ANSI_Keypad7: .t((down: 0x47, up: 0xC7)),
  218. kVK_ANSI_Keypad8: .t((down: 0x48, up: 0xC8)),
  219. kVK_ANSI_Keypad9: .t((down: 0x49, up: 0xC9)),
  220. kVK_ANSI_KeypadDecimal: .t((down: 0x53, up: 0xD3)),
  221. kVK_ANSI_KeypadEquals: .t((down: 0x00, up: 0x00)), /// Not found on IBM
  222. kVK_ANSI_KeypadMultiply:.t((down: 0x37, up: 0xB7)),
  223. kVK_F11: .t((down: 0x57, up: 0xd7)),
  224. kVK_F12: .t((down: 0x58, up: 0xd8)),
  225. // Insert not available in Carbon
  226. kVK_ForwardDelete: .t((down: 0xe053, up: 0xd3)), /// IBM name is `delete`
  227. kVK_Home: .t((down: 0xe047, up: 0xc7)),
  228. kVK_End: .t((down: 0xe04f, up: 0xcf)),
  229. kVK_PageUp: .t((down: 0xe049, up: 0xc9)),
  230. kVK_PageDown: .t((down: 0xe051, up: 0xd1)),
  231. kVK_LeftArrow: .t((down: 0xe04b, up: 0xcb)),
  232. kVK_RightArrow: .t((down: 0xe04d, up: 0xcd)),
  233. kVK_UpArrow: .t((down: 0xe048, up: 0xc8)),
  234. kVK_DownArrow: .t((down: 0xe050, up: 0xd0)),
  235. kVK_RightOption: .t((down: 0xe038, up: 0xb8)), /// IBM name is `right alt`
  236. kVK_RightControl: .t((down: 0xe01d, up: 0x9d)),
  237. // Pause not available in Carbon
  238. /* Additional non-IBM keys */
  239. kVK_Command: .t((down: 0xe05b, up: 0xdb)),
  240. kVK_RightCommand: .t((down: 0xe05c, up: 0xdc)),
  241. kVK_ISO_Section: .t((down: 0x56, up: 0xD6)),
  242. kVK_VolumeUp: .t((down: 0xe030, up: 0xb0)),
  243. kVK_VolumeDown: .t((down: 0xe02e, up: 0xae)),
  244. kVK_Mute: .t((down: 0xE020, up: 0xa0)),
  245. kVK_F13: .t((down: 0x64, up: 0xe4)),
  246. kVK_F14: .t((down: 0x65, up: 0xe5)),
  247. kVK_F15: .t((down: 0x66, up: 0xe6)),
  248. kVK_F16: .t((down: 0x67, up: 0xe7)),
  249. kVK_F17: .t((down: 0x68, up: 0xe8)),
  250. kVK_F18: .t((down: 0x69, up: 0xe9)),
  251. kVK_F19: .t((down: 0x6a, up: 0xea)),
  252. kVK_F20: .t((down: 0x6b, up: 0xeb)),
  253. kVK_JIS_Yen: .t((down: 0x7d, up: 0xfd)),
  254. kVK_JIS_Underscore: .t((down: 0x73, up: 0xf3)),
  255. kVK_JIS_KeypadComma: .t((down: 0x5c, up: 0xdc)),
  256. kVK_JIS_Eisu: .t((down: 0x73, up: 0xf3)),
  257. kVK_JIS_Kana: .t((down: 0x70, up: 0xf0)),
  258. /* The Function and help keys doesn't have a scan code */
  259. kVK_Function: .t((down: 0x00, up: 0x00)),
  260. kVK_Help: .t((down: 0x00, up: 0x00))
  261. ]
  262. }
  263. extension KeyCodeMap {
  264. /// Support ASCII control characters
  265. /// https://jkorpela.fi/chars/c0.html
  266. fileprivate static func tryHandleSpecialChar(_ character: Character) -> Dictionary<String, Int>? {
  267. if let ascii = character.asciiValue {
  268. var virtKeyCode: Int?
  269. if ascii <= 31 {
  270. /// Control held
  271. switch ascii {
  272. case 1: virtKeyCode = kVK_ANSI_A
  273. case 2: virtKeyCode = kVK_ANSI_B
  274. case 3: virtKeyCode = kVK_ANSI_C
  275. case 4: virtKeyCode = kVK_ANSI_D
  276. case 5: virtKeyCode = kVK_ANSI_E
  277. case 6: virtKeyCode = kVK_ANSI_F
  278. case 7: virtKeyCode = kVK_ANSI_G
  279. case 8: virtKeyCode = kVK_ANSI_H
  280. case 9: virtKeyCode = kVK_ANSI_I
  281. case 10: virtKeyCode = kVK_ANSI_J
  282. case 11: virtKeyCode = kVK_ANSI_K
  283. case 12: virtKeyCode = kVK_ANSI_L
  284. case 13: virtKeyCode = kVK_ANSI_M
  285. case 14: virtKeyCode = kVK_ANSI_N
  286. case 15: virtKeyCode = kVK_ANSI_O
  287. case 16: virtKeyCode = kVK_ANSI_P
  288. case 17: virtKeyCode = kVK_ANSI_Q
  289. case 18: virtKeyCode = kVK_ANSI_R
  290. case 19: virtKeyCode = kVK_ANSI_S
  291. case 20: virtKeyCode = kVK_ANSI_T
  292. case 21: virtKeyCode = kVK_ANSI_U
  293. case 22: virtKeyCode = kVK_ANSI_V
  294. case 23: virtKeyCode = kVK_ANSI_W
  295. case 24: virtKeyCode = kVK_ANSI_Y
  296. case 25: virtKeyCode = kVK_ANSI_X
  297. case 26: virtKeyCode = kVK_ANSI_Z
  298. case 27: virtKeyCode = kVK_ANSI_LeftBracket
  299. case 28: virtKeyCode = kVK_ANSI_Backslash
  300. case 29: virtKeyCode = kVK_ANSI_RightBracket
  301. case 30:
  302. if var dict = characterToKeyCode(character: "^") {
  303. dict["control"] = 1
  304. return dict
  305. } else { return nil }
  306. case 31:
  307. if var dict = characterToKeyCode(character: "_") {
  308. dict["control"] = 1
  309. return dict
  310. } else { return nil }
  311. default:
  312. virtKeyCode = nil
  313. }
  314. if let virtKeyCode = virtKeyCode {
  315. return [
  316. "option": 0,
  317. "shift": 0,
  318. "function": 0,
  319. "control": 1,
  320. "command": 0,
  321. "virtKeyCode": virtKeyCode
  322. ]
  323. }
  324. } else if ascii == 127 {
  325. /// Delete key
  326. return [
  327. "option": 0,
  328. "shift": 0,
  329. "function": 0,
  330. "control": 1,
  331. "command": 0,
  332. "virtKeyCode": kVK_Delete
  333. ]
  334. }
  335. }
  336. return nil
  337. }
  338. }