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}