events.rs

  1use crate::{
  2    platform::mac::NSStringExt, point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
  3    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
  4    MouseUpEvent, NavigationDirection, Pixels, PlatformInput, ScrollDelta, ScrollWheelEvent,
  5    TouchPhase,
  6};
  7use cocoa::{
  8    appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
  9    base::{id, YES},
 10};
 11use core_graphics::{
 12    event::{CGEvent, CGEventFlags, CGKeyCode},
 13    event_source::{CGEventSource, CGEventSourceStateID},
 14};
 15use metal::foreign_types::ForeignType as _;
 16use objc::{class, msg_send, sel, sel_impl};
 17use std::{borrow::Cow, mem, ptr, sync::Once};
 18
 19const BACKSPACE_KEY: u16 = 0x7f;
 20const SPACE_KEY: u16 = b' ' as u16;
 21const ENTER_KEY: u16 = 0x0d;
 22const NUMPAD_ENTER_KEY: u16 = 0x03;
 23const ESCAPE_KEY: u16 = 0x1b;
 24const TAB_KEY: u16 = 0x09;
 25const SHIFT_TAB_KEY: u16 = 0x19;
 26
 27fn synthesize_keyboard_event(code: CGKeyCode) -> CGEvent {
 28    static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
 29    static INIT_EVENT_SOURCE: Once = Once::new();
 30
 31    INIT_EVENT_SOURCE.call_once(|| {
 32        let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
 33        unsafe {
 34            EVENT_SOURCE = source.as_ptr();
 35        };
 36        mem::forget(source);
 37    });
 38
 39    let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
 40    let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
 41    mem::forget(source);
 42    event
 43}
 44
 45pub fn key_to_native(key: &str) -> Cow<str> {
 46    use cocoa::appkit::*;
 47    let code = match key {
 48        "space" => SPACE_KEY,
 49        "backspace" => BACKSPACE_KEY,
 50        "up" => NSUpArrowFunctionKey,
 51        "down" => NSDownArrowFunctionKey,
 52        "left" => NSLeftArrowFunctionKey,
 53        "right" => NSRightArrowFunctionKey,
 54        "pageup" => NSPageUpFunctionKey,
 55        "pagedown" => NSPageDownFunctionKey,
 56        "home" => NSHomeFunctionKey,
 57        "end" => NSEndFunctionKey,
 58        "delete" => NSDeleteFunctionKey,
 59        "insert" => NSHelpFunctionKey,
 60        "f1" => NSF1FunctionKey,
 61        "f2" => NSF2FunctionKey,
 62        "f3" => NSF3FunctionKey,
 63        "f4" => NSF4FunctionKey,
 64        "f5" => NSF5FunctionKey,
 65        "f6" => NSF6FunctionKey,
 66        "f7" => NSF7FunctionKey,
 67        "f8" => NSF8FunctionKey,
 68        "f9" => NSF9FunctionKey,
 69        "f10" => NSF10FunctionKey,
 70        "f11" => NSF11FunctionKey,
 71        "f12" => NSF12FunctionKey,
 72        "f13" => NSF13FunctionKey,
 73        "f14" => NSF14FunctionKey,
 74        "f15" => NSF15FunctionKey,
 75        "f16" => NSF16FunctionKey,
 76        "f17" => NSF17FunctionKey,
 77        "f18" => NSF18FunctionKey,
 78        "f19" => NSF19FunctionKey,
 79        _ => return Cow::Borrowed(key),
 80    };
 81    Cow::Owned(String::from_utf16(&[code]).unwrap())
 82}
 83
 84unsafe fn read_modifiers(native_event: id) -> Modifiers {
 85    let modifiers = native_event.modifierFlags();
 86    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
 87    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
 88    let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
 89    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
 90    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
 91
 92    Modifiers {
 93        control,
 94        alt,
 95        shift,
 96        platform: command,
 97        function,
 98    }
 99}
100
101impl PlatformInput {
102    pub(crate) unsafe fn from_native(
103        native_event: id,
104        window_height: Option<Pixels>,
105    ) -> Option<Self> {
106        let event_type = native_event.eventType();
107
108        // Filter out event types that aren't in the NSEventType enum.
109        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
110        match event_type as u64 {
111            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
112                return None;
113            }
114            _ => {}
115        }
116
117        match event_type {
118            NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
119                modifiers: read_modifiers(native_event),
120            })),
121            NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
122                keystroke: parse_keystroke(native_event),
123                is_held: native_event.isARepeat() == YES,
124            })),
125            NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
126                keystroke: parse_keystroke(native_event),
127            })),
128            NSEventType::NSLeftMouseDown
129            | NSEventType::NSRightMouseDown
130            | NSEventType::NSOtherMouseDown => {
131                let button = match native_event.buttonNumber() {
132                    0 => MouseButton::Left,
133                    1 => MouseButton::Right,
134                    2 => MouseButton::Middle,
135                    3 => MouseButton::Navigate(NavigationDirection::Back),
136                    4 => MouseButton::Navigate(NavigationDirection::Forward),
137                    // Other mouse buttons aren't tracked currently
138                    _ => return None,
139                };
140                window_height.map(|window_height| {
141                    Self::MouseDown(MouseDownEvent {
142                        button,
143                        position: point(
144                            px(native_event.locationInWindow().x as f32),
145                            // MacOS screen coordinates are relative to bottom left
146                            window_height - px(native_event.locationInWindow().y as f32),
147                        ),
148                        modifiers: read_modifiers(native_event),
149                        click_count: native_event.clickCount() as usize,
150                        first_mouse: false,
151                    })
152                })
153            }
154            NSEventType::NSLeftMouseUp
155            | NSEventType::NSRightMouseUp
156            | NSEventType::NSOtherMouseUp => {
157                let button = match native_event.buttonNumber() {
158                    0 => MouseButton::Left,
159                    1 => MouseButton::Right,
160                    2 => MouseButton::Middle,
161                    3 => MouseButton::Navigate(NavigationDirection::Back),
162                    4 => MouseButton::Navigate(NavigationDirection::Forward),
163                    // Other mouse buttons aren't tracked currently
164                    _ => return None,
165                };
166
167                window_height.map(|window_height| {
168                    Self::MouseUp(MouseUpEvent {
169                        button,
170                        position: point(
171                            px(native_event.locationInWindow().x as f32),
172                            window_height - px(native_event.locationInWindow().y as f32),
173                        ),
174                        modifiers: read_modifiers(native_event),
175                        click_count: native_event.clickCount() as usize,
176                    })
177                })
178            }
179            NSEventType::NSScrollWheel => window_height.map(|window_height| {
180                let phase = match native_event.phase() {
181                    NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
182                        TouchPhase::Started
183                    }
184                    NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
185                    _ => TouchPhase::Moved,
186                };
187
188                let raw_data = point(
189                    native_event.scrollingDeltaX() as f32,
190                    native_event.scrollingDeltaY() as f32,
191                );
192
193                let delta = if native_event.hasPreciseScrollingDeltas() == YES {
194                    ScrollDelta::Pixels(raw_data.map(px))
195                } else {
196                    ScrollDelta::Lines(raw_data)
197                };
198
199                Self::ScrollWheel(ScrollWheelEvent {
200                    position: point(
201                        px(native_event.locationInWindow().x as f32),
202                        window_height - px(native_event.locationInWindow().y as f32),
203                    ),
204                    delta,
205                    touch_phase: phase,
206                    modifiers: read_modifiers(native_event),
207                })
208            }),
209            NSEventType::NSLeftMouseDragged
210            | NSEventType::NSRightMouseDragged
211            | NSEventType::NSOtherMouseDragged => {
212                let pressed_button = match native_event.buttonNumber() {
213                    0 => MouseButton::Left,
214                    1 => MouseButton::Right,
215                    2 => MouseButton::Middle,
216                    3 => MouseButton::Navigate(NavigationDirection::Back),
217                    4 => MouseButton::Navigate(NavigationDirection::Forward),
218                    // Other mouse buttons aren't tracked currently
219                    _ => return None,
220                };
221
222                window_height.map(|window_height| {
223                    Self::MouseMove(MouseMoveEvent {
224                        pressed_button: Some(pressed_button),
225                        position: point(
226                            px(native_event.locationInWindow().x as f32),
227                            window_height - px(native_event.locationInWindow().y as f32),
228                        ),
229                        modifiers: read_modifiers(native_event),
230                    })
231                })
232            }
233            NSEventType::NSMouseMoved => window_height.map(|window_height| {
234                Self::MouseMove(MouseMoveEvent {
235                    position: point(
236                        px(native_event.locationInWindow().x as f32),
237                        window_height - px(native_event.locationInWindow().y as f32),
238                    ),
239                    pressed_button: None,
240                    modifiers: read_modifiers(native_event),
241                })
242            }),
243            NSEventType::NSMouseExited => window_height.map(|window_height| {
244                Self::MouseExited(MouseExitEvent {
245                    position: point(
246                        px(native_event.locationInWindow().x as f32),
247                        window_height - px(native_event.locationInWindow().y as f32),
248                    ),
249
250                    pressed_button: None,
251                    modifiers: read_modifiers(native_event),
252                })
253            }),
254            _ => None,
255        }
256    }
257}
258
259unsafe fn parse_keystroke(native_event: id) -> Keystroke {
260    use cocoa::appkit::*;
261
262    let mut chars_ignoring_modifiers = native_event
263        .charactersIgnoringModifiers()
264        .to_str()
265        .to_string();
266    let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
267    let modifiers = native_event.modifierFlags();
268
269    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
270    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
271    let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
272    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
273    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
274        && first_char.map_or(true, |ch| {
275            !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
276        });
277
278    #[allow(non_upper_case_globals)]
279    let key = match first_char {
280        Some(SPACE_KEY) => "space".to_string(),
281        Some(BACKSPACE_KEY) => "backspace".to_string(),
282        Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
283        Some(ESCAPE_KEY) => "escape".to_string(),
284        Some(TAB_KEY) => "tab".to_string(),
285        Some(SHIFT_TAB_KEY) => "tab".to_string(),
286        Some(NSUpArrowFunctionKey) => "up".to_string(),
287        Some(NSDownArrowFunctionKey) => "down".to_string(),
288        Some(NSLeftArrowFunctionKey) => "left".to_string(),
289        Some(NSRightArrowFunctionKey) => "right".to_string(),
290        Some(NSPageUpFunctionKey) => "pageup".to_string(),
291        Some(NSPageDownFunctionKey) => "pagedown".to_string(),
292        Some(NSHomeFunctionKey) => "home".to_string(),
293        Some(NSEndFunctionKey) => "end".to_string(),
294        Some(NSDeleteFunctionKey) => "delete".to_string(),
295        // Observed Insert==NSHelpFunctionKey not NSInsertFunctionKey.
296        Some(NSHelpFunctionKey) => "insert".to_string(),
297        Some(NSF1FunctionKey) => "f1".to_string(),
298        Some(NSF2FunctionKey) => "f2".to_string(),
299        Some(NSF3FunctionKey) => "f3".to_string(),
300        Some(NSF4FunctionKey) => "f4".to_string(),
301        Some(NSF5FunctionKey) => "f5".to_string(),
302        Some(NSF6FunctionKey) => "f6".to_string(),
303        Some(NSF7FunctionKey) => "f7".to_string(),
304        Some(NSF8FunctionKey) => "f8".to_string(),
305        Some(NSF9FunctionKey) => "f9".to_string(),
306        Some(NSF10FunctionKey) => "f10".to_string(),
307        Some(NSF11FunctionKey) => "f11".to_string(),
308        Some(NSF12FunctionKey) => "f12".to_string(),
309        Some(NSF13FunctionKey) => "f13".to_string(),
310        Some(NSF14FunctionKey) => "f14".to_string(),
311        Some(NSF15FunctionKey) => "f15".to_string(),
312        Some(NSF16FunctionKey) => "f16".to_string(),
313        Some(NSF17FunctionKey) => "f17".to_string(),
314        Some(NSF18FunctionKey) => "f18".to_string(),
315        Some(NSF19FunctionKey) => "f19".to_string(),
316        _ => {
317            let mut chars_ignoring_modifiers_and_shift =
318                chars_for_modified_key(native_event.keyCode(), false, false);
319
320            // Honor ⌘ when Dvorak-QWERTY is used.
321            let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
322            if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
323                chars_ignoring_modifiers =
324                    chars_for_modified_key(native_event.keyCode(), true, shift);
325                chars_ignoring_modifiers_and_shift = chars_with_cmd;
326            }
327
328            if shift {
329                if chars_ignoring_modifiers_and_shift
330                    == chars_ignoring_modifiers.to_ascii_lowercase()
331                {
332                    chars_ignoring_modifiers_and_shift
333                } else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
334                    shift = false;
335                    chars_ignoring_modifiers
336                } else {
337                    chars_ignoring_modifiers
338                }
339            } else {
340                chars_ignoring_modifiers
341            }
342        }
343    };
344
345    Keystroke {
346        modifiers: Modifiers {
347            control,
348            alt,
349            shift,
350            platform: command,
351            function,
352        },
353        key,
354        ime_key: None,
355    }
356}
357
358fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
359    // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
360    // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
361    // an event with the given flags instead lets us access `characters`, which always
362    // returns a valid string.
363    let event = synthesize_keyboard_event(code);
364
365    let mut flags = CGEventFlags::empty();
366    if cmd {
367        flags |= CGEventFlags::CGEventFlagCommand;
368    }
369    if shift {
370        flags |= CGEventFlags::CGEventFlagShift;
371    }
372    event.set_flags(flags);
373
374    unsafe {
375        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
376        event.characters().to_str().to_string()
377    }
378}