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 {
new(msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Self133 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 {
from(message: MSG) -> Self143 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 {
new() -> Self181 pub fn new() -> Self {
182 Self::NotInLoop
183 }
184
update(&mut self, msg: UINT, w_param: WPARAM)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
is_in_loop(&self) -> bool195 pub fn is_in_loop(&self) -> bool {
196 *self != Self::NotInLoop
197 }
198
is_resizing_starting(&self) -> bool199 pub fn is_resizing_starting(&self) -> bool {
200 *self == Self::IsResizing { is_first: true }
201 }
202
on_entering_loop(&mut self)203 fn on_entering_loop(&mut self) {
204 info!("Entering window sizing/moving modal loop");
205 *self = Self::WillResizeOrMove;
206 }
207
on_exiting_loop(&mut self)208 fn on_exiting_loop(&mut self) {
209 info!("Exiting window sizing/moving modal loop");
210 *self = Self::NotInLoop;
211 }
212
on_resizing_window(&mut self, w_param: WPARAM)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
on_moving_window(&mut self)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.
handle(&self) -> HWND253 unsafe fn handle(&self) -> HWND;
254
is_same_window(&self, hwnd: HWND) -> bool255 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.
default_process_message(&self, packet: &MessagePacket) -> LRESULT262 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.
set_property(&self, property: &str, data: *mut c_void) -> Result<()>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.
remove_property(&self, property: &str) -> Result<()>282 fn remove_property(&self, property: &str) -> Result<()> {
283 // SAFETY:
284 // Safe because the window object won't outlive the HWND, and failures are handled below.
285 unsafe {
286 SetLastError(0);
287 RemovePropW(self.handle(), win32_wide_string(property).as_ptr());
288 if GetLastError() != 0 {
289 syscall_bail!("Failed to call RemovePropW()");
290 }
291 }
292 Ok(())
293 }
294
295 /// Calls `DestroyWindow()` internally.
destroy(&self) -> Result<()>296 fn destroy(&self) -> Result<()> {
297 // SAFETY:
298 // Safe because the window object won't outlive the HWND.
299 if unsafe { DestroyWindow(self.handle()) } == 0 {
300 syscall_bail!("Failed to call DestroyWindow()");
301 }
302 Ok(())
303 }
304 }
305
306 /// This class helps create and operate on a GUI window using Windows APIs. The owner of `GuiWindow`
307 /// object is responsible for:
308 /// (1) Calling `update_states()` when a new window message arrives.
309 /// (2) Dropping the `GuiWindow` object before the underlying window is completely gone.
310 pub struct GuiWindow {
311 hwnd: HWND,
312 scanout_id: u32,
313 size_move_loop_state: SizeMoveLoopState,
314 }
315
316 impl GuiWindow {
317 /// # Safety
318 /// The owner of `GuiWindow` object is responsible for dropping it before we finish processing
319 /// `WM_NCDESTROY`, because the window handle will become invalid afterwards.
new( scanout_id: u32, class_name: &str, title: &str, dw_style: DWORD, initial_window_size: &Size2D<i32, HostWindowSpace>, ) -> Result<Self>320 pub unsafe fn new(
321 scanout_id: u32,
322 class_name: &str,
323 title: &str,
324 dw_style: DWORD,
325 initial_window_size: &Size2D<i32, HostWindowSpace>,
326 ) -> Result<Self> {
327 info!("Creating GUI window for scanout {}", scanout_id);
328
329 let hwnd = create_sys_window(
330 get_current_module_handle(),
331 class_name,
332 title,
333 dw_style,
334 /* hwnd_parent */ null_mut(),
335 initial_window_size,
336 )
337 .context("When creating GuiWindow")?;
338 let window = Self {
339 hwnd,
340 scanout_id,
341 size_move_loop_state: SizeMoveLoopState::new(),
342 };
343 window.register_touch();
344 Ok(window)
345 }
346
scanout_id(&self) -> u32347 pub fn scanout_id(&self) -> u32 {
348 self.scanout_id
349 }
350
update_states(&mut self, msg: UINT, w_param: WPARAM)351 pub fn update_states(&mut self, msg: UINT, w_param: WPARAM) {
352 self.size_move_loop_state.update(msg, w_param);
353 }
354
is_sizing_or_moving(&self) -> bool355 pub fn is_sizing_or_moving(&self) -> bool {
356 self.size_move_loop_state.is_in_loop()
357 }
358
is_resizing_loop_starting(&self) -> bool359 pub fn is_resizing_loop_starting(&self) -> bool {
360 self.size_move_loop_state.is_resizing_starting()
361 }
362
363 /// Calls `IsWindow()` internally. Returns true if the HWND identifies an existing window.
is_valid(&self) -> bool364 pub fn is_valid(&self) -> bool {
365 // SAFETY:
366 // Safe because it is called from the same thread the created the window.
367 unsafe { IsWindow(self.hwnd) != 0 }
368 }
369
370 /// Calls `GetWindowLongPtrW()` internally.
get_attribute(&self, index: i32) -> Result<isize>371 pub fn get_attribute(&self, index: i32) -> Result<isize> {
372 // SAFETY:
373 // Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
374 unsafe {
375 // GetWindowLongPtrW() may return zero if we haven't set that attribute before, so we
376 // need to check if the error code is non-zero.
377 SetLastError(0);
378 let value = GetWindowLongPtrW(self.hwnd, index);
379 if value == 0 && GetLastError() != 0 {
380 syscall_bail!("Failed to call GetWindowLongPtrW()");
381 }
382 Ok(value)
383 }
384 }
385
386 /// Calls `SetWindowLongPtrW()` internally.
set_attribute(&self, index: i32, value: isize) -> Result<()>387 pub fn set_attribute(&self, index: i32, value: isize) -> Result<()> {
388 // SAFETY:
389 // Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
390 unsafe {
391 // SetWindowLongPtrW() may return zero if the previous value of that attribute was zero,
392 // so we need to check if the error code is non-zero.
393 SetLastError(0);
394 let prev_value = SetWindowLongPtrW(self.hwnd, index, value);
395 if prev_value == 0 && GetLastError() != 0 {
396 syscall_bail!("Failed to call SetWindowLongPtrW()");
397 }
398 Ok(())
399 }
400 }
401
402 /// Calls `GetWindowRect()` internally.
get_window_rect(&self) -> Result<Rect>403 pub fn get_window_rect(&self) -> Result<Rect> {
404 let mut rect: RECT = Default::default();
405 // SAFETY:
406 // Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and
407 // failures are handled below.
408 unsafe {
409 if GetWindowRect(self.hwnd, &mut rect) == 0 {
410 syscall_bail!("Failed to call GetWindowRect()");
411 }
412 }
413 Ok(rect.to_rect())
414 }
415
416 /// Calls `GetWindowRect()` internally.
get_window_origin(&self) -> Result<Point>417 pub fn get_window_origin(&self) -> Result<Point> {
418 Ok(self.get_window_rect()?.origin)
419 }
420
421 /// Calls `GetClientRect()` internally.
get_client_rect(&self) -> Result<Rect>422 pub fn get_client_rect(&self) -> Result<Rect> {
423 let mut rect: RECT = Default::default();
424 // SAFETY:
425 // Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and
426 // failures are handled below.
427 unsafe {
428 if GetClientRect(self.hwnd, &mut rect) == 0 {
429 syscall_bail!("Failed to call GetClientRect()");
430 }
431 }
432 Ok(rect.to_rect())
433 }
434
435 /// The system may add adornments around the client area of the window, such as the title bar
436 /// and borders. This function returns the size of all those paddings. It can be assumed that:
437 /// window_size = client_size + window_padding_size
get_window_padding_size(&self, dw_style: u32) -> Result<Size>438 pub fn get_window_padding_size(&self, dw_style: u32) -> Result<Size> {
439 static CONTEXT_MESSAGE: &str = "When calculating window padding";
440 // The padding is always the same in windowed mode, hence we can use an arbitrary rect.
441 let client_rect = Rect::new(point2(0, 0), size2(500, 500));
442 let dw_ex_style = self.get_attribute(GWL_EXSTYLE).context(CONTEXT_MESSAGE)?;
443 let window_rect: Rect = self
444 .get_adjusted_window_rect(&client_rect, dw_style, dw_ex_style as u32)
445 .context(CONTEXT_MESSAGE)?;
446 Ok(window_rect.size - client_rect.size)
447 }
448
449 /// Calls `ClientToScreen()` internally. Converts the window client area coordinates of a
450 /// specified point to screen coordinates.
client_to_screen(&self, point: &Point) -> Result<Point>451 pub fn client_to_screen(&self, point: &Point) -> Result<Point> {
452 let mut point = point.to_sys_point();
453 // SAFETY:
454 // Safe because `GuiWindow` object won't outlive the HWND, we know `point` is valid, and
455 // failures are handled below.
456 unsafe {
457 if ClientToScreen(self.hwnd, &mut point) == 0 {
458 syscall_bail!("Failed to call ClientToScreen()");
459 }
460 }
461 Ok(point.to_point())
462 }
463
464 /// Calls `ScreenToClient()` internally. Converts the screen coordinates to window client area
465 /// coordinates.
screen_to_client(&self, point: Point) -> Result<Point>466 pub fn screen_to_client(&self, point: Point) -> Result<Point> {
467 let mut point = point.to_sys_point();
468
469 // SAFETY:
470 // Safe because:
471 // 1. point is stack allocated & lives as long as the function call.
472 // 2. the window handle is guaranteed valid by self.
473 // 3. we check the error before using the output data.
474 unsafe {
475 let res = ScreenToClient(self.hwnd, point.as_mut_ptr());
476 if res == 0 {
477 syscall_bail!("failed to convert cursor position to client coordinates");
478 }
479 }
480 Ok(Point2D::new(point.x, point.y))
481 }
482
483 /// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,
484 /// returns the handle to the closest one.
get_nearest_monitor_handle(&self) -> HMONITOR485 pub fn get_nearest_monitor_handle(&self) -> HMONITOR {
486 // SAFETY:
487 // Safe because `GuiWindow` object won't outlive the HWND.
488 unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONEAREST) }
489 }
490
491 /// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,
492 /// returns the info of the closest one.
get_monitor_info(&self) -> Result<MonitorInfo>493 pub fn get_monitor_info(&self) -> Result<MonitorInfo> {
494 // SAFETY:
495 // Safe because `get_nearest_monitor_handle()` always returns a valid monitor handle.
496 unsafe { MonitorInfo::new(self.get_nearest_monitor_handle()) }
497 }
498
499 /// Calls `MonitorFromWindow()` internally.
is_on_active_display(&self) -> bool500 pub fn is_on_active_display(&self) -> bool {
501 // SAFETY:
502 // Safe because `GuiWindow` object won't outlive the HWND.
503 unsafe { !MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL).is_null() }
504 }
505
506 /// Calls `SetWindowPos()` internally.
set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()>507 pub fn set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()> {
508 // SAFETY:
509 // Safe because `GuiWindow` object won't outlive the HWND, and failures are handled below.
510 unsafe {
511 if SetWindowPos(
512 self.hwnd,
513 null_mut(),
514 window_rect.origin.x,
515 window_rect.origin.y,
516 window_rect.size.width,
517 window_rect.size.height,
518 flags,
519 ) == 0
520 {
521 syscall_bail!("Failed to call SetWindowPos()");
522 }
523 Ok(())
524 }
525 }
526
527 /// Calls `SetWindowPos()` internally. If window size and position need to be changed as well,
528 /// prefer to call `set_pos()` with the `SWP_FRAMECHANGED` flag instead.
flush_window_style_change(&self) -> Result<()>529 pub fn flush_window_style_change(&self) -> Result<()> {
530 // Because of `SWP_NOMOVE` and `SWP_NOSIZE` flags, we can pass in arbitrary window size and
531 // position as they will be ignored.
532 self.set_pos(
533 &Rect::zero(),
534 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED,
535 )
536 }
537
538 /// Calls `ShowWindow()` internally. Note that it is more preferable to call `set_pos()` with
539 /// `SWP_SHOWWINDOW` since that would set the error code on failure.
show(&self)540 pub fn show(&self) {
541 // SAFETY:
542 // Safe because `GuiWindow` object won't outlive the HWND.
543 unsafe {
544 ShowWindow(self.hwnd, SW_SHOW);
545 }
546 }
547
548 /// Calls `SetWindowPos()` internally. Returns false if the window is already hidden and thus
549 /// this operation is skipped.
hide_if_visible(&self) -> Result<bool>550 pub fn hide_if_visible(&self) -> Result<bool> {
551 Ok(if self.is_visible()? {
552 self.set_pos(
553 &Rect::zero(),
554 SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER,
555 )?;
556 true
557 } else {
558 false
559 })
560 }
561
562 /// Calls `ShowWindow()` internally to restore a minimized window.
restore(&self)563 pub fn restore(&self) {
564 // SAFETY:
565 // Safe because `GuiWindow` object won't outlive the HWND.
566 unsafe {
567 ShowWindow(self.hwnd, SW_RESTORE);
568 }
569 }
570
571 /// Calls `IsZoomed()` internally. Note that the window may carry the WS_MAXIMIZE flag until it
572 /// is restored. For example, if we have switched from maximized to fullscreen, this function
573 /// would still return true.
was_maximized(&self) -> bool574 pub fn was_maximized(&self) -> bool {
575 // SAFETY:
576 // Safe because `GuiWindow` object won't outlive the HWND.
577 unsafe { IsZoomed(self.hwnd) != 0 }
578 }
579
580 /// Calls `IsWindowVisible()` internally. We also require that the window size is nonzero to be
581 /// considered visible.
is_visible(&self) -> Result<bool>582 pub fn is_visible(&self) -> Result<bool> {
583 // SAFETY:
584 // Safe because `GuiWindow` object won't outlive the HWND.
585 if unsafe { IsWindowVisible(self.hwnd) } != 0 {
586 let window_rect = self
587 .get_window_rect()
588 .context("When querying window visibility")?;
589 if window_rect.size != Size::zero() {
590 return Ok(true);
591 } else {
592 info!("Window has WS_VISIBLE flag but its size is zero");
593 }
594 }
595 Ok(false)
596 }
597
598 /// Calls `GetForegroundWindow()` internally. A foreground window is the window with which the
599 /// user is currently working. It might belong to a different thread/process than the calling
600 /// thread.
is_global_foreground_window(&self) -> bool601 pub fn is_global_foreground_window(&self) -> bool {
602 // SAFETY:
603 // Safe because there is no argument.
604 unsafe { GetForegroundWindow() == self.hwnd }
605 }
606
607 /// Calls `GetActiveWindow()` internally. An active window is the window with which the user is
608 /// currently working and is attached to the calling thread's message queue. It is possible that
609 /// there is no active window if the foreground focus is on another thread/process.
is_thread_foreground_window(&self) -> bool610 pub fn is_thread_foreground_window(&self) -> bool {
611 // SAFETY:
612 // Safe because there is no argument.
613 unsafe { GetActiveWindow() == self.hwnd }
614 }
615
616 /// Calls `IsIconic()` internally.
is_minimized(&self) -> bool617 pub fn is_minimized(&self) -> bool {
618 // SAFETY:
619 // Safe because `GuiWindow` object won't outlive the HWND.
620 unsafe { IsIconic(self.hwnd) != 0 }
621 }
622
623 /// Calls `SetForegroundWindow()` internally. `SetForegroundWindow()` may fail, for example,
624 /// when the taskbar is in the foreground, hence this is a best-effort call.
bring_to_foreground(&self)625 pub fn bring_to_foreground(&self) {
626 // SAFETY:
627 // Safe because `GuiWindow` object won't outlive the HWND.
628 if unsafe { SetForegroundWindow(self.hwnd) } == 0 {
629 info!("Cannot bring the window to foreground.");
630 }
631 }
632
633 /// Calls `DwmEnableBlurBehindWindow()` internally. This is only used for a top-level window.
634 /// Even though the name of Windows API suggests that it blurs the background, beginning with
635 /// Windows 8, it does not blur it, but only makes the window semi-transparent.
set_backgound_transparency(&self, semi_transparent: bool) -> Result<()>636 pub fn set_backgound_transparency(&self, semi_transparent: bool) -> Result<()> {
637 let blur_behind = DWM_BLURBEHIND {
638 dwFlags: DWM_BB_ENABLE,
639 fEnable: if semi_transparent { TRUE } else { FALSE },
640 hRgnBlur: null_mut(),
641 fTransitionOnMaximized: FALSE,
642 };
643 // SAFETY:
644 // Safe because `GuiWindow` object won't outlive the HWND, we know `blur_behind` is valid,
645 // and failures are handled below.
646 let errno = unsafe { DwmEnableBlurBehindWindow(self.hwnd, &blur_behind) };
647 match errno {
648 0 => Ok(()),
649 _ => bail!(
650 "Failed to call DwmEnableBlurBehindWindow() when setting \
651 window background transparency to {} (Error code {})",
652 semi_transparent,
653 errno
654 ),
655 }
656 }
657
658 /// Calls `AdjustWindowRectExForDpi()` internally.
get_adjusted_window_rect( &self, client_rect: &Rect, dw_style: u32, dw_ex_style: u32, ) -> Result<Rect>659 pub fn get_adjusted_window_rect(
660 &self,
661 client_rect: &Rect,
662 dw_style: u32,
663 dw_ex_style: u32,
664 ) -> Result<Rect> {
665 let mut window_rect: RECT = client_rect.to_sys_rect();
666 // SAFETY:
667 // Safe because `GuiWindow` object won't outlive the HWND, we know `window_rect` is valid,
668 // and failures are handled below.
669 unsafe {
670 if AdjustWindowRectExForDpi(
671 &mut window_rect,
672 dw_style,
673 FALSE,
674 dw_ex_style,
675 GetDpiForSystem(),
676 ) == 0
677 {
678 syscall_bail!("Failed to call AdjustWindowRectExForDpi()");
679 }
680 }
681 Ok(window_rect.to_rect())
682 }
683
684 /// Calls `GetWindowPlacement()` and `SetWindowPlacement()` internally.
set_restored_pos(&self, window_rect: &Rect) -> Result<()>685 pub fn set_restored_pos(&self, window_rect: &Rect) -> Result<()> {
686 let mut window_placement = WINDOWPLACEMENT {
687 length: mem::size_of::<WINDOWPLACEMENT>().try_into().unwrap(),
688 ..Default::default()
689 };
690 // SAFETY:
691 // Safe because `GuiWindow` object won't outlive the HWND, we know `window_placement` is
692 // valid, and failures are handled below.
693 unsafe {
694 if GetWindowPlacement(self.hwnd, &mut window_placement) == 0 {
695 syscall_bail!("Failed to call GetWindowPlacement()");
696 }
697 window_placement.rcNormalPosition = window_rect.to_sys_rect();
698 if SetWindowPlacement(self.hwnd, &window_placement) == 0 {
699 syscall_bail!("Failed to call SetWindowPlacement()");
700 }
701 }
702 Ok(())
703 }
704
705 /// Calls `PostMessageW()` internally.
post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()>706 pub fn post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()> {
707 // SAFETY:
708 // Safe because `GuiWindow` object won't outlive the HWND.
709 unsafe {
710 if PostMessageW(self.hwnd, msg, w_param, l_param) == 0 {
711 syscall_bail!("Failed to call PostMessageW()");
712 }
713 }
714 Ok(())
715 }
716
717 /// Calls `LoadIconW()` internally.
load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON>718 pub(crate) fn load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON> {
719 // SAFETY:
720 // Safe because we handle failures below.
721 unsafe {
722 let hicon = LoadIconW(hinstance, MAKEINTRESOURCEW(resource_id));
723 if hicon.is_null() {
724 syscall_bail!("Failed to call LoadIconW()");
725 }
726 Ok(hicon)
727 }
728 }
729
730 /// Calls `LoadCursorW()` internally.
load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR>731 pub(crate) fn load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR> {
732 // SAFETY:
733 // Safe because we handle failures below.
734 unsafe {
735 let hcursor = LoadCursorW(null_mut(), cursor_id);
736 if hcursor.is_null() {
737 syscall_bail!("Failed to call LoadCursorW()");
738 }
739 Ok(hcursor)
740 }
741 }
742
743 /// Calls `GetStockObject()` internally.
create_opaque_black_brush() -> Result<HBRUSH>744 pub(crate) fn create_opaque_black_brush() -> Result<HBRUSH> {
745 // SAFETY:
746 // Safe because we handle failures below.
747 unsafe {
748 let hobject = GetStockObject(BLACK_BRUSH as i32);
749 if hobject.is_null() {
750 syscall_bail!("Failed to call GetStockObject()");
751 }
752 Ok(hobject as HBRUSH)
753 }
754 }
755
756 /// Calls `RegisterTouchWindow()` internally.
register_touch(&self)757 fn register_touch(&self) {
758 // SAFETY: Safe because `GuiWindow` object won't outlive the HWND.
759 if unsafe { RegisterTouchWindow(self.handle(), 0) } == 0 {
760 // For now, we register touch only to get stats. It is ok if the registration fails.
761 // SAFETY: trivially-safe
762 warn!("failed to register touch: {}", unsafe { GetLastError() });
763 }
764 }
765 }
766
767 impl BasicWindow for GuiWindow {
768 /// # Safety
769 /// The returned handle should be used carefully, since it may have become invalid if it
770 /// outlives the `GuiWindow` object.
handle(&self) -> HWND771 unsafe fn handle(&self) -> HWND {
772 self.hwnd
773 }
774 }
775
776 /// A message-only window is always invisible, and is only responsible for sending and receiving
777 /// messages. The owner of `MessageOnlyWindow` object is responsible for dropping it before the
778 /// underlying window is completely gone.
779 pub(crate) struct MessageOnlyWindow {
780 hwnd: HWND,
781 }
782
783 impl MessageOnlyWindow {
784 /// # Safety
785 /// The owner of `MessageOnlyWindow` object is responsible for dropping it before we finish
786 /// processing `WM_NCDESTROY`, because the window handle will become invalid afterwards.
new(class_name: &str, title: &str) -> Result<Self>787 pub unsafe fn new(class_name: &str, title: &str) -> Result<Self> {
788 info!("Creating message-only window");
789 static CONTEXT_MESSAGE: &str = "When creating MessageOnlyWindow";
790
791 let window = Self {
792 hwnd: create_sys_window(
793 get_current_module_handle(),
794 class_name,
795 title,
796 /* dw_style */ 0,
797 HWND_MESSAGE,
798 /* initial_window_size */ &size2(0, 0),
799 )
800 .context(CONTEXT_MESSAGE)?,
801 };
802 window.register_raw_input_mouse().context(CONTEXT_MESSAGE)?;
803 Ok(window)
804 }
805
806 /// Registers this window as the receiver of raw mouse input events.
807 ///
808 /// On Windows, an application can only have one window that receives raw input events, so we
809 /// make `MessageOnlyWindow` take on this role and reroute events to the foreground `GuiWindow`.
register_raw_input_mouse(&self) -> Result<()>810 fn register_raw_input_mouse(&self) -> Result<()> {
811 let mouse_device = RAWINPUTDEVICE {
812 usUsagePage: 1, // Generic
813 usUsage: 2, // Mouse
814 dwFlags: 0,
815 // SAFETY: Safe because `self` won't outlive the HWND.
816 hwndTarget: unsafe { self.handle() },
817 };
818 // SAFETY: Safe because `mouse_device` lives longer than this function call.
819 if unsafe {
820 RegisterRawInputDevices(
821 &mouse_device as PCRAWINPUTDEVICE,
822 1,
823 mem::size_of::<RAWINPUTDEVICE>() as u32,
824 )
825 } == 0
826 {
827 syscall_bail!("Relative mouse is broken. Failed to call RegisterRawInputDevices()");
828 }
829 Ok(())
830 }
831 }
832
833 impl BasicWindow for MessageOnlyWindow {
834 /// # Safety
835 /// The returned handle should be used carefully, since it may have become invalid if it
836 /// outlives the `MessageOnlyWindow` object.
handle(&self) -> HWND837 unsafe fn handle(&self) -> HWND {
838 self.hwnd
839 }
840 }
841
842 /// Calls `CreateWindowExW()` internally.
create_sys_window( hinstance: HINSTANCE, class_name: &str, title: &str, dw_style: DWORD, hwnd_parent: HWND, initial_window_size: &Size2D<i32, HostWindowSpace>, ) -> Result<HWND>843 fn create_sys_window(
844 hinstance: HINSTANCE,
845 class_name: &str,
846 title: &str,
847 dw_style: DWORD,
848 hwnd_parent: HWND,
849 initial_window_size: &Size2D<i32, HostWindowSpace>,
850 ) -> Result<HWND> {
851 // SAFETY:
852 // Safe because we handle failures below.
853 let hwnd = unsafe {
854 CreateWindowExW(
855 /* dwExStyle */ 0,
856 win32_wide_string(class_name).as_ptr(),
857 win32_wide_string(title).as_ptr(),
858 dw_style,
859 /* x */ 0,
860 /* y */ 0,
861 initial_window_size.width,
862 initial_window_size.height,
863 hwnd_parent,
864 /* hMenu */ null_mut(),
865 hinstance,
866 /* lpParam */ null_mut(),
867 )
868 };
869 if hwnd.is_null() {
870 syscall_bail!("Failed to call CreateWindowExW()");
871 }
872 info!("Created window {:p}", hwnd);
873 Ok(hwnd)
874 }
875
876 /// Calls `GetModuleHandleW()` internally.
get_current_module_handle() -> HMODULE877 pub(crate) fn get_current_module_handle() -> HMODULE {
878 // SAFETY:
879 // Safe because we handle failures below.
880 let hmodule = unsafe { GetModuleHandleW(null_mut()) };
881 if hmodule.is_null() {
882 // If it fails, we are in a very broken state and it doesn't make sense to keep running.
883 panic!(
884 "Failed to call GetModuleHandleW() for the current module (Error code {})",
885 // SAFETY: trivially safe
886 unsafe { GetLastError() }
887 );
888 }
889 hmodule
890 }
891
892 /// If the resolution/orientation of the monitor changes, or if the monitor is unplugged, this must
893 /// be recreated with a valid HMONITOR.
894 pub struct MonitorInfo {
895 pub hmonitor: HMONITOR,
896 pub display_rect: Rect,
897 pub work_rect: Rect,
898 raw_dpi: i32,
899 // Whether we are running in a Remote Desktop Protocol (RDP) session. The monitor DPI returned
900 // by `GetDpiForMonitor()` may not make sense in that case. For example, the DPI is always 25
901 // under Chrome Remote Desktop, which is way lower than the standard DPI 96. This might be a
902 // flaw in RDP itself. We have to override the DPI in that case, otherwise the guest DPI
903 // calculated based on it would be too low as well.
904 // https://learn.microsoft.com/en-us/troubleshoot/windows-server/shell-experience/dpi-adjustment-unavailable-in-rdp
905 is_rdp_session: bool,
906 }
907
908 impl MonitorInfo {
909 /// # Safety
910 /// Caller is responsible for ensuring that `hmonitor` is a valid handle.
new(hmonitor: HMONITOR) -> Result<Self>911 pub unsafe fn new(hmonitor: HMONITOR) -> Result<Self> {
912 let monitor_info: MONITORINFO =
913 Self::get_monitor_info(hmonitor).context("When creating MonitorInfo")?;
914 // Docs state that apart from `GetSystemMetrics(SM_REMOTESESSION)`, we also need to check
915 // registry entries to see if we are running in a remote session that uses RemoteFX vGPU:
916 // https://learn.microsoft.com/en-us/windows/win32/termserv/detecting-the-terminal-services-environment
917 // However, RemoteFX vGPU was then removed because of security vulnerabilities:
918 // https://support.microsoft.com/en-us/topic/kb4570006-update-to-disable-and-remove-the-remotefx-vgpu-component-in-windows-bbdf1531-7188-2bf4-0de6-641de79f09d2
919 // So, we are only calling `GetSystemMetrics(SM_REMOTESESSION)` here until this changes in
920 // the future.
921 // SAFETY:
922 // Safe because no memory management is needed for arguments.
923 let is_rdp_session = unsafe { GetSystemMetrics(SM_REMOTESESSION) != 0 };
924 Ok(Self {
925 hmonitor,
926 display_rect: monitor_info.rcMonitor.to_rect(),
927 work_rect: monitor_info.rcWork.to_rect(),
928 raw_dpi: Self::get_monitor_dpi(hmonitor),
929 is_rdp_session,
930 })
931 }
932
get_dpi(&self) -> i32933 pub fn get_dpi(&self) -> i32 {
934 if self.is_rdp_session {
935 // Override the DPI since the system may not tell us the correct value in RDP sessions.
936 DEFAULT_HOST_DPI
937 } else {
938 self.raw_dpi
939 }
940 }
941
942 /// Calls `GetMonitorInfoW()` internally.
943 /// # Safety
944 /// Caller is responsible for ensuring that `hmonitor` is a valid handle.
get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO>945 unsafe fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO> {
946 let mut monitor_info = MONITORINFO {
947 cbSize: mem::size_of::<MONITORINFO>().try_into().unwrap(),
948 ..Default::default()
949 };
950 if GetMonitorInfoW(hmonitor, &mut monitor_info) == 0 {
951 syscall_bail!("Failed to call GetMonitorInfoW()");
952 }
953 Ok(monitor_info)
954 }
955
956 /// Calls `GetDpiForMonitor()` internally.
get_monitor_dpi(hmonitor: HMONITOR) -> i32957 fn get_monitor_dpi(hmonitor: HMONITOR) -> i32 {
958 let mut dpi_x = 0;
959 let mut dpi_y = 0;
960 // SAFETY:
961 // This is always safe since `GetDpiForMonitor` won't crash if HMONITOR is invalid, but
962 // return E_INVALIDARG.
963 unsafe {
964 if GetDpiForMonitor(hmonitor, MDT_RAW_DPI, &mut dpi_x, &mut dpi_y) == S_OK
965 || GetDpiForMonitor(hmonitor, MDT_DEFAULT, &mut dpi_x, &mut dpi_y) == S_OK
966 {
967 // We assume screen pixels are square and DPI in different directions are the same.
968 dpi_x as i32
969 } else {
970 error!("Failed to retrieve DPI with HMONITOR {:p}", hmonitor);
971 DEFAULT_HOST_DPI
972 }
973 }
974 }
975 }
976
977 impl fmt::Debug for MonitorInfo {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result978 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
979 write!(
980 f,
981 "{{hmonitor: {:p}, display_rect: {:?}, work_rect: {:?}, DPI: {}{}}}",
982 self.hmonitor,
983 self.display_rect,
984 self.work_rect,
985 self.get_dpi(),
986 if self.is_rdp_session {
987 format!(" (raw value: {}, overriden due to RDP)", self.raw_dpi)
988 } else {
989 String::new()
990 }
991 )
992 }
993 }
994