xref: /aosp_15_r20/external/crosvm/gpu_display/src/gpu_display_win/mouse_input_manager.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::ffi::c_void;
6 use std::mem;
7 use std::ops::ControlFlow;
8 use std::ptr::null;
9 use std::ptr::null_mut;
10 
11 use anyhow::Context;
12 use anyhow::Result;
13 use base::error;
14 use base::info;
15 use base::warn;
16 use euclid::point2;
17 use euclid::size2;
18 use euclid::Box2D;
19 use euclid::Point2D;
20 use euclid::Size2D;
21 use euclid::Transform2D;
22 use euclid::Vector2D;
23 use linux_input_sys::virtio_input_event;
24 use smallvec::smallvec;
25 use smallvec::SmallVec;
26 use win_util::syscall_bail;
27 use winapi::shared::minwindef::LOWORD;
28 use winapi::shared::minwindef::LPARAM;
29 use winapi::shared::minwindef::LRESULT;
30 use winapi::shared::minwindef::UINT;
31 use winapi::shared::windef::RECT;
32 use winapi::shared::windowsx::GET_X_LPARAM;
33 use winapi::shared::windowsx::GET_Y_LPARAM;
34 use winapi::um::errhandlingapi::GetLastError;
35 use winapi::um::winuser::ClipCursor;
36 use winapi::um::winuser::GetRawInputData;
37 use winapi::um::winuser::GetSystemMetrics;
38 use winapi::um::winuser::IntersectRect;
39 use winapi::um::winuser::ReleaseCapture;
40 use winapi::um::winuser::SetCapture;
41 use winapi::um::winuser::SetCursor;
42 use winapi::um::winuser::SetRect;
43 use winapi::um::winuser::GET_WHEEL_DELTA_WPARAM;
44 use winapi::um::winuser::HRAWINPUT;
45 use winapi::um::winuser::HTCLIENT;
46 use winapi::um::winuser::MA_ACTIVATE;
47 use winapi::um::winuser::MA_NOACTIVATE;
48 use winapi::um::winuser::MK_LBUTTON;
49 use winapi::um::winuser::MOUSE_MOVE_ABSOLUTE;
50 use winapi::um::winuser::MOUSE_MOVE_RELATIVE;
51 use winapi::um::winuser::MOUSE_VIRTUAL_DESKTOP;
52 use winapi::um::winuser::RAWINPUT;
53 use winapi::um::winuser::RAWINPUTHEADER;
54 use winapi::um::winuser::RID_INPUT;
55 use winapi::um::winuser::RIM_TYPEMOUSE;
56 use winapi::um::winuser::SM_CXSCREEN;
57 use winapi::um::winuser::SM_CXVIRTUALSCREEN;
58 use winapi::um::winuser::SM_CYSCREEN;
59 use winapi::um::winuser::SM_CYVIRTUALSCREEN;
60 use winapi::um::winuser::SM_XVIRTUALSCREEN;
61 use winapi::um::winuser::SM_YVIRTUALSCREEN;
62 use winapi::um::winuser::WHEEL_DELTA;
63 
64 use super::math_util::Rect;
65 use super::math_util::RectExtension;
66 use super::window::BasicWindow;
67 use super::window::GuiWindow;
68 use super::window_message_dispatcher::DisplayEventDispatcher;
69 use super::window_message_processor::MouseMessage;
70 use super::window_message_processor::WindowMessage;
71 use super::window_message_processor::WindowPosMessage;
72 use super::HostWindowSpace;
73 use super::MouseMode;
74 use super::VirtualDisplaySpace;
75 use crate::EventDeviceKind;
76 
77 // Used as the multi-touch slot & tracking IDs.
78 // See https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html for further details.
79 const PRIMARY_FINGER_ID: i32 = 0;
80 
81 // The fixed amount of pixels to remove in each side of the client window when confining the cursor
82 // in relative mouse mode
83 const BORDER_OFFSET: i32 = 10;
84 
85 /// Responsible for capturing input from a HWND and forwarding it to the guest.
86 pub(crate) struct MouseInputManager {
87     display_event_dispatcher: DisplayEventDispatcher,
88     mouse_pos: Option<Point2D<f64, HostWindowSpace>>,
89     /// Accumulates the delta value for mouse/touchpad scrolling. The doc for `WHEEL_DELTA` says it
90     /// "... is the threshold for action to be taken, and one such action (for example, scrolling
91     /// one increment) should occur for each delta". While the mouse wheel produces exactly
92     /// `WHEEL_DELTA` every time it is scrolled, a touchpad may produce much smaller amounts, so we
93     /// would want to accumulate it until `WHEEL_DELTA` is reached.
94     accumulated_wheel_delta: i16,
95     mouse_mode: MouseMode,
96     /// Used to transform coordinates from the host window space to the virtual device space
97     transform: Transform2D<f64, HostWindowSpace, VirtualDisplaySpace>,
98     /// A 2D box in virtual device coordinate space. If a touch event happens outside the box, the
99     /// event won't be processed.
100     virtual_display_box: Box2D<f64, VirtualDisplaySpace>,
101 }
102 
103 impl MouseInputManager {
new( _window: &GuiWindow, transform: Transform2D<f64, HostWindowSpace, VirtualDisplaySpace>, virtual_display_size: Size2D<u32, VirtualDisplaySpace>, display_event_dispatcher: DisplayEventDispatcher, ) -> Self104     pub fn new(
105         _window: &GuiWindow,
106         transform: Transform2D<f64, HostWindowSpace, VirtualDisplaySpace>,
107         virtual_display_size: Size2D<u32, VirtualDisplaySpace>,
108         display_event_dispatcher: DisplayEventDispatcher,
109     ) -> Self {
110         let virtual_display_box = Box2D::new(
111             Point2D::zero(),
112             Point2D::zero().add_size(&virtual_display_size),
113         )
114         .to_f64();
115         Self {
116             display_event_dispatcher,
117             mouse_pos: None,
118             accumulated_wheel_delta: 0,
119             mouse_mode: MouseMode::Touchscreen,
120             transform,
121             virtual_display_box,
122         }
123     }
124 
125     /// Processes raw input events for the mouse.
126     ///
127     /// Raw input is required to properly create relative motion events. A previous version used
128     /// simulated relative motion based on WM_MOUSEMOVE events (which provide absolute position).
129     /// That version worked surprisingly well, except it had one fatal flaw: the guest & host
130     /// pointer are not necessarily in sync. This means the host's pointer can hit the edge of the
131     /// VM's display window, but the guest's pointer is still in the middle of the screen; for
132     /// example, the host pointer hits the left edge and stops generating position change events,
133     /// but the guest pointer is still in the middle of the screen. Because of that desync, the left
134     /// half of the guest's screen is inaccessible. To avoid that flaw, we use raw input to get
135     /// the actual relative input events directly from Windows.
136     #[inline]
handle_raw_input_event(&mut self, window: &GuiWindow, input_lparam: HRAWINPUT)137     pub fn handle_raw_input_event(&mut self, window: &GuiWindow, input_lparam: HRAWINPUT) {
138         if !self.should_capture_cursor(window) {
139             return;
140         }
141 
142         let mut promised_size: UINT = 0;
143         // SAFETY:
144         // Safe because promised_size is guaranteed to exist.
145         let ret = unsafe {
146             GetRawInputData(
147                 input_lparam,
148                 RID_INPUT,
149                 null_mut(),
150                 &mut promised_size as *mut UINT,
151                 mem::size_of::<RAWINPUTHEADER>() as u32,
152             )
153         };
154         if ret == UINT::MAX {
155             error!(
156                 "Relative mouse error: GetRawInputData failed to get size of events: {}",
157                 // SAFETY: trivially safe
158                 unsafe { GetLastError() }
159             );
160             return;
161         }
162         if promised_size == 0 {
163             // No actual raw input to process
164             return;
165         }
166 
167         // buf should be 8 byte aligned because it is used as a RAWINPUT struct. Note that this
168         // buffer could be slightly larger, but that's necessary for safety.
169         let mut buf: Vec<u64> = Vec::with_capacity(
170             promised_size as usize / mem::size_of::<u64>()
171                 + promised_size as usize % mem::size_of::<u64>(),
172         );
173         let mut buf_size: UINT = promised_size as UINT;
174 
175         // SAFETY:
176         // Safe because buf is guaranteed to exist, and be of sufficient size because we checked the
177         // required size previously.
178         let input_size = unsafe {
179             GetRawInputData(
180                 input_lparam,
181                 RID_INPUT,
182                 buf.as_mut_ptr() as *mut c_void,
183                 &mut buf_size as *mut UINT,
184                 mem::size_of::<RAWINPUTHEADER>() as u32,
185             )
186         };
187         if input_size == UINT::MAX {
188             error!(
189                 "Relative mouse error: GetRawInputData failed to get events: {}",
190                 // SAFETY: trivially safe
191                 unsafe { GetLastError() }
192             );
193             return;
194         }
195         if input_size != promised_size {
196             error!(
197                 "GetRawInputData returned {}, but was expected to return {}.",
198                 input_size, promised_size
199             );
200             return;
201         }
202 
203         // SAFETY:
204         // Safe because buf is guaranteed to exist, and it was correctly populated by the previous
205         // call to GetRawInputData.
206         let raw_input = unsafe { (buf.as_ptr() as *const RAWINPUT).as_ref().unwrap() };
207 
208         self.process_valid_raw_input_mouse(window, raw_input)
209     }
210 
211     /// Processes a RAWINPUT event for a mouse and dispatches the appropriate virtio_input_events
212     /// to the guest.
213     #[inline]
process_valid_raw_input_mouse(&mut self, window: &GuiWindow, raw_input: &RAWINPUT)214     fn process_valid_raw_input_mouse(&mut self, window: &GuiWindow, raw_input: &RAWINPUT) {
215         if raw_input.header.dwType != RIM_TYPEMOUSE {
216             error!("Receiving non-mouse RAWINPUT.");
217             return;
218         }
219 
220         // SAFETY:
221         // Safe because we checked that raw_input.data is a mouse event.
222         let mouse = unsafe { raw_input.data.mouse() };
223 
224         // MOUSE_MOVE_RELATIVE is a bitwise flag value that is zero; in other words, it is
225         // considered "set" if the 0th bit is zero. For that reason, we mask off the relevant
226         // bit, and assert it is equal to the flag value (which is zero).
227         let mouse_motion = if mouse.usFlags & 0x1 == MOUSE_MOVE_RELATIVE {
228             // Most mice will report as relative, which makes this simple.
229             Some(Vector2D::<f64, HostWindowSpace>::new(
230                 mouse.lLastX as f64,
231                 mouse.lLastY as f64,
232             ))
233         } else if mouse.usFlags & MOUSE_MOVE_ABSOLUTE == MOUSE_MOVE_ABSOLUTE {
234             // Trackpads may present as "absolute" devices, but we want to show a relative
235             // device to the guest. We simulate relative motion in that case by figuring out
236             // how much the mouse has moved relative to its last known position.
237             // `lLastX` and `lLastY` contain normalized absolute coordinates, and they should be
238             // mapped to the primary monitor coordinate space first:
239             // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawmouse#remarks
240             let primary_monitor_rect = get_primary_monitor_rect(
241                 /* is_virtual_desktop= */
242                 mouse.usFlags & MOUSE_VIRTUAL_DESKTOP == MOUSE_VIRTUAL_DESKTOP,
243             )
244             .to_f64();
245             let new_mouse_pos = point2(
246                 mouse.lLastX as f64 * primary_monitor_rect.width() / 65535.0
247                     + primary_monitor_rect.min_x(),
248                 mouse.lLastY as f64 * primary_monitor_rect.height() / 65535.0
249                     + primary_monitor_rect.min_y(),
250             );
251             let motion = self.mouse_pos.as_ref().map(|pos| new_mouse_pos - *pos);
252             self.mouse_pos = Some(new_mouse_pos);
253             motion
254         } else {
255             // Other non-motion events we don't care about.
256             None
257         };
258 
259         if let Some(mouse_motion) = mouse_motion {
260             let events = self.create_relative_mouse_events(mouse_motion);
261             self.display_event_dispatcher.dispatch(
262                 window,
263                 events.as_slice(),
264                 EventDeviceKind::Mouse,
265             );
266         }
267     }
268 
269     #[inline]
create_relative_mouse_events( &self, mouse_motion: Vector2D<f64, HostWindowSpace>, ) -> SmallVec<[virtio_input_event; 2]>270     fn create_relative_mouse_events(
271         &self,
272         mouse_motion: Vector2D<f64, HostWindowSpace>,
273     ) -> SmallVec<[virtio_input_event; 2]> {
274         smallvec![
275             virtio_input_event::relative_x(mouse_motion.x as i32),
276             virtio_input_event::relative_y(mouse_motion.y as i32),
277         ]
278     }
279 
280     /// Converts the given host point to a guest point, clipping it to the host window viewport.
281     #[inline]
to_guest_point( &self, pos: Point2D<i32, HostWindowSpace>, ) -> Option<Point2D<i32, VirtualDisplaySpace>>282     fn to_guest_point(
283         &self,
284         pos: Point2D<i32, HostWindowSpace>,
285     ) -> Option<Point2D<i32, VirtualDisplaySpace>> {
286         let pos = self.transform.transform_point(pos.to_f64());
287         let pos = pos.clamp(self.virtual_display_box.min, self.virtual_display_box.max);
288         Some(pos.round().to_i32())
289     }
290 
291     /// Takes a down or up event and converts it into suitable multi touch events. Those events are
292     /// then dispatched to the guest. Note that a "click" and movement of the cursor with a button
293     /// down are represented as the same event.
handle_multi_touch_finger( &mut self, window: &GuiWindow, pos: Point2D<i32, HostWindowSpace>, pressed: bool, finger_id: i32, )294     fn handle_multi_touch_finger(
295         &mut self,
296         window: &GuiWindow,
297         pos: Point2D<i32, HostWindowSpace>,
298         pressed: bool,
299         finger_id: i32,
300     ) {
301         let pos = match self.to_guest_point(pos) {
302             Some(pos) => pos,
303             None => return,
304         };
305         if pressed {
306             self.display_event_dispatcher.dispatch(
307                 window,
308                 &[
309                     virtio_input_event::multitouch_slot(finger_id),
310                     virtio_input_event::multitouch_tracking_id(finger_id),
311                     virtio_input_event::multitouch_absolute_x(pos.x),
312                     virtio_input_event::multitouch_absolute_y(pos.y),
313                     virtio_input_event::touch(pressed),
314                 ],
315                 EventDeviceKind::Touchscreen,
316             );
317         } else {
318             self.display_event_dispatcher.dispatch(
319                 window,
320                 &[
321                     virtio_input_event::multitouch_slot(finger_id),
322                     virtio_input_event::multitouch_tracking_id(-1),
323                     virtio_input_event::touch(false),
324                 ],
325                 EventDeviceKind::Touchscreen,
326             );
327         }
328     }
329 
330     /// Handles WM_MOUSEMOVE events. Note that these events are NOT used for the relative mouse
331     /// (we use raw input instead).
332     #[inline]
handle_mouse_move( &mut self, window: &GuiWindow, pos: Point2D<i32, HostWindowSpace>, left_down: bool, )333     fn handle_mouse_move(
334         &mut self,
335         window: &GuiWindow,
336         pos: Point2D<i32, HostWindowSpace>,
337         left_down: bool,
338     ) {
339         if let MouseMode::Touchscreen { .. } = self.mouse_mode {
340             if left_down {
341                 self.handle_multi_touch_finger(window, pos, left_down, PRIMARY_FINGER_ID);
342             }
343         }
344     }
345 
346     /// Sets or releases mouse "capture" when a mouse button is pressed or released. This lets us
347     /// track motion beyond the window bounds, which is useful for drag gestures in the guest.
adjust_capture_on_mouse_button(&self, down: bool, window: &GuiWindow)348     fn adjust_capture_on_mouse_button(&self, down: bool, window: &GuiWindow) {
349         if let MouseMode::Touchscreen = self.mouse_mode {
350             if down {
351                 // SAFETY: safe because window is alive during the call, and we don't care if the
352                 // function fails to capture the mouse because there's nothing we can do about that
353                 // anyway.
354                 unsafe { SetCapture(window.handle()) };
355             }
356         }
357 
358         if !down {
359             // SAFETY: safe because no memory is involved.
360             if unsafe { ReleaseCapture() } == 0 {
361                 // SAFETY: trivially safe
362                 warn!("failed to release capture: {}", unsafe { GetLastError() });
363             }
364         }
365     }
366 
handle_mouse_button_left( &mut self, pos: Point2D<i32, HostWindowSpace>, down: bool, window: &GuiWindow, )367     fn handle_mouse_button_left(
368         &mut self,
369         pos: Point2D<i32, HostWindowSpace>,
370         down: bool,
371         window: &GuiWindow,
372     ) {
373         self.adjust_capture_on_mouse_button(down, window);
374         match self.mouse_mode {
375             MouseMode::Touchscreen { .. } => {
376                 self.handle_multi_touch_finger(window, pos, down, PRIMARY_FINGER_ID);
377             }
378             MouseMode::Relative => {
379                 self.display_event_dispatcher.dispatch(
380                     window,
381                     &[virtio_input_event::left_click(down)],
382                     EventDeviceKind::Mouse,
383                 );
384             }
385         }
386     }
387 
handle_mouse_button_right(&mut self, window: &GuiWindow, down: bool)388     fn handle_mouse_button_right(&mut self, window: &GuiWindow, down: bool) {
389         if let MouseMode::Relative = self.mouse_mode {
390             self.display_event_dispatcher.dispatch(
391                 window,
392                 &[virtio_input_event::right_click(down)],
393                 EventDeviceKind::Mouse,
394             );
395         }
396     }
397 
handle_mouse_button_middle(&mut self, window: &GuiWindow, down: bool)398     fn handle_mouse_button_middle(&mut self, window: &GuiWindow, down: bool) {
399         if let MouseMode::Relative = self.mouse_mode {
400             self.display_event_dispatcher.dispatch(
401                 window,
402                 &[virtio_input_event::middle_click(down)],
403                 EventDeviceKind::Mouse,
404             );
405         }
406     }
407 
handle_mouse_button_forward(&mut self, window: &GuiWindow, down: bool)408     fn handle_mouse_button_forward(&mut self, window: &GuiWindow, down: bool) {
409         if let MouseMode::Relative = self.mouse_mode {
410             self.display_event_dispatcher.dispatch(
411                 window,
412                 &[virtio_input_event::forward_click(down)],
413                 EventDeviceKind::Mouse,
414             );
415         }
416     }
417 
handle_mouse_button_back(&mut self, window: &GuiWindow, down: bool)418     fn handle_mouse_button_back(&mut self, window: &GuiWindow, down: bool) {
419         if let MouseMode::Relative = self.mouse_mode {
420             self.display_event_dispatcher.dispatch(
421                 window,
422                 &[virtio_input_event::back_click(down)],
423                 EventDeviceKind::Mouse,
424             );
425         }
426     }
427 
set_mouse_mode(&mut self, window: &GuiWindow, mode: MouseMode)428     fn set_mouse_mode(&mut self, window: &GuiWindow, mode: MouseMode) {
429         info!(
430             "requested mouse mode switch to {:?} (current mode is: {:?})",
431             mode, self.mouse_mode
432         );
433         if mode == self.mouse_mode {
434             return;
435         }
436 
437         self.mouse_mode = mode;
438         self.mouse_pos = None;
439         if let Err(e) = self.adjust_cursor_capture(window) {
440             error!(
441                 "Failed to adjust cursor capture on mouse mode change: {:?}",
442                 e
443             )
444         }
445     }
446 
handle_mouse_wheel( &mut self, window: &GuiWindow, z_delta: i16, _cursor_pos: Option<Point2D<i32, HostWindowSpace>>, )447     fn handle_mouse_wheel(
448         &mut self,
449         window: &GuiWindow,
450         z_delta: i16,
451         _cursor_pos: Option<Point2D<i32, HostWindowSpace>>,
452     ) {
453         let accumulated_delta = self.accumulated_wheel_delta + z_delta;
454         self.accumulated_wheel_delta = accumulated_delta % WHEEL_DELTA;
455         let scaled_delta = accumulated_delta / WHEEL_DELTA;
456         if scaled_delta == 0 {
457             return;
458         }
459         let deivce_kind = match self.mouse_mode {
460             MouseMode::Relative => EventDeviceKind::Mouse,
461             MouseMode::Touchscreen => return,
462         };
463         self.display_event_dispatcher.dispatch(
464             window,
465             &[virtio_input_event::wheel(scaled_delta as i32)],
466             deivce_kind,
467         );
468     }
469 
update_host_to_guest_transform( &mut self, transform: Transform2D<f64, HostWindowSpace, VirtualDisplaySpace>, )470     pub fn update_host_to_guest_transform(
471         &mut self,
472         transform: Transform2D<f64, HostWindowSpace, VirtualDisplaySpace>,
473     ) {
474         self.transform = transform;
475     }
476 
477     /// Possible return values:
478     /// 1. `ControlFlow::Continue`, should continue invoking other modules, such as the window
479     ///    manager, to perform more processing.
480     /// 2. `ControlFlow::Break(Some)`, should skip any other processing and return the value.
481     /// 3. `ControlFlow::Break(None)`, should immediately perform default processing.
482     #[inline]
handle_window_message( &mut self, window: &GuiWindow, message: &WindowMessage, ) -> ControlFlow<Option<LRESULT>>483     pub fn handle_window_message(
484         &mut self,
485         window: &GuiWindow,
486         message: &WindowMessage,
487     ) -> ControlFlow<Option<LRESULT>> {
488         match message {
489             WindowMessage::WindowActivate { .. }
490             | WindowMessage::WindowPos(WindowPosMessage::EnterSizeMove)
491             | WindowMessage::WindowPos(WindowPosMessage::ExitSizeMove)
492             | WindowMessage::WindowPos(WindowPosMessage::WindowPosChanged { .. }) => {
493                 if let Err(e) = self.adjust_cursor_capture(window) {
494                     error!("Failed to adjust cursor capture on {:?}: {:?}", message, e)
495                 }
496             }
497             WindowMessage::Mouse(mouse_message) => {
498                 return self.handle_mouse_message(window, mouse_message);
499             }
500             _ => (),
501         }
502         ControlFlow::Continue(())
503     }
504 
505     /// Possible return values are documented at `handle_window_message()`.
506     #[inline]
handle_mouse_message( &mut self, window: &GuiWindow, message: &MouseMessage, ) -> ControlFlow<Option<LRESULT>>507     fn handle_mouse_message(
508         &mut self,
509         window: &GuiWindow,
510         message: &MouseMessage,
511     ) -> ControlFlow<Option<LRESULT>> {
512         match message {
513             MouseMessage::MouseMove { w_param, l_param } => {
514                 // Safe because `l_param` comes from the window message and should contain valid
515                 // numbers.
516                 let (x, y) = get_x_y_from_lparam(*l_param);
517                 self.handle_mouse_move(
518                     window,
519                     Point2D::<_, HostWindowSpace>::new(x, y),
520                     w_param & MK_LBUTTON != 0,
521                 );
522             }
523             MouseMessage::LeftMouseButton { is_down, l_param } => {
524                 // Safe because `l_param` comes from the window message and should contain valid
525                 // numbers.
526                 let (x, y) = get_x_y_from_lparam(*l_param);
527                 self.handle_mouse_button_left(
528                     Point2D::<_, HostWindowSpace>::new(x, y),
529                     *is_down,
530                     window,
531                 );
532             }
533             MouseMessage::RightMouseButton { is_down } => {
534                 self.handle_mouse_button_right(window, *is_down)
535             }
536             MouseMessage::MiddleMouseButton { is_down } => {
537                 self.handle_mouse_button_middle(window, *is_down)
538             }
539             MouseMessage::ForwardMouseButton { is_down } => {
540                 self.handle_mouse_button_forward(window, *is_down)
541             }
542             MouseMessage::BackMouseButton { is_down } => {
543                 self.handle_mouse_button_back(window, *is_down)
544             }
545             MouseMessage::MouseWheel { w_param, l_param } => {
546                 // Safe because `l_param` comes from the window message and should contain valid
547                 // numbers.
548                 let (x, y) = get_x_y_from_lparam(*l_param);
549                 let cursor_pos = window.screen_to_client(Point2D::new(x, y));
550                 if let Err(ref e) = cursor_pos {
551                     error!(
552                         "Failed to convert cursor position to client coordinates: {}",
553                         e
554                     );
555                 }
556 
557                 let z_delta = GET_WHEEL_DELTA_WPARAM(*w_param);
558                 self.handle_mouse_wheel(window, z_delta, cursor_pos.ok());
559             }
560             MouseMessage::SetCursor => {
561                 return if self.should_capture_cursor(window) {
562                     // Hide the cursor and skip default processing.
563                     // SAFETY: trivially safe
564                     unsafe { SetCursor(null_mut()) };
565                     ControlFlow::Continue(())
566                 } else {
567                     // Request default processing, i.e. showing the cursor.
568                     ControlFlow::Break(None)
569                 };
570             }
571             MouseMessage::MouseActivate { l_param } => {
572                 let hit_test = LOWORD(*l_param as u32) as isize;
573                 // Only activate if we hit the client area.
574                 let activate = if hit_test == HTCLIENT {
575                     MA_ACTIVATE
576                 } else {
577                     MA_NOACTIVATE
578                 };
579                 return ControlFlow::Break(Some(activate as LRESULT));
580             }
581         }
582         ControlFlow::Continue(())
583     }
584 
handle_change_mouse_mode_request(&mut self, window: &GuiWindow, mouse_mode: MouseMode)585     pub fn handle_change_mouse_mode_request(&mut self, window: &GuiWindow, mouse_mode: MouseMode) {
586         self.set_mouse_mode(window, mouse_mode);
587     }
588 
589     /// Confines/releases the cursor to/from `window`, depending on the current mouse mode and
590     /// window states.
adjust_cursor_capture(&mut self, window: &GuiWindow) -> Result<()>591     fn adjust_cursor_capture(&mut self, window: &GuiWindow) -> Result<()> {
592         let should_capture = self.should_capture_cursor(window);
593         if should_capture {
594             self.confine_cursor_to_window_internal(window)
595                 .context("When confining cursor to window")?;
596         } else {
597             // SAFETY: trivially safe
598             unsafe {
599                 clip_cursor(null()).context("When releasing cursor from window")?;
600             }
601         }
602         Ok(())
603     }
604 
605     /// Confines the host cursor to a new window area.
confine_cursor_to_window_internal(&mut self, window: &GuiWindow) -> Result<()>606     fn confine_cursor_to_window_internal(&mut self, window: &GuiWindow) -> Result<()> {
607         let work_rect = window.get_monitor_info()?.work_rect.to_sys_rect();
608         let client_rect = window.get_client_rect()?;
609 
610         // Translate client rect to screen coordinates.
611         let client_ul = window.client_to_screen(&client_rect.min())?;
612         let client_br = window.client_to_screen(&client_rect.max())?;
613         let mut client_rect = client_rect.to_sys_rect();
614 
615         // SAFETY:
616         // Safe because hwnd and all RECT are valid objects
617         unsafe {
618             SetRect(
619                 &mut client_rect,
620                 client_ul.x + BORDER_OFFSET,
621                 client_ul.y + BORDER_OFFSET,
622                 client_br.x - BORDER_OFFSET,
623                 client_br.y - BORDER_OFFSET,
624             );
625             let mut clip_rect = RECT::default();
626 
627             // If client_rect intersects with the taskbar then remove that area.
628             if IntersectRect(&mut clip_rect, &client_rect, &work_rect) != 0 {
629                 clip_cursor(&clip_rect)?;
630             } else {
631                 clip_cursor(&client_rect)?;
632             }
633         }
634         Ok(())
635     }
636 
637     /// Returns whether we intend the mouse cursor to be captured based on the mouse mode.
638     ///
639     /// Note that we also need to check the window state to see if we actually want to capture the
640     /// cursor at this moment. See `should_capture_cursor()`.
is_capture_mode(&self) -> bool641     fn is_capture_mode(&self) -> bool {
642         self.mouse_mode == MouseMode::Relative
643     }
644 
645     /// Returns true if the mouse cursor should be captured and hidden.
646     ///
647     /// We don't always want mouse capture in relative mouse mode. When we are dragging the title
648     /// bar to move the window around, or dragging window borders/corners for resizing, we'd still
649     /// want to show the cursor until the dragging ends.
should_capture_cursor(&self, window: &GuiWindow) -> bool650     fn should_capture_cursor(&self, window: &GuiWindow) -> bool {
651         self.is_capture_mode()
652             && window.is_global_foreground_window()
653             && !window.is_sizing_or_moving()
654     }
655 }
656 
657 /// Given an l_param from a mouse event, extracts the (x, y) coordinates. Note that these
658 /// coordinates should be positive provided the mouse is not captured. (They can be negative when
659 /// the mouse is captured and it moves outside the bounds of the hwnd.)
get_x_y_from_lparam(l_param: LPARAM) -> (i32, i32)660 fn get_x_y_from_lparam(l_param: LPARAM) -> (i32, i32) {
661     (GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param))
662 }
663 
clip_cursor(rect: *const RECT) -> Result<()>664 unsafe fn clip_cursor(rect: *const RECT) -> Result<()> {
665     if ClipCursor(rect) == 0 {
666         syscall_bail!("Failed to call ClipCursor()");
667     }
668     Ok(())
669 }
670 
get_primary_monitor_rect(is_virtual_desktop: bool) -> Rect671 fn get_primary_monitor_rect(is_virtual_desktop: bool) -> Rect {
672     // SAFETY: trivially-safe
673     let (origin, size) = unsafe {
674         if is_virtual_desktop {
675             (
676                 point2(
677                     GetSystemMetrics(SM_XVIRTUALSCREEN),
678                     GetSystemMetrics(SM_YVIRTUALSCREEN),
679                 ),
680                 size2(
681                     GetSystemMetrics(SM_CXVIRTUALSCREEN),
682                     GetSystemMetrics(SM_CYVIRTUALSCREEN),
683                 ),
684             )
685         } else {
686             (
687                 Point2D::zero(),
688                 size2(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)),
689             )
690         }
691     };
692     Rect::new(origin, size)
693 }
694