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}