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}