StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use std::slice; |
| 2 | |
| 3 | use cocoa::{ |
| 4 | base::{id, nil, BOOL}, |
| 5 | foundation::{NSArray, NSString, NSUInteger}, |
| 6 | }; |
| 7 | use objc::{msg_send, sel, sel_impl}; |
| 8 | use strato_ui_core::keymap::Keystroke; |
| 9 | use strato_ui_core::platform::keyboard::{KeyCode, NativeKeyCode, PhysicalKey}; |
| 10 | |
| 11 | use super::make_nsstring; |
| 12 | |
| 13 | // Modifier key mask values for the Carbon API. |
| 14 | pub const CMD_KEY: u16 = 256; |
| 15 | pub const SHIFT_KEY: u16 = 512; |
| 16 | pub const OPTION_KEY: u16 = 2048; |
| 17 | pub const CONTROL_KEY: u16 = 4096; |
| 18 | |
| 19 | extern "C" { |
| 20 | fn charToKeyCodes(keyChar: id) -> id; |
| 21 | fn keyCodeToChar(keyCode: NSUInteger, shifted: BOOL) -> id; |
| 22 | } |
| 23 | |
| 24 | pub struct Keycode(pub u16); |
| 25 | |
| 26 | impl Keycode { |
| 27 | pub fn try_to_key_name(self, shift_key_pressed: bool) -> Option<String> { |
| 28 | unsafe { |
| 29 | // The underlying core-foundation library interprets objc BOOL type as bool |
| 30 | // in aarch machines but as i8 in intel machines so we need to call .into here. |
| 31 | // But clippy isn't smart enough to know that so we silence it here for now. |
| 32 | #[allow(clippy::useless_conversion)] |
| 33 | let key = keyCodeToChar(self.0 as u64, shift_key_pressed.into()); |
| 34 | |
| 35 | if key == nil { |
| 36 | return None; |
| 37 | } |
| 38 | |
| 39 | let cstr = key.UTF8String() as *const u8; |
| 40 | std::str::from_utf8(slice::from_raw_parts(cstr, key.len())) |
| 41 | .ok() |
| 42 | .map(|s| s.to_string()) |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | // There could have multiple keycodes mapping to one virtual key. Return an iterator |
| 47 | // to all possible values of keycode here. |
| 48 | pub fn keycodes_from_key_name(key_name: &str) -> impl Iterator<Item = Keycode> { |
| 49 | unsafe { |
| 50 | let keycodes: id = charToKeyCodes(make_nsstring(key_name)); |
| 51 | let keycodes_length = keycodes.count(); |
| 52 | |
| 53 | (0..keycodes_length).map(move |i| { |
| 54 | let keycode: NSUInteger = |
| 55 | msg_send![keycodes.objectAtIndex(i), unsignedIntegerValue]; |
| 56 | Self(keycode as u16) |
| 57 | }) |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | // Convert modifier flags to Carbon style modifier key mask. |
| 63 | pub fn modifier_code(keystroke: &Keystroke) -> u16 { |
| 64 | let mut code = 0; |
| 65 | if keystroke.alt { |
| 66 | code |= OPTION_KEY; |
| 67 | } |
| 68 | |
| 69 | if keystroke.cmd { |
| 70 | code |= CMD_KEY; |
| 71 | } |
| 72 | |
| 73 | if keystroke.shift { |
| 74 | code |= SHIFT_KEY; |
| 75 | } |
| 76 | |
| 77 | if keystroke.ctrl { |
| 78 | code |= CONTROL_KEY; |
| 79 | } |
| 80 | |
| 81 | code |
| 82 | } |
| 83 | |
| 84 | // The following types and functions are taken from winit's appkit implementation. |
| 85 | // We redefine them here to avoid needing to include the entirety of winit as a dependency for MacOS. |
| 86 | // -------------------------------------------------------------------------------------------------------- |
| 87 | |
| 88 | /// Converts a scancode to a physical key. Logic is taken from winit appkit code. |
| 89 | pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey { |
| 90 | // Follows what Chromium and Firefox do: |
| 91 | // https://chromium.googlesource.com/chromium/src.git/+/3e1a26c44c024d97dc9a4c09bbc6a2365398ca2c/ui/events/keycodes/dom/dom_code_data.inc |
| 92 | // https://searchfox.org/mozilla-central/rev/c597e9c789ad36af84a0370d395be066b7dc94f4/widget/NativeKeyToDOMCodeName.h |
| 93 | // |
| 94 | // See also: |
| 95 | // Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h |
| 96 | // |
| 97 | // Also see https://developer.apple.com/documentation/appkit/function-key-unicode-values: |
| 98 | // |
| 99 | // > the system handles some function keys at a lower level and your app never sees them. |
| 100 | // > Examples include the Volume Up key, Volume Down key, Volume Mute key, Eject key, and |
| 101 | // > Function key found on many Macs. |
| 102 | // |
| 103 | // So the handling of some of these is mostly for show. |
| 104 | PhysicalKey::Code(match scancode { |
| 105 | 0x00 => KeyCode::KeyA, |
| 106 | 0x01 => KeyCode::KeyS, |
| 107 | 0x02 => KeyCode::KeyD, |
| 108 | 0x03 => KeyCode::KeyF, |
| 109 | 0x04 => KeyCode::KeyH, |
| 110 | 0x05 => KeyCode::KeyG, |
| 111 | 0x06 => KeyCode::KeyZ, |
| 112 | 0x07 => KeyCode::KeyX, |
| 113 | 0x08 => KeyCode::KeyC, |
| 114 | 0x09 => KeyCode::KeyV, |
| 115 | // This key is typically located near LeftShift key, roughly the same location as backquote |
| 116 | // (`) on Windows' US layout. |
| 117 | // |
| 118 | // The keycap varies on international keyboards. |
| 119 | 0x0a => KeyCode::IntlBackslash, |
| 120 | 0x0b => KeyCode::KeyB, |
| 121 | 0x0c => KeyCode::KeyQ, |
| 122 | 0x0d => KeyCode::KeyW, |
| 123 | 0x0e => KeyCode::KeyE, |
| 124 | 0x0f => KeyCode::KeyR, |
| 125 | 0x10 => KeyCode::KeyY, |
| 126 | 0x11 => KeyCode::KeyT, |
| 127 | 0x12 => KeyCode::Digit1, |
| 128 | 0x13 => KeyCode::Digit2, |
| 129 | 0x14 => KeyCode::Digit3, |
| 130 | 0x15 => KeyCode::Digit4, |
| 131 | 0x16 => KeyCode::Digit6, |
| 132 | 0x17 => KeyCode::Digit5, |
| 133 | 0x18 => KeyCode::Equal, |
| 134 | 0x19 => KeyCode::Digit9, |
| 135 | 0x1a => KeyCode::Digit7, |
| 136 | 0x1b => KeyCode::Minus, |
| 137 | 0x1c => KeyCode::Digit8, |
| 138 | 0x1d => KeyCode::Digit0, |
| 139 | 0x1e => KeyCode::BracketRight, |
| 140 | 0x1f => KeyCode::KeyO, |
| 141 | 0x20 => KeyCode::KeyU, |
| 142 | 0x21 => KeyCode::BracketLeft, |
| 143 | 0x22 => KeyCode::KeyI, |
| 144 | 0x23 => KeyCode::KeyP, |
| 145 | 0x24 => KeyCode::Enter, |
| 146 | 0x25 => KeyCode::KeyL, |
| 147 | 0x26 => KeyCode::KeyJ, |
| 148 | 0x27 => KeyCode::Quote, |
| 149 | 0x28 => KeyCode::KeyK, |
| 150 | 0x29 => KeyCode::Semicolon, |
| 151 | 0x2a => KeyCode::Backslash, |
| 152 | 0x2b => KeyCode::Comma, |
| 153 | 0x2c => KeyCode::Slash, |
| 154 | 0x2d => KeyCode::KeyN, |
| 155 | 0x2e => KeyCode::KeyM, |
| 156 | 0x2f => KeyCode::Period, |
| 157 | 0x30 => KeyCode::Tab, |
| 158 | 0x31 => KeyCode::Space, |
| 159 | 0x32 => KeyCode::Backquote, |
| 160 | 0x33 => KeyCode::Backspace, |
| 161 | // 0x34 => unknown, // kVK_Powerbook_KeypadEnter |
| 162 | 0x35 => KeyCode::Escape, |
| 163 | 0x36 => KeyCode::SuperRight, |
| 164 | 0x37 => KeyCode::SuperLeft, |
| 165 | 0x38 => KeyCode::ShiftLeft, |
| 166 | 0x39 => KeyCode::CapsLock, |
| 167 | 0x3a => KeyCode::AltLeft, |
| 168 | 0x3b => KeyCode::ControlLeft, |
| 169 | 0x3c => KeyCode::ShiftRight, |
| 170 | 0x3d => KeyCode::AltRight, |
| 171 | 0x3e => KeyCode::ControlRight, |
| 172 | 0x3f => KeyCode::Fn, |
| 173 | 0x40 => KeyCode::F17, |
| 174 | 0x41 => KeyCode::NumpadDecimal, |
| 175 | // 0x42 -> unknown, |
| 176 | 0x43 => KeyCode::NumpadMultiply, |
| 177 | // 0x44 => unknown, |
| 178 | 0x45 => KeyCode::NumpadAdd, |
| 179 | // 0x46 => unknown, |
| 180 | 0x47 => KeyCode::NumLock, // kVK_ANSI_KeypadClear |
| 181 | 0x48 => KeyCode::AudioVolumeUp, |
| 182 | 0x49 => KeyCode::AudioVolumeDown, |
| 183 | 0x4a => KeyCode::AudioVolumeMute, |
| 184 | 0x4b => KeyCode::NumpadDivide, |
| 185 | 0x4c => KeyCode::NumpadEnter, |
| 186 | // 0x4d => unknown, |
| 187 | 0x4e => KeyCode::NumpadSubtract, |
| 188 | 0x4f => KeyCode::F18, |
| 189 | 0x50 => KeyCode::F19, |
| 190 | 0x51 => KeyCode::NumpadEqual, |
| 191 | 0x52 => KeyCode::Numpad0, |
| 192 | 0x53 => KeyCode::Numpad1, |
| 193 | 0x54 => KeyCode::Numpad2, |
| 194 | 0x55 => KeyCode::Numpad3, |
| 195 | 0x56 => KeyCode::Numpad4, |
| 196 | 0x57 => KeyCode::Numpad5, |
| 197 | 0x58 => KeyCode::Numpad6, |
| 198 | 0x59 => KeyCode::Numpad7, |
| 199 | 0x5a => KeyCode::F20, |
| 200 | 0x5b => KeyCode::Numpad8, |
| 201 | 0x5c => KeyCode::Numpad9, |
| 202 | 0x5d => KeyCode::IntlYen, |
| 203 | 0x5e => KeyCode::IntlRo, |
| 204 | 0x5f => KeyCode::NumpadComma, |
| 205 | 0x60 => KeyCode::F5, |
| 206 | 0x61 => KeyCode::F6, |
| 207 | 0x62 => KeyCode::F7, |
| 208 | 0x63 => KeyCode::F3, |
| 209 | 0x64 => KeyCode::F8, |
| 210 | 0x65 => KeyCode::F9, |
| 211 | 0x66 => KeyCode::Lang2, |
| 212 | 0x67 => KeyCode::F11, |
| 213 | 0x68 => KeyCode::Lang1, |
| 214 | 0x69 => KeyCode::F13, |
| 215 | 0x6a => KeyCode::F16, |
| 216 | 0x6b => KeyCode::F14, |
| 217 | // 0x6c => unknown, |
| 218 | 0x6d => KeyCode::F10, |
| 219 | 0x6e => KeyCode::ContextMenu, |
| 220 | 0x6f => KeyCode::F12, |
| 221 | // 0x70 => unknown, |
| 222 | 0x71 => KeyCode::F15, |
| 223 | 0x72 => KeyCode::Insert, |
| 224 | 0x73 => KeyCode::Home, |
| 225 | 0x74 => KeyCode::PageUp, |
| 226 | 0x75 => KeyCode::Delete, |
| 227 | 0x76 => KeyCode::F4, |
| 228 | 0x77 => KeyCode::End, |
| 229 | 0x78 => KeyCode::F2, |
| 230 | 0x79 => KeyCode::PageDown, |
| 231 | 0x7a => KeyCode::F1, |
| 232 | 0x7b => KeyCode::ArrowLeft, |
| 233 | 0x7c => KeyCode::ArrowRight, |
| 234 | 0x7d => KeyCode::ArrowDown, |
| 235 | 0x7e => KeyCode::ArrowUp, |
| 236 | 0x7f => KeyCode::Power, // On 10.7 and 10.8 only |
| 237 | _ => return PhysicalKey::Unidentified(NativeKeyCode::MacOS(scancode as u16)), |
| 238 | }) |
| 239 | } |
| 240 |