.../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  |  | }  |