event.rs

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