Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/gpu_display/src/gpu_display_win/window.rs
5394 views
1
// Copyright 2022 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::convert::From;
6
use std::fmt;
7
use std::mem;
8
use std::os::raw::c_void;
9
use std::ptr::null_mut;
10
11
use anyhow::bail;
12
use anyhow::Context;
13
use anyhow::Result;
14
use base::error;
15
use base::info;
16
use base::warn;
17
use euclid::point2;
18
use euclid::size2;
19
use euclid::Point2D;
20
use euclid::Size2D;
21
use win_util::syscall_bail;
22
use win_util::win32_wide_string;
23
use winapi::shared::minwindef::DWORD;
24
use winapi::shared::minwindef::FALSE;
25
use winapi::shared::minwindef::HINSTANCE;
26
use winapi::shared::minwindef::HMODULE;
27
use winapi::shared::minwindef::LPARAM;
28
use winapi::shared::minwindef::LRESULT;
29
use winapi::shared::minwindef::TRUE;
30
use winapi::shared::minwindef::UINT;
31
use winapi::shared::minwindef::WORD;
32
use winapi::shared::minwindef::WPARAM;
33
use winapi::shared::windef::HBRUSH;
34
use winapi::shared::windef::HCURSOR;
35
use winapi::shared::windef::HICON;
36
use winapi::shared::windef::HMONITOR;
37
use winapi::shared::windef::HWND;
38
use winapi::shared::windef::RECT;
39
use winapi::shared::winerror::S_OK;
40
use winapi::um::dwmapi::DwmEnableBlurBehindWindow;
41
use winapi::um::dwmapi::DWM_BB_ENABLE;
42
use winapi::um::dwmapi::DWM_BLURBEHIND;
43
use winapi::um::errhandlingapi::GetLastError;
44
use winapi::um::errhandlingapi::SetLastError;
45
use winapi::um::libloaderapi::GetModuleHandleW;
46
use winapi::um::shellscalingapi::GetDpiForMonitor;
47
use winapi::um::shellscalingapi::MDT_DEFAULT;
48
use winapi::um::shellscalingapi::MDT_RAW_DPI;
49
use winapi::um::wingdi::GetStockObject;
50
use winapi::um::wingdi::BLACK_BRUSH;
51
use winapi::um::winnt::LPCWSTR;
52
use winapi::um::winuser::AdjustWindowRectExForDpi;
53
use winapi::um::winuser::ClientToScreen;
54
use winapi::um::winuser::CreateWindowExW;
55
use winapi::um::winuser::DefWindowProcW;
56
use winapi::um::winuser::DestroyWindow;
57
use winapi::um::winuser::GetActiveWindow;
58
use winapi::um::winuser::GetClientRect;
59
use winapi::um::winuser::GetDpiForSystem;
60
use winapi::um::winuser::GetForegroundWindow;
61
use winapi::um::winuser::GetMonitorInfoW;
62
use winapi::um::winuser::GetSystemMetrics;
63
use winapi::um::winuser::GetWindowLongPtrW;
64
use winapi::um::winuser::GetWindowPlacement;
65
use winapi::um::winuser::GetWindowRect;
66
use winapi::um::winuser::IsIconic;
67
use winapi::um::winuser::IsWindow;
68
use winapi::um::winuser::IsWindowVisible;
69
use winapi::um::winuser::IsZoomed;
70
use winapi::um::winuser::LoadCursorW;
71
use winapi::um::winuser::LoadIconW;
72
use winapi::um::winuser::MonitorFromWindow;
73
use winapi::um::winuser::PostMessageW;
74
use winapi::um::winuser::RegisterRawInputDevices;
75
use winapi::um::winuser::RegisterTouchWindow;
76
use winapi::um::winuser::RemovePropW;
77
use winapi::um::winuser::ScreenToClient;
78
use winapi::um::winuser::SetForegroundWindow;
79
use winapi::um::winuser::SetPropW;
80
use winapi::um::winuser::SetWindowLongPtrW;
81
use winapi::um::winuser::SetWindowPlacement;
82
use winapi::um::winuser::SetWindowPos;
83
use winapi::um::winuser::ShowWindow;
84
use winapi::um::winuser::GWL_EXSTYLE;
85
use winapi::um::winuser::HWND_MESSAGE;
86
use winapi::um::winuser::MAKEINTRESOURCEW;
87
use winapi::um::winuser::MONITORINFO;
88
use winapi::um::winuser::MONITOR_DEFAULTTONEAREST;
89
use winapi::um::winuser::MONITOR_DEFAULTTONULL;
90
use winapi::um::winuser::MSG;
91
use winapi::um::winuser::PCRAWINPUTDEVICE;
92
use winapi::um::winuser::RAWINPUTDEVICE;
93
use winapi::um::winuser::SM_REMOTESESSION;
94
use winapi::um::winuser::SWP_FRAMECHANGED;
95
use winapi::um::winuser::SWP_HIDEWINDOW;
96
use winapi::um::winuser::SWP_NOACTIVATE;
97
use winapi::um::winuser::SWP_NOMOVE;
98
use winapi::um::winuser::SWP_NOSIZE;
99
use winapi::um::winuser::SWP_NOZORDER;
100
use winapi::um::winuser::SW_RESTORE;
101
use winapi::um::winuser::SW_SHOW;
102
use winapi::um::winuser::WINDOWPLACEMENT;
103
use winapi::um::winuser::WMSZ_BOTTOM;
104
use winapi::um::winuser::WMSZ_BOTTOMLEFT;
105
use winapi::um::winuser::WMSZ_BOTTOMRIGHT;
106
use winapi::um::winuser::WMSZ_LEFT;
107
use winapi::um::winuser::WMSZ_RIGHT;
108
use winapi::um::winuser::WMSZ_TOP;
109
use winapi::um::winuser::WMSZ_TOPLEFT;
110
use winapi::um::winuser::WMSZ_TOPRIGHT;
111
use winapi::um::winuser::WM_ENTERSIZEMOVE;
112
use winapi::um::winuser::WM_EXITSIZEMOVE;
113
use winapi::um::winuser::WM_MOVING;
114
use winapi::um::winuser::WM_SIZING;
115
116
use super::math_util::*;
117
use super::HostWindowSpace;
118
119
// Windows desktop's default DPI at default scaling settings is 96.
120
// (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mpc/pixel-density-and-usability)
121
pub(crate) const DEFAULT_HOST_DPI: i32 = 96;
122
123
/// Stores a message retrieved from the message pump. We don't include the HWND since it is only
124
/// used for determining the recipient.
125
#[derive(Copy, Clone, Debug)]
126
pub struct MessagePacket {
127
pub msg: UINT,
128
pub w_param: WPARAM,
129
pub l_param: LPARAM,
130
}
131
132
impl MessagePacket {
133
pub fn new(msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Self {
134
Self {
135
msg,
136
w_param,
137
l_param,
138
}
139
}
140
}
141
142
impl From<MSG> for MessagePacket {
143
fn from(message: MSG) -> Self {
144
Self::new(message.message, message.wParam, message.lParam)
145
}
146
}
147
148
/// The state of window moving or sizing modal loop.
149
///
150
/// We do receive `WM_ENTERSIZEMOVE` when the window is about to be resized or moved, but it doesn't
151
/// tell us whether resizing or moving should be expected. We won't know that until later we receive
152
/// `WM_SIZING` or `WM_MOVING`. Corner cases are:
153
/// (1) If the user long presses the title bar, window borders or corners, and then releases without
154
/// moving the mouse, we would receive both `WM_ENTERSIZEMOVE` and `WM_EXITSIZEMOVE`, but
155
/// without any `WM_SIZING` or `WM_MOVING` in between.
156
/// (2) When the window is maximized, if we drag the title bar of it, it will be restored to the
157
/// normal size and then move along with the cursor. In this case, we would expect
158
/// `WM_ENTERSIZEMOVE` to be followed by one `WM_SIZING`, and then multiple `WM_MOVING`.
159
///
160
/// This enum tracks the modal loop state. Possible state transition:
161
/// (1) NotInLoop -> WillResizeOrMove -> IsResizing -> NotInLoop. This is for sizing modal loops.
162
/// (2) NotInLoop -> WillResizeOrMove -> IsMoving -> NotInLoop. This is for moving modal loops.
163
/// (3) NotInLoop -> WillResizeOrMove -> NotInLoop. This may occur if the user long presses the
164
/// window title bar, window borders or corners, but doesn't actually resize or move the window.
165
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
166
enum SizeMoveLoopState {
167
/// The window is not in the moving or sizing modal loop.
168
NotInLoop,
169
/// We have received `WM_ENTERSIZEMOVE` but haven't received either `WM_SIZING` or `WM_MOVING`,
170
/// so we don't know if the window is going to be resized or moved at this point.
171
WillResizeOrMove,
172
/// We have received `WM_SIZING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is
173
/// the first `WM_SIZING`.
174
IsResizing { is_first: bool },
175
/// We have received `WM_MOVING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is
176
/// the first `WM_MOVING`.
177
IsMoving { is_first: bool },
178
}
179
180
impl SizeMoveLoopState {
181
pub fn new() -> Self {
182
Self::NotInLoop
183
}
184
185
pub fn update(&mut self, msg: UINT, w_param: WPARAM) {
186
match msg {
187
WM_ENTERSIZEMOVE => self.on_entering_loop(),
188
WM_EXITSIZEMOVE => self.on_exiting_loop(),
189
WM_SIZING => self.on_resizing_window(w_param),
190
WM_MOVING => self.on_moving_window(),
191
_ => (),
192
};
193
}
194
195
pub fn is_in_loop(&self) -> bool {
196
*self != Self::NotInLoop
197
}
198
199
pub fn is_resizing_starting(&self) -> bool {
200
*self == Self::IsResizing { is_first: true }
201
}
202
203
fn on_entering_loop(&mut self) {
204
info!("Entering window sizing/moving modal loop");
205
*self = Self::WillResizeOrMove;
206
}
207
208
fn on_exiting_loop(&mut self) {
209
info!("Exiting window sizing/moving modal loop");
210
*self = Self::NotInLoop;
211
}
212
213
fn on_resizing_window(&mut self, w_param: WPARAM) {
214
match *self {
215
Self::NotInLoop => (),
216
Self::WillResizeOrMove => match w_param as u32 {
217
// In these cases, the user is dragging window borders or corners for resizing.
218
WMSZ_LEFT | WMSZ_RIGHT | WMSZ_TOP | WMSZ_BOTTOM | WMSZ_TOPLEFT | WMSZ_TOPRIGHT
219
| WMSZ_BOTTOMLEFT | WMSZ_BOTTOMRIGHT => {
220
info!("Window is being resized");
221
*self = Self::IsResizing { is_first: true };
222
}
223
// In this case, the user is dragging the title bar of the maximized window. The
224
// window will be restored to the normal size and then move along with the cursor,
225
// so we can expect `WM_MOVING` coming and entering the moving modal loop.
226
_ => info!("Window is being restored"),
227
},
228
Self::IsResizing { .. } => *self = Self::IsResizing { is_first: false },
229
Self::IsMoving { .. } => warn!("WM_SIZING is unexpected in moving modal loops!"),
230
}
231
}
232
233
fn on_moving_window(&mut self) {
234
match *self {
235
Self::NotInLoop => (),
236
Self::WillResizeOrMove => {
237
info!("Window is being moved");
238
*self = Self::IsMoving { is_first: true };
239
}
240
Self::IsMoving { .. } => *self = Self::IsMoving { is_first: false },
241
Self::IsResizing { .. } => warn!("WM_MOVING is unexpected in sizing modal loops!"),
242
}
243
}
244
}
245
246
/// A trait for basic functionalities that are common to both message-only windows and GUI windows.
247
/// Implementers must guarantee that when these functions are called, the underlying window object
248
/// is still alive.
249
pub(crate) trait BasicWindow {
250
/// # Safety
251
/// The returned handle should be used carefully, since it may have become invalid if it
252
/// outlives the window object.
253
unsafe fn handle(&self) -> HWND;
254
255
fn is_same_window(&self, hwnd: HWND) -> bool {
256
// SAFETY:
257
// Safe because we are just comparing handle values.
258
hwnd == unsafe { self.handle() }
259
}
260
261
/// Calls `DefWindowProcW()` internally.
262
fn default_process_message(&self, packet: &MessagePacket) -> LRESULT {
263
// SAFETY:
264
// Safe because the window object won't outlive the HWND.
265
unsafe { DefWindowProcW(self.handle(), packet.msg, packet.w_param, packet.l_param) }
266
}
267
268
/// Calls `SetPropW()` internally.
269
/// # Safety
270
/// The caller is responsible for keeping the data pointer valid until `remove_property()` is
271
/// called.
272
unsafe fn set_property(&self, property: &str, data: *mut c_void) -> Result<()> {
273
// Partially safe because the window object won't outlive the HWND, and failures are handled
274
// below. The caller is responsible for the rest of safety.
275
if SetPropW(self.handle(), win32_wide_string(property).as_ptr(), data) == 0 {
276
syscall_bail!("Failed to call SetPropW()");
277
}
278
Ok(())
279
}
280
281
/// Calls `RemovePropW()` internally.
282
#[allow(dead_code)]
283
fn remove_property(&self, property: &str) -> Result<()> {
284
// SAFETY:
285
// Safe because the window object won't outlive the HWND, and failures are handled below.
286
unsafe {
287
SetLastError(0);
288
RemovePropW(self.handle(), win32_wide_string(property).as_ptr());
289
if GetLastError() != 0 {
290
syscall_bail!("Failed to call RemovePropW()");
291
}
292
}
293
Ok(())
294
}
295
296
/// Calls `DestroyWindow()` internally.
297
fn destroy(&self) -> Result<()> {
298
// SAFETY:
299
// Safe because the window object won't outlive the HWND.
300
if unsafe { DestroyWindow(self.handle()) } == 0 {
301
syscall_bail!("Failed to call DestroyWindow()");
302
}
303
Ok(())
304
}
305
}
306
307
/// This class helps create and operate on a GUI window using Windows APIs. The owner of `GuiWindow`
308
/// object is responsible for:
309
/// (1) Calling `update_states()` when a new window message arrives.
310
/// (2) Dropping the `GuiWindow` object before the underlying window is completely gone.
311
pub struct GuiWindow {
312
hwnd: HWND,
313
scanout_id: u32,
314
size_move_loop_state: SizeMoveLoopState,
315
}
316
317
impl GuiWindow {
318
/// # Safety
319
/// The owner of `GuiWindow` object is responsible for dropping it before we finish processing
320
/// `WM_NCDESTROY`, because the window handle will become invalid afterwards.
321
pub unsafe fn new(
322
scanout_id: u32,
323
class_name: &str,
324
title: &str,
325
dw_style: DWORD,
326
initial_window_size: &Size2D<i32, HostWindowSpace>,
327
) -> Result<Self> {
328
info!("Creating GUI window for scanout {}", scanout_id);
329
330
let hwnd = create_sys_window(
331
get_current_module_handle(),
332
class_name,
333
title,
334
dw_style,
335
/* hwnd_parent */ null_mut(),
336
initial_window_size,
337
)
338
.context("When creating GuiWindow")?;
339
let window = Self {
340
hwnd,
341
scanout_id,
342
size_move_loop_state: SizeMoveLoopState::new(),
343
};
344
window.register_touch();
345
Ok(window)
346
}
347
348
pub fn scanout_id(&self) -> u32 {
349
self.scanout_id
350
}
351
352
pub fn update_states(&mut self, msg: UINT, w_param: WPARAM) {
353
self.size_move_loop_state.update(msg, w_param);
354
}
355
356
pub fn is_sizing_or_moving(&self) -> bool {
357
self.size_move_loop_state.is_in_loop()
358
}
359
360
pub fn is_resizing_loop_starting(&self) -> bool {
361
self.size_move_loop_state.is_resizing_starting()
362
}
363
364
/// Calls `IsWindow()` internally. Returns true if the HWND identifies an existing window.
365
pub fn is_valid(&self) -> bool {
366
// SAFETY:
367
// Safe because it is called from the same thread the created the window.
368
unsafe { IsWindow(self.hwnd) != 0 }
369
}
370
371
/// Calls `GetWindowLongPtrW()` internally.
372
pub fn get_attribute(&self, index: i32) -> Result<isize> {
373
// SAFETY:
374
// Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
375
unsafe {
376
// GetWindowLongPtrW() may return zero if we haven't set that attribute before, so we
377
// need to check if the error code is non-zero.
378
SetLastError(0);
379
let value = GetWindowLongPtrW(self.hwnd, index);
380
if value == 0 && GetLastError() != 0 {
381
syscall_bail!("Failed to call GetWindowLongPtrW()");
382
}
383
Ok(value)
384
}
385
}
386
387
/// Calls `SetWindowLongPtrW()` internally.
388
pub fn set_attribute(&self, index: i32, value: isize) -> Result<()> {
389
// SAFETY:
390
// Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
391
unsafe {
392
// SetWindowLongPtrW() may return zero if the previous value of that attribute was zero,
393
// so we need to check if the error code is non-zero.
394
SetLastError(0);
395
let prev_value = SetWindowLongPtrW(self.hwnd, index, value);
396
if prev_value == 0 && GetLastError() != 0 {
397
syscall_bail!("Failed to call SetWindowLongPtrW()");
398
}
399
Ok(())
400
}
401
}
402
403
/// Calls `GetWindowRect()` internally.
404
pub fn get_window_rect(&self) -> Result<Rect> {
405
let mut rect: RECT = Default::default();
406
// SAFETY:
407
// Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and
408
// failures are handled below.
409
unsafe {
410
if GetWindowRect(self.hwnd, &mut rect) == 0 {
411
syscall_bail!("Failed to call GetWindowRect()");
412
}
413
}
414
Ok(rect.to_rect())
415
}
416
417
/// Calls `GetWindowRect()` internally.
418
pub fn get_window_origin(&self) -> Result<Point> {
419
Ok(self.get_window_rect()?.origin)
420
}
421
422
/// Calls `GetClientRect()` internally.
423
pub fn get_client_rect(&self) -> Result<Rect> {
424
let mut rect: RECT = Default::default();
425
// SAFETY:
426
// Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and
427
// failures are handled below.
428
unsafe {
429
if GetClientRect(self.hwnd, &mut rect) == 0 {
430
syscall_bail!("Failed to call GetClientRect()");
431
}
432
}
433
Ok(rect.to_rect())
434
}
435
436
/// The system may add adornments around the client area of the window, such as the title bar
437
/// and borders. This function returns the size of all those paddings. It can be assumed that:
438
/// window_size = client_size + window_padding_size
439
pub fn get_window_padding_size(&self, dw_style: u32) -> Result<Size> {
440
static CONTEXT_MESSAGE: &str = "When calculating window padding";
441
// The padding is always the same in windowed mode, hence we can use an arbitrary rect.
442
let client_rect = Rect::new(point2(0, 0), size2(500, 500));
443
let dw_ex_style = self.get_attribute(GWL_EXSTYLE).context(CONTEXT_MESSAGE)?;
444
let window_rect: Rect = self
445
.get_adjusted_window_rect(&client_rect, dw_style, dw_ex_style as u32)
446
.context(CONTEXT_MESSAGE)?;
447
Ok(window_rect.size - client_rect.size)
448
}
449
450
/// Calls `ClientToScreen()` internally. Converts the window client area coordinates of a
451
/// specified point to screen coordinates.
452
pub fn client_to_screen(&self, point: &Point) -> Result<Point> {
453
let mut point = point.to_sys_point();
454
// SAFETY:
455
// Safe because `GuiWindow` object won't outlive the HWND, we know `point` is valid, and
456
// failures are handled below.
457
unsafe {
458
if ClientToScreen(self.hwnd, &mut point) == 0 {
459
syscall_bail!("Failed to call ClientToScreen()");
460
}
461
}
462
Ok(point.to_point())
463
}
464
465
/// Calls `ScreenToClient()` internally. Converts the screen coordinates to window client area
466
/// coordinates.
467
pub fn screen_to_client(&self, point: Point) -> Result<Point> {
468
let mut point = point.to_sys_point();
469
470
// SAFETY:
471
// Safe because:
472
// 1. point is stack allocated & lives as long as the function call.
473
// 2. the window handle is guaranteed valid by self.
474
// 3. we check the error before using the output data.
475
unsafe {
476
let res = ScreenToClient(self.hwnd, point.as_mut_ptr());
477
if res == 0 {
478
syscall_bail!("failed to convert cursor position to client coordinates");
479
}
480
}
481
Ok(Point2D::new(point.x, point.y))
482
}
483
484
/// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,
485
/// returns the handle to the closest one.
486
pub fn get_nearest_monitor_handle(&self) -> HMONITOR {
487
// SAFETY:
488
// Safe because `GuiWindow` object won't outlive the HWND.
489
unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONEAREST) }
490
}
491
492
/// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,
493
/// returns the info of the closest one.
494
pub fn get_monitor_info(&self) -> Result<MonitorInfo> {
495
// SAFETY:
496
// Safe because `get_nearest_monitor_handle()` always returns a valid monitor handle.
497
unsafe { MonitorInfo::new(self.get_nearest_monitor_handle()) }
498
}
499
500
/// Calls `MonitorFromWindow()` internally.
501
pub fn is_on_active_display(&self) -> bool {
502
// SAFETY:
503
// Safe because `GuiWindow` object won't outlive the HWND.
504
unsafe { !MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL).is_null() }
505
}
506
507
/// Calls `SetWindowPos()` internally.
508
pub fn set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()> {
509
// SAFETY:
510
// Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
511
unsafe {
512
if SetWindowPos(
513
self.hwnd,
514
null_mut(),
515
window_rect.origin.x,
516
window_rect.origin.y,
517
window_rect.size.width,
518
window_rect.size.height,
519
flags,
520
) == 0
521
{
522
syscall_bail!("Failed to call SetWindowPos()");
523
}
524
Ok(())
525
}
526
}
527
528
/// Calls `SetWindowPos()` internally. If window size and position need to be changed as well,
529
/// prefer to call `set_pos()` with the `SWP_FRAMECHANGED` flag instead.
530
pub fn flush_window_style_change(&self) -> Result<()> {
531
// Because of `SWP_NOMOVE` and `SWP_NOSIZE` flags, we can pass in arbitrary window size and
532
// position as they will be ignored.
533
self.set_pos(
534
&Rect::zero(),
535
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED,
536
)
537
}
538
539
/// Calls `ShowWindow()` internally. Note that it is more preferable to call `set_pos()` with
540
/// `SWP_SHOWWINDOW` since that would set the error code on failure.
541
pub fn show(&self) {
542
// SAFETY:
543
// Safe because `GuiWindow` object won't outlive the HWND.
544
unsafe {
545
ShowWindow(self.hwnd, SW_SHOW);
546
}
547
}
548
549
/// Calls `SetWindowPos()` internally. Returns false if the window is already hidden and thus
550
/// this operation is skipped.
551
pub fn hide_if_visible(&self) -> Result<bool> {
552
Ok(if self.is_visible()? {
553
self.set_pos(
554
&Rect::zero(),
555
SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER,
556
)?;
557
true
558
} else {
559
false
560
})
561
}
562
563
/// Calls `ShowWindow()` internally to restore a minimized window.
564
pub fn restore(&self) {
565
// SAFETY:
566
// Safe because `GuiWindow` object won't outlive the HWND.
567
unsafe {
568
ShowWindow(self.hwnd, SW_RESTORE);
569
}
570
}
571
572
/// Calls `IsZoomed()` internally. Note that the window may carry the WS_MAXIMIZE flag until it
573
/// is restored. For example, if we have switched from maximized to fullscreen, this function
574
/// would still return true.
575
pub fn was_maximized(&self) -> bool {
576
// SAFETY:
577
// Safe because `GuiWindow` object won't outlive the HWND.
578
unsafe { IsZoomed(self.hwnd) != 0 }
579
}
580
581
/// Calls `IsWindowVisible()` internally. We also require that the window size is nonzero to be
582
/// considered visible.
583
pub fn is_visible(&self) -> Result<bool> {
584
// SAFETY:
585
// Safe because `GuiWindow` object won't outlive the HWND.
586
if unsafe { IsWindowVisible(self.hwnd) } != 0 {
587
let window_rect = self
588
.get_window_rect()
589
.context("When querying window visibility")?;
590
if window_rect.size != Size::zero() {
591
return Ok(true);
592
} else {
593
info!("Window has WS_VISIBLE flag but its size is zero");
594
}
595
}
596
Ok(false)
597
}
598
599
/// Calls `GetForegroundWindow()` internally. A foreground window is the window with which the
600
/// user is currently working. It might belong to a different thread/process than the calling
601
/// thread.
602
pub fn is_global_foreground_window(&self) -> bool {
603
// SAFETY:
604
// Safe because there is no argument.
605
unsafe { GetForegroundWindow() == self.hwnd }
606
}
607
608
/// Calls `GetActiveWindow()` internally. An active window is the window with which the user is
609
/// currently working and is attached to the calling thread's message queue. It is possible that
610
/// there is no active window if the foreground focus is on another thread/process.
611
pub fn is_thread_foreground_window(&self) -> bool {
612
// SAFETY:
613
// Safe because there is no argument.
614
unsafe { GetActiveWindow() == self.hwnd }
615
}
616
617
/// Calls `IsIconic()` internally.
618
pub fn is_minimized(&self) -> bool {
619
// SAFETY:
620
// Safe because `GuiWindow` object won't outlive the HWND.
621
unsafe { IsIconic(self.hwnd) != 0 }
622
}
623
624
/// Calls `SetForegroundWindow()` internally. `SetForegroundWindow()` may fail, for example,
625
/// when the taskbar is in the foreground, hence this is a best-effort call.
626
pub fn bring_to_foreground(&self) {
627
// SAFETY:
628
// Safe because `GuiWindow` object won't outlive the HWND.
629
if unsafe { SetForegroundWindow(self.hwnd) } == 0 {
630
info!("Cannot bring the window to foreground.");
631
}
632
}
633
634
/// Calls `DwmEnableBlurBehindWindow()` internally. This is only used for a top-level window.
635
/// Even though the name of Windows API suggests that it blurs the background, beginning with
636
/// Windows 8, it does not blur it, but only makes the window semi-transparent.
637
pub fn set_backgound_transparency(&self, semi_transparent: bool) -> Result<()> {
638
let blur_behind = DWM_BLURBEHIND {
639
dwFlags: DWM_BB_ENABLE,
640
fEnable: if semi_transparent { TRUE } else { FALSE },
641
hRgnBlur: null_mut(),
642
fTransitionOnMaximized: FALSE,
643
};
644
// SAFETY:
645
// Safe because `GuiWindow` object won't outlive the HWND, we know `blur_behind` is valid,
646
// and failures are handled below.
647
let errno = unsafe { DwmEnableBlurBehindWindow(self.hwnd, &blur_behind) };
648
match errno {
649
0 => Ok(()),
650
_ => bail!(
651
"Failed to call DwmEnableBlurBehindWindow() when setting \
652
window background transparency to {} (Error code {})",
653
semi_transparent,
654
errno
655
),
656
}
657
}
658
659
/// Calls `AdjustWindowRectExForDpi()` internally.
660
pub fn get_adjusted_window_rect(
661
&self,
662
client_rect: &Rect,
663
dw_style: u32,
664
dw_ex_style: u32,
665
) -> Result<Rect> {
666
let mut window_rect: RECT = client_rect.to_sys_rect();
667
// SAFETY:
668
// Safe because `GuiWindow` object won't outlive the HWND, we know `window_rect` is valid,
669
// and failures are handled below.
670
unsafe {
671
if AdjustWindowRectExForDpi(
672
&mut window_rect,
673
dw_style,
674
FALSE,
675
dw_ex_style,
676
GetDpiForSystem(),
677
) == 0
678
{
679
syscall_bail!("Failed to call AdjustWindowRectExForDpi()");
680
}
681
}
682
Ok(window_rect.to_rect())
683
}
684
685
/// Calls `GetWindowPlacement()` and `SetWindowPlacement()` internally.
686
pub fn set_restored_pos(&self, window_rect: &Rect) -> Result<()> {
687
let mut window_placement = WINDOWPLACEMENT {
688
length: mem::size_of::<WINDOWPLACEMENT>().try_into().unwrap(),
689
..Default::default()
690
};
691
// SAFETY:
692
// Safe because `GuiWindow` object won't outlive the HWND, we know `window_placement` is
693
// valid, and failures are handled below.
694
unsafe {
695
if GetWindowPlacement(self.hwnd, &mut window_placement) == 0 {
696
syscall_bail!("Failed to call GetWindowPlacement()");
697
}
698
window_placement.rcNormalPosition = window_rect.to_sys_rect();
699
if SetWindowPlacement(self.hwnd, &window_placement) == 0 {
700
syscall_bail!("Failed to call SetWindowPlacement()");
701
}
702
}
703
Ok(())
704
}
705
706
/// Calls `PostMessageW()` internally.
707
pub fn post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()> {
708
// SAFETY:
709
// Safe because `GuiWindow` object won't outlive the HWND.
710
unsafe {
711
if PostMessageW(self.hwnd, msg, w_param, l_param) == 0 {
712
syscall_bail!("Failed to call PostMessageW()");
713
}
714
}
715
Ok(())
716
}
717
718
/// Calls `LoadIconW()` internally.
719
pub(crate) fn load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON> {
720
// SAFETY:
721
// Safe because we handle failures below.
722
unsafe {
723
let hicon = LoadIconW(hinstance, MAKEINTRESOURCEW(resource_id));
724
if hicon.is_null() {
725
syscall_bail!("Failed to call LoadIconW()");
726
}
727
Ok(hicon)
728
}
729
}
730
731
/// Calls `LoadCursorW()` internally.
732
pub(crate) fn load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR> {
733
// SAFETY:
734
// Safe because we handle failures below.
735
unsafe {
736
let hcursor = LoadCursorW(null_mut(), cursor_id);
737
if hcursor.is_null() {
738
syscall_bail!("Failed to call LoadCursorW()");
739
}
740
Ok(hcursor)
741
}
742
}
743
744
/// Calls `GetStockObject()` internally.
745
pub(crate) fn create_opaque_black_brush() -> Result<HBRUSH> {
746
// SAFETY:
747
// Safe because we handle failures below.
748
unsafe {
749
let hobject = GetStockObject(BLACK_BRUSH as i32);
750
if hobject.is_null() {
751
syscall_bail!("Failed to call GetStockObject()");
752
}
753
Ok(hobject as HBRUSH)
754
}
755
}
756
757
/// Calls `RegisterTouchWindow()` internally.
758
fn register_touch(&self) {
759
// SAFETY: Safe because `GuiWindow` object won't outlive the HWND.
760
if unsafe { RegisterTouchWindow(self.handle(), 0) } == 0 {
761
// For now, we register touch only to get stats. It is ok if the registration fails.
762
// SAFETY: trivially-safe
763
warn!("failed to register touch: {}", unsafe { GetLastError() });
764
}
765
}
766
}
767
768
impl BasicWindow for GuiWindow {
769
/// # Safety
770
/// The returned handle should be used carefully, since it may have become invalid if it
771
/// outlives the `GuiWindow` object.
772
unsafe fn handle(&self) -> HWND {
773
self.hwnd
774
}
775
}
776
777
/// A message-only window is always invisible, and is only responsible for sending and receiving
778
/// messages. The owner of `MessageOnlyWindow` object is responsible for dropping it before the
779
/// underlying window is completely gone.
780
pub(crate) struct MessageOnlyWindow {
781
hwnd: HWND,
782
}
783
784
impl MessageOnlyWindow {
785
/// # Safety
786
/// The owner of `MessageOnlyWindow` object is responsible for dropping it before we finish
787
/// processing `WM_NCDESTROY`, because the window handle will become invalid afterwards.
788
pub unsafe fn new(class_name: &str, title: &str) -> Result<Self> {
789
info!("Creating message-only window");
790
static CONTEXT_MESSAGE: &str = "When creating MessageOnlyWindow";
791
792
let window = Self {
793
hwnd: create_sys_window(
794
get_current_module_handle(),
795
class_name,
796
title,
797
/* dw_style */ 0,
798
HWND_MESSAGE,
799
/* initial_window_size */ &size2(0, 0),
800
)
801
.context(CONTEXT_MESSAGE)?,
802
};
803
window.register_raw_input_mouse().context(CONTEXT_MESSAGE)?;
804
Ok(window)
805
}
806
807
/// Registers this window as the receiver of raw mouse input events.
808
///
809
/// On Windows, an application can only have one window that receives raw input events, so we
810
/// make `MessageOnlyWindow` take on this role and reroute events to the foreground `GuiWindow`.
811
fn register_raw_input_mouse(&self) -> Result<()> {
812
let mouse_device = RAWINPUTDEVICE {
813
usUsagePage: 1, // Generic
814
usUsage: 2, // Mouse
815
dwFlags: 0,
816
// SAFETY: Safe because `self` won't outlive the HWND.
817
hwndTarget: unsafe { self.handle() },
818
};
819
// SAFETY: Safe because `mouse_device` lives longer than this function call.
820
if unsafe {
821
RegisterRawInputDevices(
822
&mouse_device as PCRAWINPUTDEVICE,
823
1,
824
mem::size_of::<RAWINPUTDEVICE>() as u32,
825
)
826
} == 0
827
{
828
syscall_bail!("Relative mouse is broken. Failed to call RegisterRawInputDevices()");
829
}
830
Ok(())
831
}
832
}
833
834
impl BasicWindow for MessageOnlyWindow {
835
/// # Safety
836
/// The returned handle should be used carefully, since it may have become invalid if it
837
/// outlives the `MessageOnlyWindow` object.
838
unsafe fn handle(&self) -> HWND {
839
self.hwnd
840
}
841
}
842
843
/// Calls `CreateWindowExW()` internally.
844
fn create_sys_window(
845
hinstance: HINSTANCE,
846
class_name: &str,
847
title: &str,
848
dw_style: DWORD,
849
hwnd_parent: HWND,
850
initial_window_size: &Size2D<i32, HostWindowSpace>,
851
) -> Result<HWND> {
852
// SAFETY:
853
// Safe because we handle failures below.
854
let hwnd = unsafe {
855
CreateWindowExW(
856
/* dwExStyle */ 0,
857
win32_wide_string(class_name).as_ptr(),
858
win32_wide_string(title).as_ptr(),
859
dw_style,
860
/* x */ 0,
861
/* y */ 0,
862
initial_window_size.width,
863
initial_window_size.height,
864
hwnd_parent,
865
/* hMenu */ null_mut(),
866
hinstance,
867
/* lpParam */ null_mut(),
868
)
869
};
870
if hwnd.is_null() {
871
syscall_bail!("Failed to call CreateWindowExW()");
872
}
873
info!("Created window {:p}", hwnd);
874
Ok(hwnd)
875
}
876
877
/// Calls `GetModuleHandleW()` internally.
878
pub(crate) fn get_current_module_handle() -> HMODULE {
879
// SAFETY:
880
// Safe because we handle failures below.
881
let hmodule = unsafe { GetModuleHandleW(null_mut()) };
882
if hmodule.is_null() {
883
// If it fails, we are in a very broken state and it doesn't make sense to keep running.
884
panic!(
885
"Failed to call GetModuleHandleW() for the current module (Error code {})",
886
// SAFETY: trivially safe
887
unsafe { GetLastError() }
888
);
889
}
890
hmodule
891
}
892
893
/// If the resolution/orientation of the monitor changes, or if the monitor is unplugged, this must
894
/// be recreated with a valid HMONITOR.
895
pub struct MonitorInfo {
896
pub hmonitor: HMONITOR,
897
pub display_rect: Rect,
898
pub work_rect: Rect,
899
raw_dpi: i32,
900
// Whether we are running in a Remote Desktop Protocol (RDP) session. The monitor DPI returned
901
// by `GetDpiForMonitor()` may not make sense in that case. For example, the DPI is always 25
902
// under Chrome Remote Desktop, which is way lower than the standard DPI 96. This might be a
903
// flaw in RDP itself. We have to override the DPI in that case, otherwise the guest DPI
904
// calculated based on it would be too low as well.
905
// https://learn.microsoft.com/en-us/troubleshoot/windows-server/shell-experience/dpi-adjustment-unavailable-in-rdp
906
is_rdp_session: bool,
907
}
908
909
impl MonitorInfo {
910
/// # Safety
911
/// Caller is responsible for ensuring that `hmonitor` is a valid handle.
912
pub unsafe fn new(hmonitor: HMONITOR) -> Result<Self> {
913
let monitor_info: MONITORINFO =
914
Self::get_monitor_info(hmonitor).context("When creating MonitorInfo")?;
915
// Docs state that apart from `GetSystemMetrics(SM_REMOTESESSION)`, we also need to check
916
// registry entries to see if we are running in a remote session that uses RemoteFX vGPU:
917
// https://learn.microsoft.com/en-us/windows/win32/termserv/detecting-the-terminal-services-environment
918
// However, RemoteFX vGPU was then removed because of security vulnerabilities:
919
// https://support.microsoft.com/en-us/topic/kb4570006-update-to-disable-and-remove-the-remotefx-vgpu-component-in-windows-bbdf1531-7188-2bf4-0de6-641de79f09d2
920
// So, we are only calling `GetSystemMetrics(SM_REMOTESESSION)` here until this changes in
921
// the future.
922
// SAFETY:
923
// Safe because no memory management is needed for arguments.
924
let is_rdp_session = unsafe { GetSystemMetrics(SM_REMOTESESSION) != 0 };
925
Ok(Self {
926
hmonitor,
927
display_rect: monitor_info.rcMonitor.to_rect(),
928
work_rect: monitor_info.rcWork.to_rect(),
929
raw_dpi: Self::get_monitor_dpi(hmonitor),
930
is_rdp_session,
931
})
932
}
933
934
pub fn get_dpi(&self) -> i32 {
935
if self.is_rdp_session {
936
// Override the DPI since the system may not tell us the correct value in RDP sessions.
937
DEFAULT_HOST_DPI
938
} else {
939
self.raw_dpi
940
}
941
}
942
943
/// Calls `GetMonitorInfoW()` internally.
944
/// # Safety
945
/// Caller is responsible for ensuring that `hmonitor` is a valid handle.
946
unsafe fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO> {
947
let mut monitor_info = MONITORINFO {
948
cbSize: mem::size_of::<MONITORINFO>().try_into().unwrap(),
949
..Default::default()
950
};
951
if GetMonitorInfoW(hmonitor, &mut monitor_info) == 0 {
952
syscall_bail!("Failed to call GetMonitorInfoW()");
953
}
954
Ok(monitor_info)
955
}
956
957
/// Calls `GetDpiForMonitor()` internally.
958
fn get_monitor_dpi(hmonitor: HMONITOR) -> i32 {
959
let mut dpi_x = 0;
960
let mut dpi_y = 0;
961
// SAFETY:
962
// This is always safe since `GetDpiForMonitor` won't crash if HMONITOR is invalid, but
963
// return E_INVALIDARG.
964
unsafe {
965
if GetDpiForMonitor(hmonitor, MDT_RAW_DPI, &mut dpi_x, &mut dpi_y) == S_OK
966
|| GetDpiForMonitor(hmonitor, MDT_DEFAULT, &mut dpi_x, &mut dpi_y) == S_OK
967
{
968
// We assume screen pixels are square and DPI in different directions are the same.
969
dpi_x as i32
970
} else {
971
error!("Failed to retrieve DPI with HMONITOR {:p}", hmonitor);
972
DEFAULT_HOST_DPI
973
}
974
}
975
}
976
}
977
978
impl fmt::Debug for MonitorInfo {
979
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
980
write!(
981
f,
982
"{{hmonitor: {:p}, display_rect: {:?}, work_rect: {:?}, DPI: {}{}}}",
983
self.hmonitor,
984
self.display_rect,
985
self.work_rect,
986
self.get_dpi(),
987
if self.is_rdp_session {
988
format!(" (raw value: {}, overriden due to RDP)", self.raw_dpi)
989
} else {
990
String::new()
991
}
992
)
993
}
994
}
995
996