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