event.rs

  1use crate::{
  2    geometry::vector::vec2f,
  3    keymap::Keystroke,
  4    platform::{Event, NavigationDirection},
  5};
  6use cocoa::{
  7    appkit::{NSEvent, NSEventModifierFlags, NSEventType},
  8    base::{id, nil, YES},
  9    foundation::NSString as _,
 10};
 11use std::{borrow::Cow, ffi::CStr, os::raw::c_char};
 12
 13pub fn key_to_native(key: &str) -> Cow<str> {
 14    use cocoa::appkit::*;
 15    let code = match key {
 16        "backspace" => 0x7F,
 17        "up" => NSUpArrowFunctionKey,
 18        "down" => NSDownArrowFunctionKey,
 19        "left" => NSLeftArrowFunctionKey,
 20        "right" => NSRightArrowFunctionKey,
 21        "pageup" => NSPageUpFunctionKey,
 22        "pagedown" => NSPageDownFunctionKey,
 23        "delete" => NSDeleteFunctionKey,
 24        "f1" => NSF1FunctionKey,
 25        "f2" => NSF2FunctionKey,
 26        "f3" => NSF3FunctionKey,
 27        "f4" => NSF4FunctionKey,
 28        "f5" => NSF5FunctionKey,
 29        "f6" => NSF6FunctionKey,
 30        "f7" => NSF7FunctionKey,
 31        "f8" => NSF8FunctionKey,
 32        "f9" => NSF9FunctionKey,
 33        "f10" => NSF10FunctionKey,
 34        "f11" => NSF11FunctionKey,
 35        "f12" => NSF12FunctionKey,
 36        _ => return Cow::Borrowed(key),
 37    };
 38    Cow::Owned(String::from_utf16(&[code]).unwrap())
 39}
 40
 41impl Event {
 42    pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
 43        let event_type = native_event.eventType();
 44
 45        // Filter out event types that aren't in the NSEventType enum.
 46        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
 47        match event_type as u64 {
 48            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
 49                return None;
 50            }
 51            _ => {}
 52        }
 53
 54        match event_type {
 55            NSEventType::NSFlagsChanged => {
 56                let modifiers = native_event.modifierFlags();
 57                let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
 58                let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
 59                let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
 60                let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
 61
 62                Some(Self::ModifiersChanged {
 63                    ctrl,
 64                    alt,
 65                    shift,
 66                    cmd,
 67                })
 68            }
 69            NSEventType::NSKeyDown => {
 70                let modifiers = native_event.modifierFlags();
 71                let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
 72                let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
 73                let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
 74                let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
 75                let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
 76
 77                let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
 78
 79                Some(Self::KeyDown {
 80                    keystroke: Keystroke {
 81                        ctrl,
 82                        alt,
 83                        shift,
 84                        cmd,
 85                        key: unmodified_chars.into(),
 86                    },
 87                    input,
 88                    is_held: native_event.isARepeat() == YES,
 89                })
 90            }
 91            NSEventType::NSKeyUp => {
 92                let modifiers = native_event.modifierFlags();
 93                let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
 94                let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
 95                let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
 96                let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
 97                let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
 98
 99                let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
100
101                Some(Self::KeyUp {
102                    keystroke: Keystroke {
103                        ctrl,
104                        alt,
105                        shift,
106                        cmd,
107                        key: unmodified_chars.into(),
108                    },
109                    input,
110                })
111            }
112            NSEventType::NSLeftMouseDown => {
113                let modifiers = native_event.modifierFlags();
114                window_height.map(|window_height| Self::LeftMouseDown {
115                    position: vec2f(
116                        native_event.locationInWindow().x as f32,
117                        window_height - native_event.locationInWindow().y as f32,
118                    ),
119                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
120                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
121                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
122                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
123                    click_count: native_event.clickCount() as usize,
124                })
125            }
126            NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {
127                position: vec2f(
128                    native_event.locationInWindow().x as f32,
129                    window_height - native_event.locationInWindow().y as f32,
130                ),
131                click_count: native_event.clickCount() as usize,
132            }),
133            NSEventType::NSRightMouseDown => {
134                let modifiers = native_event.modifierFlags();
135                window_height.map(|window_height| Self::RightMouseDown {
136                    position: vec2f(
137                        native_event.locationInWindow().x as f32,
138                        window_height - native_event.locationInWindow().y as f32,
139                    ),
140                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
141                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
142                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
143                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
144                    click_count: native_event.clickCount() as usize,
145                })
146            }
147            NSEventType::NSRightMouseUp => window_height.map(|window_height| Self::RightMouseUp {
148                position: vec2f(
149                    native_event.locationInWindow().x as f32,
150                    window_height - native_event.locationInWindow().y as f32,
151                ),
152                click_count: native_event.clickCount() as usize,
153            }),
154            NSEventType::NSOtherMouseDown => {
155                let direction = match native_event.buttonNumber() {
156                    3 => NavigationDirection::Back,
157                    4 => NavigationDirection::Forward,
158                    // Other mouse buttons aren't tracked currently
159                    _ => return None,
160                };
161
162                let modifiers = native_event.modifierFlags();
163                window_height.map(|window_height| Self::NavigateMouseDown {
164                    position: vec2f(
165                        native_event.locationInWindow().x as f32,
166                        window_height - native_event.locationInWindow().y as f32,
167                    ),
168                    direction,
169                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
170                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
171                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
172                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
173                    click_count: native_event.clickCount() as usize,
174                })
175            }
176            NSEventType::NSOtherMouseUp => {
177                let direction = match native_event.buttonNumber() {
178                    3 => NavigationDirection::Back,
179                    4 => NavigationDirection::Forward,
180                    // Other mouse buttons aren't tracked currently
181                    _ => return None,
182                };
183
184                window_height.map(|window_height| Self::NavigateMouseUp {
185                    position: vec2f(
186                        native_event.locationInWindow().x as f32,
187                        window_height - native_event.locationInWindow().y as f32,
188                    ),
189                    direction,
190                })
191            }
192            NSEventType::NSLeftMouseDragged => window_height.map(|window_height| {
193                let modifiers = native_event.modifierFlags();
194                Self::LeftMouseDragged {
195                    position: vec2f(
196                        native_event.locationInWindow().x as f32,
197                        window_height - native_event.locationInWindow().y as f32,
198                    ),
199                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
200                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
201                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
202                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
203                }
204            }),
205            NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel {
206                position: vec2f(
207                    native_event.locationInWindow().x as f32,
208                    window_height - native_event.locationInWindow().y as f32,
209                ),
210                delta: vec2f(
211                    native_event.scrollingDeltaX() as f32,
212                    native_event.scrollingDeltaY() as f32,
213                ),
214                precise: native_event.hasPreciseScrollingDeltas() == YES,
215            }),
216            NSEventType::NSMouseMoved => window_height.map(|window_height| {
217                let modifiers = native_event.modifierFlags();
218                Self::MouseMoved {
219                    position: vec2f(
220                        native_event.locationInWindow().x as f32,
221                        window_height - native_event.locationInWindow().y as f32,
222                    ),
223                    left_mouse_down: NSEvent::pressedMouseButtons(nil) & 1 != 0,
224                    ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
225                    alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
226                    shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
227                    cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
228                }
229            }),
230            _ => None,
231        }
232    }
233}
234
235unsafe fn get_key_text(
236    native_event: id,
237    cmd: bool,
238    ctrl: bool,
239    function: bool,
240) -> Option<(&'static str, Option<String>)> {
241    let unmodified_chars =
242        CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
243            .to_str()
244            .unwrap();
245
246    let mut input = None;
247    let first_char = unmodified_chars.chars().next()?;
248    use cocoa::appkit::*;
249    const BACKSPACE_KEY: u16 = 0x7f;
250    const ENTER_KEY: u16 = 0x0d;
251    const ESCAPE_KEY: u16 = 0x1b;
252    const TAB_KEY: u16 = 0x09;
253    const SHIFT_TAB_KEY: u16 = 0x19;
254    const SPACE_KEY: u16 = b' ' as u16;
255
256    #[allow(non_upper_case_globals)]
257    let unmodified_chars = match first_char as u16 {
258        SPACE_KEY => {
259            input = Some(" ".to_string());
260            "space"
261        }
262        BACKSPACE_KEY => "backspace",
263        ENTER_KEY => "enter",
264        ESCAPE_KEY => "escape",
265        TAB_KEY => "tab",
266        SHIFT_TAB_KEY => "tab",
267
268        NSUpArrowFunctionKey => "up",
269        NSDownArrowFunctionKey => "down",
270        NSLeftArrowFunctionKey => "left",
271        NSRightArrowFunctionKey => "right",
272        NSPageUpFunctionKey => "pageup",
273        NSPageDownFunctionKey => "pagedown",
274        NSDeleteFunctionKey => "delete",
275        NSF1FunctionKey => "f1",
276        NSF2FunctionKey => "f2",
277        NSF3FunctionKey => "f3",
278        NSF4FunctionKey => "f4",
279        NSF5FunctionKey => "f5",
280        NSF6FunctionKey => "f6",
281        NSF7FunctionKey => "f7",
282        NSF8FunctionKey => "f8",
283        NSF9FunctionKey => "f9",
284        NSF10FunctionKey => "f10",
285        NSF11FunctionKey => "f11",
286        NSF12FunctionKey => "f12",
287
288        _ => {
289            if !cmd && !ctrl && !function {
290                input = Some(
291                    CStr::from_ptr(native_event.characters().UTF8String() as *mut c_char)
292                        .to_str()
293                        .unwrap()
294                        .into(),
295                );
296            }
297            unmodified_chars
298        }
299    };
300
301    Some((unmodified_chars, input))
302}