Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/gpu_display/src/gpu_display_win/mouse_input_manager.rs
5394 views
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 {
104
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]
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]
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]
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]
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.
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]
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.
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
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
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
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
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
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
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
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
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]
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]
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
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.
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.
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()`.
641
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.
650
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.)
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
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
671
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
695