.../Source/Utilities/FakeKey.swift
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // FakeKeyEvents.swift |
3 | | // CutBox |
4 | | // |
5 | | // Created by Jason Milkins on 26/3/18. |
6 | | // Copyright © 2018-2023 ocodo. All rights reserved. |
7 | | // |
8 | | |
9 | | import Foundation |
10 | | import Carbon |
11 | | |
12 | | class FakeKey { |
13 | | static var testing = false |
14 | | static var testResult: [CGEvent?] = [] |
15 | | |
16 | 144 | private let keyCodeRange = 0x00...0x7E |
17 | | |
18 | 5 | func send(fakeKey key: String, useCommandFlag: Bool) { |
19 | 5 | let keyCode = stringToKeyCode(key) |
20 | 5 | send(fakeKey: numericCast(keyCode), useCommandFlag: useCommandFlag) |
21 | 5 | } |
22 | | |
23 | 508 | private func transformKeyCode(_ keyCode: Int) -> String { |
24 | 508 | guard keyCodeRange.contains(keyCode) |
25 | 508 | else { fatalError("Cannot transform \(keyCode) - out of range") } |
26 | 508 | |
27 | 508 | let inputSource = unsafeBitCast( |
28 | 508 | TISGetInputSourceProperty( |
29 | 508 | TISCopyCurrentASCIICapableKeyboardLayoutInputSource().takeUnretainedValue(), |
30 | 508 | kTISPropertyUnicodeKeyLayoutData |
31 | 508 | ), to: CFData.self) |
32 | 508 | |
33 | 508 | var deadKeyState: UInt32 = 0 |
34 | 508 | var char = UniChar() |
35 | 508 | var length = 0 |
36 | 508 | |
37 | 508 | CoreServices.UCKeyTranslate(unsafeBitCast(CFDataGetBytePtr(inputSource), |
38 | 508 | to: UnsafePointer<CoreServices.UCKeyboardLayout>.self), |
39 | 508 | UInt16(keyCode), |
40 | 508 | UInt16(CoreServices.kUCKeyActionDisplay), |
41 | 508 | UInt32(0), |
42 | 508 | UInt32(LMGetKbdType()), |
43 | 508 | OptionBits(CoreServices.kUCKeyTranslateNoDeadKeysBit), |
44 | 508 | |
45 | 508 | &deadKeyState, |
46 | 508 | 1, |
47 | 508 | &length, |
48 | 508 | &char) |
49 | 508 | |
50 | 508 | return NSString(characters: &char, length: length).uppercased |
51 | 508 | } |
52 | | |
53 | 4 | private func keyCodeTranslationTable() -> [String: Int] { |
54 | 4 | var table = [String: Int]() |
55 | 508 | for code in keyCodeRange { |
56 | 508 | table[transformKeyCode(code)] = code |
57 | 508 | } |
58 | 4 | return table |
59 | 4 | } |
60 | | |
61 | 5 | private func stringToKeyCode(_ key: String) -> Int { |
62 | 5 | guard key.count == 1 else { fatalError("Only single char strings can be used") } |
63 | 5 | |
64 | 5 | let translationTable = keyCodeTranslationTable() |
65 | 5 | |
66 | 5 | guard let keyCode = translationTable[key] |
67 | 5 | else { fatalError("Unable to get keyCode for: \(key)") } |
68 | 5 | |
69 | 5 | return keyCode |
70 | 5 | } |
71 | | |
72 | 4 | private func send(fakeKey keyCode: CGKeyCode, useCommandFlag: Bool) { |
73 | 4 | let sourceRef = CGEventSource(stateID: .combinedSessionState) |
74 | 4 | |
75 | 4 | if sourceRef == nil { |
76 | 0 | NSLog("No event source") |
77 | 0 | return |
78 | 4 | } |
79 | 4 | |
80 | 4 | let keyDownEvent = CGEvent( |
81 | 4 | keyboardEventSource: sourceRef, |
82 | 4 | virtualKey: keyCode, |
83 | 4 | keyDown: true |
84 | 4 | ) |
85 | 4 | |
86 | 4 | if useCommandFlag { |
87 | 3 | keyDownEvent?.flags = .maskCommand |
88 | 4 | } |
89 | 4 | |
90 | 4 | let keyUpEvent = CGEvent( |
91 | 4 | keyboardEventSource: sourceRef, |
92 | 4 | virtualKey: keyCode, |
93 | 4 | keyDown: false |
94 | 4 | ) |
95 | 4 | |
96 | 4 | if !Self.testing { |
97 | 0 | keyDownEvent?.post(tap: .cghidEventTap) |
98 | 0 | keyUpEvent?.post(tap: .cghidEventTap) |
99 | 4 | } else { |
100 | 4 | Self.testResult = [keyDownEvent, keyUpEvent] |
101 | 4 | } |
102 | 4 | } |
103 | | } |