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