Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/platform/mac/objc/keycode.m
1#include <AppKit/AppKit.h>
2#include <Carbon/Carbon.h>
3#include <CoreFoundation/CoreFoundation.h>
4 
5// One virtual key could map to multiple physical keys on the keyboard. So we keep
6// a mapping from key name to an array of keycodes.
7NSMutableDictionary<NSString*, NSMutableArray<NSNumber*>*>* keycodeDict;
8 
9BOOL IsUnicodeControl(unichar c) {
10 // C0 control characters: http://unicode.org/charts/PDF/U0000.pdf
11 // C1 control characters: http://unicode.org/charts/PDF/U0080.pdf
12 return c <= 0x1F || (c >= 0x7F && c <= 0x9F);
13}
14 
15// Control character naming needs to be in sync with the corresponding rust definition in
16// `event.rs`: see
17// https://github.com/warpdotdev/warp-internal/blob/master/ui/src/platform/mac/utils.rs#L42
18// The list of control characters are referenced from chromium code here:
19// https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_mac.mm#329
20NSString* KeyFromControlKeyCode(unsigned short keyCode) {
21 switch (keyCode) {
22 case kVK_ANSI_KeypadEnter:
23 return @"numpadenter";
24 case kVK_Return:
25 return @"enter";
26 case kVK_Tab:
27 return @"tab";
28 case kVK_Delete:
29 return @"backspace";
30 case kVK_Escape:
31 return @"escape";
32 case kVK_F1:
33 return @"f1";
34 case kVK_F2:
35 return @"f2";
36 case kVK_F3:
37 return @"f3";
38 case kVK_F4:
39 return @"f4";
40 case kVK_F5:
41 return @"f5";
42 case kVK_F6:
43 return @"f6";
44 case kVK_F7:
45 return @"f7";
46 case kVK_F8:
47 return @"f8";
48 case kVK_F9:
49 return @"f9";
50 case kVK_F10:
51 return @"f10";
52 case kVK_F11:
53 return @"f11";
54 case kVK_F12:
55 return @"f12";
56 case kVK_F13:
57 return @"f13";
58 case kVK_F14:
59 return @"f14";
60 case kVK_F15:
61 return @"f15";
62 case kVK_F16:
63 return @"f16";
64 case kVK_F17:
65 return @"f17";
66 case kVK_F18:
67 return @"f18";
68 case kVK_F19:
69 return @"f19";
70 case kVK_F20:
71 return @"f20";
72 case kVK_ForwardDelete:
73 return @"delete";
74 case kVK_Help:
75 return @"insert";
76 case kVK_Home:
77 return @"home";
78 case kVK_PageUp:
79 return @"pageup";
80 case kVK_End:
81 return @"end";
82 case kVK_PageDown:
83 return @"pagedown";
84 case kVK_LeftArrow:
85 return @"left";
86 case kVK_RightArrow:
87 return @"right";
88 case kVK_DownArrow:
89 return @"down";
90 case kVK_UpArrow:
91 return @"up";
92 default:
93 return nil;
94 }
95}
96 
97// Helper function to get the keyboard layout data
98CFDataRef GetKeyboardLayoutData() {
99 TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();
100 CFDataRef layout_data =
101 (CFDataRef)(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData));
102 if (!layout_data) {
103 // TISGetInputSourceProperty returns null with some keyboard layouts (e.g. Japanese and
104 // Chinese). Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return.
105 source = TISCopyCurrentKeyboardLayoutInputSource();
106 layout_data =
107 (CFDataRef)(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData));
108 }
109 return layout_data;
110}
111 
112// Referenced from chromium:
113// https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_mac.mm
114// Here we take the keyboard layout, keycode, modifier keys, and keyboard type to
115// determine the output character.
116// Notice that we don't yet handle multiple character case here. But this should
117// be minor given Chrome also doesn't support it.
118UniChar TranslatedUnicodeCharFromKeyCode(CFDataRef layout_data, UInt16 key_code,
119 UInt32 modifier_key_state, UInt32 keyboard_type) {
120 if (!layout_data) return 0xFFFD; // REPLACEMENT CHARACTER
121 
122 const UCKeyboardLayout* keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layout_data);
123 
124 UInt32 deadKeyState = 0;
125 UniCharCount maxStringLength = 255;
126 UniCharCount actualStringLength = 0;
127 UniChar unicodeString[maxStringLength];
128 
129 UCKeyTranslate(keyboardLayout, key_code, kUCKeyActionDown, modifier_key_state, keyboard_type,
130 kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxStringLength,
131 &actualStringLength, unicodeString);
132 // TODO(kevin): Handle multiple character case. Should be rare.
133 return unicodeString[0];
134}
135 
136// Convert keycode to its corresponding character on the keyboard.
137NSString* keyCodeToChar(UInt16 keyCode, BOOL shifted) {
138 UInt32 modifier_key_state = 0;
139 
140 // The shift key representation in Carbon is 1 << 9.
141 // However, UCKeyTranslate takes the modifier keys and shift them by 8 bits. So we
142 // only need to pass in 1 << 1 here.
143 if (shifted) {
144 modifier_key_state = 1 << 1;
145 }
146 
147 CFDataRef layout_data = GetKeyboardLayoutData();
148 UniChar translated_char =
149 TranslatedUnicodeCharFromKeyCode(layout_data, keyCode, modifier_key_state, LMGetKbdLast());
150 
151 // UCKeyTranslate can't translate control characters like function keys and arrow
152 // keys. We keep a separate mapping for this case. This is the same behavior as chromium:
153 // https://chromium.googlesource.com/chromium/src/+/lkgr/ui/events/keycodes/keyboard_code_conversion_mac.mm#873
154 if (IsUnicodeControl(translated_char)) {
155 return KeyFromControlKeyCode(keyCode);
156 } else {
157 return [NSString stringWithFormat:@"%C", translated_char];
158 }
159}
160 
161NSArray<NSNumber*>* charToKeyCodes(NSString* keyChar) {
162 if (keycodeDict == nil) {
163 keycodeDict = [[NSMutableDictionary alloc] init];
164 CFDataRef layout_data = GetKeyboardLayoutData();
165 
166 // For every keycode.
167 size_t i;
168 for (i = 0; i < 128; ++i) {
169 UInt32 shift_key = 1 << 1;
170 
171 // Compute a shifted and unshifted version for one keycode.
172 UniChar unshifted =
173 TranslatedUnicodeCharFromKeyCode(layout_data, (UInt16)i, 0, LMGetKbdLast());
174 UniChar shifted =
175 TranslatedUnicodeCharFromKeyCode(layout_data, (UInt16)i, shift_key, LMGetKbdLast());
176 
177 NSString* unshifted_str;
178 if (IsUnicodeControl(unshifted)) {
179 unshifted_str = KeyFromControlKeyCode(i);
180 } else {
181 unshifted_str = [NSString stringWithFormat:@"%C", unshifted];
182 }
183 
184 NSString* shifted_str;
185 if (IsUnicodeControl(shifted)) {
186 shifted_str = KeyFromControlKeyCode(i);
187 } else {
188 shifted_str = [NSString stringWithFormat:@"%C", shifted];
189 }
190 
191 if (unshifted_str != nil && [unshifted_str length] > 0) {
192 if ([keycodeDict objectForKey:unshifted_str] == nil) {
193 [keycodeDict setObject:[[[NSMutableArray alloc] init] autorelease]
194 forKey:unshifted_str];
195 }
196 NSMutableArray* keycodes = [keycodeDict objectForKey:unshifted_str];
197 [keycodes addObject:[NSNumber numberWithInt:i]];
198 }
199 
200 if (shifted_str != nil && [shifted_str length] > 0) {
201 if ([keycodeDict objectForKey:shifted_str] == nil) {
202 [keycodeDict setObject:[[[NSMutableArray alloc] init] autorelease]
203 forKey:shifted_str];
204 }
205 NSMutableArray* keycodes = [keycodeDict objectForKey:shifted_str];
206 [keycodes addObject:[NSNumber numberWithInt:i]];
207 }
208 }
209 }
210 
211 return [keycodeDict objectForKey:keyChar];
212}
213