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