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.
282 #[allow(dead_code)]
remove_property(&self, property: &str) -> Result<()>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.
destroy(&self) -> Result<()>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.
new( scanout_id: u32, class_name: &str, title: &str, dw_style: DWORD, initial_window_size: &Size2D<i32, HostWindowSpace>, ) -> Result<Self>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
scanout_id(&self) -> u32348 pub fn scanout_id(&self) -> u32 {
349 self.scanout_id
350 }
351
update_states(&mut self, msg: UINT, w_param: WPARAM)352 pub fn update_states(&mut self, msg: UINT, w_param: WPARAM) {
353 self.size_move_loop_state.update(msg, w_param);
354 }
355
is_sizing_or_moving(&self) -> bool356 pub fn is_sizing_or_moving(&self) -> bool {
357 self.size_move_loop_state.is_in_loop()
358 }
359
is_resizing_loop_starting(&self) -> bool360 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.
is_valid(&self) -> bool365 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.
get_attribute(&self, index: i32) -> Result<isize>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.
set_attribute(&self, index: i32, value: isize) -> Result<()>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.
get_window_rect(&self) -> Result<Rect>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.
get_window_origin(&self) -> Result<Point>418 pub fn get_window_origin(&self) -> Result<Point> {
419 Ok(self.get_window_rect()?.origin)
420 }
421
422 /// Calls `GetClientRect()` internally.
get_client_rect(&self) -> Result<Rect>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
get_window_padding_size(&self, dw_style: u32) -> Result<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.
client_to_screen(&self, point: &Point) -> Result<Point>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.
screen_to_client(&self, point: Point) -> Result<Point>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.
get_nearest_monitor_handle(&self) -> HMONITOR486 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.
get_monitor_info(&self) -> Result<MonitorInfo>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.
is_on_active_display(&self) -> bool501 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.
set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()>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.
flush_window_style_change(&self) -> Result<()>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.
show(&self)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.
hide_if_visible(&self) -> Result<bool>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.
restore(&self)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.
was_maximized(&self) -> bool575 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.
is_visible(&self) -> Result<bool>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.
is_global_foreground_window(&self) -> bool602 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.
is_thread_foreground_window(&self) -> bool611 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.
is_minimized(&self) -> bool618 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.
bring_to_foreground(&self)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.
set_backgound_transparency(&self, semi_transparent: bool) -> Result<()>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.
get_adjusted_window_rect( &self, client_rect: &Rect, dw_style: u32, dw_ex_style: u32, ) -> Result<Rect>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.
set_restored_pos(&self, window_rect: &Rect) -> Result<()>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.
post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()>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.
load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON>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.
load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR>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.
create_opaque_black_brush() -> Result<HBRUSH>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.
register_touch(&self)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.
handle(&self) -> HWND772 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.
new(class_name: &str, title: &str) -> Result<Self>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`.
register_raw_input_mouse(&self) -> Result<()>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.
handle(&self) -> HWND838 unsafe fn handle(&self) -> HWND {
839 self.hwnd
840 }
841 }
842
843 /// 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>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.
get_current_module_handle() -> HMODULE878 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.
new(hmonitor: HMONITOR) -> Result<Self>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
get_dpi(&self) -> i32934 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.
get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO>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.
get_monitor_dpi(hmonitor: HMONITOR) -> i32958 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 {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result979 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