StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 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. |
| 7 | NSMutableDictionary<NSString*, NSMutableArray<NSNumber*>*>* keycodeDict; |
| 8 | |
| 9 | BOOL 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 |
| 20 | NSString* 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 |
| 98 | CFDataRef 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. |
| 118 | UniChar 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. |
| 137 | NSString* 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 | |
| 161 | NSArray<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 |