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 #[cfg(feature = "gfxstream")] 9 use std::os::raw::c_int; 10 use std::os::raw::c_void; 11 use std::ptr::null_mut; 12 13 use anyhow::bail; 14 use anyhow::Context; 15 use anyhow::Result; 16 use base::error; 17 use base::info; 18 use base::warn; 19 use euclid::point2; 20 use euclid::size2; 21 use euclid::Box2D; 22 use euclid::Size2D; 23 use vm_control::display::WindowVisibility; 24 use win_util::syscall_bail; 25 use win_util::win32_wide_string; 26 use winapi::shared::minwindef::DWORD; 27 use winapi::shared::minwindef::FALSE; 28 use winapi::shared::minwindef::HINSTANCE; 29 use winapi::shared::minwindef::HMODULE; 30 use winapi::shared::minwindef::LPARAM; 31 use winapi::shared::minwindef::LRESULT; 32 use winapi::shared::minwindef::TRUE; 33 use winapi::shared::minwindef::UINT; 34 use winapi::shared::minwindef::WORD; 35 use winapi::shared::minwindef::WPARAM; 36 use winapi::shared::windef::HBRUSH; 37 use winapi::shared::windef::HCURSOR; 38 use winapi::shared::windef::HICON; 39 use winapi::shared::windef::HMONITOR; 40 use winapi::shared::windef::HWND; 41 use winapi::shared::windef::RECT; 42 use winapi::shared::winerror::S_OK; 43 use winapi::um::dwmapi::DwmEnableBlurBehindWindow; 44 use winapi::um::dwmapi::DWM_BB_ENABLE; 45 use winapi::um::dwmapi::DWM_BLURBEHIND; 46 use winapi::um::errhandlingapi::GetLastError; 47 use winapi::um::errhandlingapi::SetLastError; 48 use winapi::um::libloaderapi::GetModuleHandleW; 49 use winapi::um::shellscalingapi::GetDpiForMonitor; 50 use winapi::um::shellscalingapi::MDT_DEFAULT; 51 use winapi::um::shellscalingapi::MDT_RAW_DPI; 52 use winapi::um::wingdi::GetStockObject; 53 use winapi::um::wingdi::BLACK_BRUSH; 54 use winapi::um::winnt::LPCWSTR; 55 use winapi::um::winuser::*; 56 57 use super::math_util::*; 58 use super::HostWindowSpace; 59 60 #[cfg(feature = "gfxstream")] 61 #[link(name = "gfxstream_backend")] 62 extern "C" { gfxstream_backend_setup_window( hwnd: *const c_void, window_x: c_int, window_y: c_int, window_width: c_int, window_height: c_int, fb_width: c_int, fb_height: c_int, )63 fn gfxstream_backend_setup_window( 64 hwnd: *const c_void, 65 window_x: c_int, 66 window_y: c_int, 67 window_width: c_int, 68 window_height: c_int, 69 fb_width: c_int, 70 fb_height: c_int, 71 ); 72 } 73 74 // Windows desktop's default DPI at default scaling settings is 96. 75 // (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mpc/pixel-density-and-usability) 76 pub(crate) const DEFAULT_HOST_DPI: i32 = 96; 77 78 /// Stores a message retrieved from the message pump. We don't include the HWND since it is only 79 /// used for determining the recipient. 80 #[derive(Copy, Clone, Debug)] 81 pub struct MessagePacket { 82 pub msg: UINT, 83 pub w_param: WPARAM, 84 pub l_param: LPARAM, 85 } 86 87 impl MessagePacket { new(msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Self88 pub fn new(msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Self { 89 Self { 90 msg, 91 w_param, 92 l_param, 93 } 94 } 95 } 96 97 impl From<MSG> for MessagePacket { from(message: MSG) -> Self98 fn from(message: MSG) -> Self { 99 Self::new(message.message, message.wParam, message.lParam) 100 } 101 } 102 103 /// The state of window moving or sizing modal loop. 104 /// 105 /// We do receive `WM_ENTERSIZEMOVE` when the window is about to be resized or moved, but it doesn't 106 /// tell us whether resizing or moving should be expected. We won't know that until later we receive 107 /// `WM_SIZING` or `WM_MOVING`. Corner cases are: 108 /// (1) If the user long presses the title bar, window borders or corners, and then releases without 109 /// moving the mouse, we would receive both `WM_ENTERSIZEMOVE` and `WM_EXITSIZEMOVE`, but 110 /// without any `WM_SIZING` or `WM_MOVING` in between. 111 /// (2) When the window is maximized, if we drag the title bar of it, it will be restored to the 112 /// normal size and then move along with the cursor. In this case, we would expect 113 /// `WM_ENTERSIZEMOVE` to be followed by one `WM_SIZING`, and then multiple `WM_MOVING`. 114 /// 115 /// This enum tracks the modal loop state. Possible state transition: 116 /// (1) NotInLoop -> WillResizeOrMove -> IsResizing -> NotInLoop. This is for sizing modal loops. 117 /// (2) NotInLoop -> WillResizeOrMove -> IsMoving -> NotInLoop. This is for moving modal loops. 118 /// (3) NotInLoop -> WillResizeOrMove -> NotInLoop. This may occur if the user long presses the 119 /// window title bar, window borders or corners, but doesn't actually resize or move the window. 120 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 121 enum SizeMoveLoopState { 122 /// The window is not in the moving or sizing modal loop. 123 NotInLoop, 124 /// We have received `WM_ENTERSIZEMOVE` but haven't received either `WM_SIZING` or `WM_MOVING`, 125 /// so we don't know if the window is going to be resized or moved at this point. 126 WillResizeOrMove, 127 /// We have received `WM_SIZING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is 128 /// the first `WM_SIZING`. 129 IsResizing { is_first: bool }, 130 /// We have received `WM_MOVING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is 131 /// the first `WM_MOVING`. 132 IsMoving { is_first: bool }, 133 } 134 135 impl SizeMoveLoopState { new() -> Self136 pub fn new() -> Self { 137 Self::NotInLoop 138 } 139 update(&mut self, msg: UINT, w_param: WPARAM)140 pub fn update(&mut self, msg: UINT, w_param: WPARAM) { 141 match msg { 142 WM_ENTERSIZEMOVE => self.on_entering_loop(), 143 WM_EXITSIZEMOVE => self.on_exiting_loop(), 144 WM_SIZING => self.on_resizing_window(w_param), 145 WM_MOVING => self.on_moving_window(), 146 _ => (), 147 }; 148 } 149 is_in_loop(&self) -> bool150 pub fn is_in_loop(&self) -> bool { 151 *self != Self::NotInLoop 152 } 153 is_resizing_starting(&self) -> bool154 pub fn is_resizing_starting(&self) -> bool { 155 *self == Self::IsResizing { is_first: true } 156 } 157 on_entering_loop(&mut self)158 fn on_entering_loop(&mut self) { 159 info!("Entering window sizing/moving modal loop"); 160 *self = Self::WillResizeOrMove; 161 } 162 on_exiting_loop(&mut self)163 fn on_exiting_loop(&mut self) { 164 info!("Exiting window sizing/moving modal loop"); 165 *self = Self::NotInLoop; 166 } 167 on_resizing_window(&mut self, w_param: WPARAM)168 fn on_resizing_window(&mut self, w_param: WPARAM) { 169 match *self { 170 Self::NotInLoop => (), 171 Self::WillResizeOrMove => match w_param as u32 { 172 // In these cases, the user is dragging window borders or corners for resizing. 173 WMSZ_LEFT | WMSZ_RIGHT | WMSZ_TOP | WMSZ_BOTTOM | WMSZ_TOPLEFT | WMSZ_TOPRIGHT 174 | WMSZ_BOTTOMLEFT | WMSZ_BOTTOMRIGHT => { 175 info!("Window is being resized"); 176 *self = Self::IsResizing { is_first: true }; 177 } 178 // In this case, the user is dragging the title bar of the maximized window. The 179 // window will be restored to the normal size and then move along with the cursor, 180 // so we can expect `WM_MOVING` coming and entering the moving modal loop. 181 _ => info!("Window is being restored"), 182 }, 183 Self::IsResizing { .. } => *self = Self::IsResizing { is_first: false }, 184 Self::IsMoving { .. } => warn!("WM_SIZING is unexpected in moving modal loops!"), 185 } 186 } 187 on_moving_window(&mut self)188 fn on_moving_window(&mut self) { 189 match *self { 190 Self::NotInLoop => (), 191 Self::WillResizeOrMove => { 192 info!("Window is being moved"); 193 *self = Self::IsMoving { is_first: true }; 194 } 195 Self::IsMoving { .. } => *self = Self::IsMoving { is_first: false }, 196 Self::IsResizing { .. } => warn!("WM_MOVING is unexpected in sizing modal loops!"), 197 } 198 } 199 } 200 201 /// This class helps create and operate on a window using Windows APIs. The owner of `Window` object 202 /// is responsible for: 203 /// (1) Calling `update_states()` when a new window message arrives. 204 /// (2) Dropping the `Window` object before the underlying window is completely gone. 205 pub struct Window { 206 hwnd: HWND, 207 size_move_loop_state: SizeMoveLoopState, 208 } 209 210 impl Window { 211 /// # Safety 212 /// The owner of `Window` object is responsible for dropping it before we finish processing 213 /// `WM_NCDESTROY`, because the window handle will become invalid afterwards. new( wnd_proc: WNDPROC, class_name: &str, title: &str, icon_resource_id: WORD, dw_style: DWORD, initial_window_size: &Size2D<i32, HostWindowSpace>, ) -> Result<Self>214 pub unsafe fn new( 215 wnd_proc: WNDPROC, 216 class_name: &str, 217 title: &str, 218 icon_resource_id: WORD, 219 dw_style: DWORD, 220 initial_window_size: &Size2D<i32, HostWindowSpace>, 221 ) -> Result<Self> { 222 info!("Creating window"); 223 static CONTEXT_MESSAGE: &str = "When creating Window"; 224 225 let hinstance = Self::get_current_module_handle(); 226 // If we fail to load any UI element below, use NULL to let the system use the default UI 227 // rather than crash. 228 let hicon = Self::load_custom_icon(hinstance, icon_resource_id).unwrap_or(null_mut()); 229 let hcursor = Self::load_system_cursor(IDC_ARROW).unwrap_or(null_mut()); 230 let hbrush_background = Self::create_opaque_black_brush().unwrap_or(null_mut()); 231 232 Self::register_window_class( 233 wnd_proc, 234 hinstance, 235 class_name, 236 hicon, 237 hcursor, 238 hbrush_background, 239 ) 240 .context(CONTEXT_MESSAGE)?; 241 242 let hwnd = 243 Self::create_sys_window(hinstance, class_name, title, dw_style, initial_window_size) 244 .context(CONTEXT_MESSAGE)?; 245 246 Ok(Self { 247 hwnd, 248 size_move_loop_state: SizeMoveLoopState::new(), 249 }) 250 } 251 252 /// # Safety 253 /// The returned handle should be used carefully, since it may have become invalid if it 254 /// outlives the `Window` object. handle(&self) -> HWND255 pub unsafe fn handle(&self) -> HWND { 256 self.hwnd 257 } 258 is_same_window(&self, hwnd: HWND) -> bool259 pub fn is_same_window(&self, hwnd: HWND) -> bool { 260 hwnd == self.hwnd 261 } 262 update_states(&mut self, msg: UINT, w_param: WPARAM)263 pub fn update_states(&mut self, msg: UINT, w_param: WPARAM) { 264 self.size_move_loop_state.update(msg, w_param); 265 } 266 is_sizing_or_moving(&self) -> bool267 pub fn is_sizing_or_moving(&self) -> bool { 268 self.size_move_loop_state.is_in_loop() 269 } 270 is_resizing_loop_starting(&self) -> bool271 pub fn is_resizing_loop_starting(&self) -> bool { 272 self.size_move_loop_state.is_resizing_starting() 273 } 274 275 /// Calls `IsWindow()` internally. Returns true if the HWND identifies an existing window. is_valid(&self) -> bool276 pub fn is_valid(&self) -> bool { 277 // Safe because it is called from the same thread the created the window. 278 unsafe { IsWindow(self.hwnd) != 0 } 279 } 280 281 /// Calls `SetPropW()` internally. set_property(&self, property: &str, data: *mut c_void) -> Result<()>282 pub fn set_property(&self, property: &str, data: *mut c_void) -> Result<()> { 283 // Safe because `Window` object won't outlive the HWND, and failures are handled below. 284 unsafe { 285 if SetPropW(self.hwnd, win32_wide_string(property).as_ptr(), data) == 0 { 286 syscall_bail!("Failed to call SetPropW()"); 287 } 288 } 289 Ok(()) 290 } 291 292 /// Calls `RemovePropW()` internally. remove_property(&self, property: &str) -> Result<()>293 pub fn remove_property(&self, property: &str) -> Result<()> { 294 // Safe because `Window` object won't outlive the HWND, and failures are handled below. 295 unsafe { 296 SetLastError(0); 297 RemovePropW(self.hwnd, win32_wide_string(property).as_ptr()); 298 if GetLastError() != 0 { 299 syscall_bail!("Failed to call RemovePropW()"); 300 } 301 } 302 Ok(()) 303 } 304 305 /// Updates the rectangle in the window's client area to which gfxstream renders. update_virtual_display_projection( &self, #[allow(unused)] projection_box: &Box2D<i32, HostWindowSpace>, )306 pub fn update_virtual_display_projection( 307 &self, 308 #[allow(unused)] projection_box: &Box2D<i32, HostWindowSpace>, 309 ) { 310 // Safe because `Window` object won't outlive the HWND. 311 #[cfg(feature = "gfxstream")] 312 unsafe { 313 gfxstream_backend_setup_window( 314 self.hwnd as *const c_void, 315 projection_box.min.x, 316 projection_box.min.y, 317 projection_box.width(), 318 projection_box.height(), 319 projection_box.width(), 320 projection_box.height(), 321 ); 322 } 323 } 324 325 /// Calls `GetWindowLongPtrW()` internally. get_attribute(&self, index: i32) -> Result<isize>326 pub fn get_attribute(&self, index: i32) -> Result<isize> { 327 // Safe because `Window` object won't outlive the HWND, and failures are handled below. 328 unsafe { 329 // GetWindowLongPtrW() may return zero if we haven't set that attribute before, so we 330 // need to check if the error code is non-zero. 331 SetLastError(0); 332 let value = GetWindowLongPtrW(self.hwnd, index); 333 if value == 0 && GetLastError() != 0 { 334 syscall_bail!("Failed to call GetWindowLongPtrW()"); 335 } 336 Ok(value) 337 } 338 } 339 340 /// Calls `SetWindowLongPtrW()` internally. set_attribute(&self, index: i32, value: isize) -> Result<()>341 pub fn set_attribute(&self, index: i32, value: isize) -> Result<()> { 342 // Safe because `Window` object won't outlive the HWND, and failures are handled below. 343 unsafe { 344 // SetWindowLongPtrW() may return zero if the previous value of that attribute was zero, 345 // so we need to check if the error code is non-zero. 346 SetLastError(0); 347 let prev_value = SetWindowLongPtrW(self.hwnd, index, value); 348 if prev_value == 0 && GetLastError() != 0 { 349 syscall_bail!("Failed to call SetWindowLongPtrW()"); 350 } 351 Ok(()) 352 } 353 } 354 355 /// Calls `GetWindowRect()` internally. get_window_rect(&self) -> Result<Rect>356 pub fn get_window_rect(&self) -> Result<Rect> { 357 let mut rect: RECT = Default::default(); 358 // Safe because `Window` object won't outlive the HWND, we know `rect` is valid, and 359 // failures are handled below. 360 unsafe { 361 if GetWindowRect(self.hwnd, &mut rect) == 0 { 362 syscall_bail!("Failed to call GetWindowRect()"); 363 } 364 } 365 Ok(rect.to_rect()) 366 } 367 368 /// Calls `GetWindowRect()` internally. get_window_origin(&self) -> Result<Point>369 pub fn get_window_origin(&self) -> Result<Point> { 370 Ok(self.get_window_rect()?.origin) 371 } 372 373 /// Calls `GetClientRect()` internally. get_client_rect(&self) -> Result<Rect>374 pub fn get_client_rect(&self) -> Result<Rect> { 375 let mut rect: RECT = Default::default(); 376 // Safe because `Window` object won't outlive the HWND, we know `rect` is valid, and 377 // failures are handled below. 378 unsafe { 379 if GetClientRect(self.hwnd, &mut rect) == 0 { 380 syscall_bail!("Failed to call GetClientRect()"); 381 } 382 } 383 Ok(rect.to_rect()) 384 } 385 386 /// The system may add adornments around the client area of the window, such as the title bar 387 /// and borders. This function returns the size of all those paddings. It can be assumed that: 388 /// window_size = client_size + window_padding_size get_window_padding_size(&self, dw_style: u32) -> Result<Size>389 pub fn get_window_padding_size(&self, dw_style: u32) -> Result<Size> { 390 static CONTEXT_MESSAGE: &str = "When calculating window padding"; 391 // The padding is always the same in windowed mode, hence we can use an arbitrary rect. 392 let client_rect = Rect::new(point2(0, 0), size2(500, 500)); 393 let dw_ex_style = self.get_attribute(GWL_EXSTYLE).context(CONTEXT_MESSAGE)?; 394 let window_rect: Rect = self 395 .get_adjusted_window_rect(&client_rect, dw_style, dw_ex_style as u32) 396 .context(CONTEXT_MESSAGE)?; 397 Ok(window_rect.size - client_rect.size) 398 } 399 400 /// Calls `ClientToScreen()` internally. Converts the window client area coordinates of a 401 /// specified point to screen coordinates. client_to_screen(&self, point: &Point) -> Result<Point>402 pub fn client_to_screen(&self, point: &Point) -> Result<Point> { 403 let mut point = point.to_sys_point(); 404 // Safe because `Window` object won't outlive the HWND, we know `point` is valid, and 405 // failures are handled below. 406 unsafe { 407 if ClientToScreen(self.hwnd, &mut point) == 0 { 408 syscall_bail!("Failed to call ClientToScreen()"); 409 } 410 } 411 Ok(point.to_point()) 412 } 413 414 /// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor, 415 /// returns the handle to the closest one. get_nearest_monitor_handle(&self) -> HMONITOR416 pub fn get_nearest_monitor_handle(&self) -> HMONITOR { 417 // Safe because `Window` object won't outlive the HWND. 418 unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONEAREST) } 419 } 420 421 /// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor, 422 /// returns the info of the closest one. get_monitor_info(&self) -> Result<MonitorInfo>423 pub fn get_monitor_info(&self) -> Result<MonitorInfo> { 424 // Safe because `get_nearest_monitor_handle()` always returns a valid monitor handle. 425 unsafe { MonitorInfo::new(self.get_nearest_monitor_handle()) } 426 } 427 428 /// Calls `MonitorFromWindow()` internally. is_on_active_display(&self) -> bool429 pub fn is_on_active_display(&self) -> bool { 430 // Safe because `Window` object won't outlive the HWND. 431 unsafe { !MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL).is_null() } 432 } 433 434 /// Calls `SetWindowPos()` internally. set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()>435 pub fn set_pos(&self, window_rect: &Rect, flags: u32) -> Result<()> { 436 // Safe because `Window` object won't outlive the HWND, and failures are handled below. 437 unsafe { 438 if SetWindowPos( 439 self.hwnd, 440 null_mut(), 441 window_rect.origin.x, 442 window_rect.origin.y, 443 window_rect.size.width, 444 window_rect.size.height, 445 flags, 446 ) == 0 447 { 448 syscall_bail!("Failed to call SetWindowPos()"); 449 } 450 Ok(()) 451 } 452 } 453 454 /// Calls `SetWindowPos()` internally. If window size and position need to be changed as well, 455 /// prefer to call `set_pos()` with the `SWP_FRAMECHANGED` flag instead. flush_window_style_change(&self) -> Result<()>456 pub fn flush_window_style_change(&self) -> Result<()> { 457 // Because of `SWP_NOMOVE` and `SWP_NOSIZE` flags, we can pass in arbitrary window size and 458 // position as they will be ignored. 459 self.set_pos( 460 &Rect::zero(), 461 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 462 ) 463 } 464 465 /// Calls `ShowWindow()` internally. Note that it is more preferable to call `set_pos()` with 466 /// `SWP_SHOWWINDOW` since that would set the error code on failure. show(&self)467 pub fn show(&self) { 468 // Safe because `Window` object won't outlive the HWND. 469 unsafe { 470 ShowWindow(self.hwnd, SW_SHOW); 471 } 472 } 473 474 /// Calls `IsZoomed()` internally. Note that the window may carry the WS_MAXIMIZE flag until it 475 /// is restored. For example, if we have switched from maximized to fullscreen, this function 476 /// would still return true. was_maximized(&self) -> bool477 pub fn was_maximized(&self) -> bool { 478 // Safe because `Window` object won't outlive the HWND. 479 unsafe { IsZoomed(self.hwnd) != 0 } 480 } 481 482 /// Calls `IsWindowVisible()` internally. We also require that the window size is nonzero to be 483 /// considered visible. is_visible(&self) -> Result<bool>484 pub fn is_visible(&self) -> Result<bool> { 485 // Safe because `Window` object won't outlive the HWND. 486 if unsafe { IsWindowVisible(self.hwnd) } != 0 { 487 let window_rect = self 488 .get_window_rect() 489 .context("When querying window visibility")?; 490 if window_rect.size != Size::zero() { 491 return Ok(true); 492 } else { 493 info!("Window has WS_VISIBLE flag but its size is zero"); 494 } 495 } 496 Ok(false) 497 } 498 get_visibility(&self) -> Result<WindowVisibility>499 pub fn get_visibility(&self) -> Result<WindowVisibility> { 500 Ok(if !self.is_visible()? { 501 WindowVisibility::Hidden 502 } else if self.is_minimized() { 503 WindowVisibility::Minimized 504 } else { 505 WindowVisibility::Normal 506 }) 507 } 508 509 /// Calls `GetForegroundWindow()` internally. A foreground window is the window with which the 510 /// user is currently working. It might belong to a different thread/process than the calling 511 /// thread. is_global_foreground_window(&self) -> bool512 pub fn is_global_foreground_window(&self) -> bool { 513 // Safe because there is no argument. 514 unsafe { GetForegroundWindow() == self.hwnd } 515 } 516 517 /// Calls `GetActiveWindow()` internally. An active window is the window with which the user is 518 /// currently working and is attached to the calling thread's message queue. It is possible that 519 /// there is no active window if the foreground focus is on another thread/process. is_thread_foreground_window(&self) -> bool520 pub fn is_thread_foreground_window(&self) -> bool { 521 // Safe because there is no argument. 522 unsafe { GetActiveWindow() == self.hwnd } 523 } 524 525 /// Calls `IsIconic()` internally. is_minimized(&self) -> bool526 pub fn is_minimized(&self) -> bool { 527 // Safe because `Window` object won't outlive the HWND. 528 unsafe { IsIconic(self.hwnd) != 0 } 529 } 530 531 /// Calls `SetForegroundWindow()` internally. `SetForegroundWindow()` may fail, for example, 532 /// when the taskbar is in the foreground, hence this is a best-effort call. bring_to_foreground(&self)533 pub fn bring_to_foreground(&self) { 534 // Safe because `Window` object won't outlive the HWND. 535 if unsafe { SetForegroundWindow(self.hwnd) } == 0 { 536 info!("Cannot bring the window to foreground."); 537 } 538 } 539 540 /// Calls `DwmEnableBlurBehindWindow()` internally. This is only used for a top-level window. 541 /// Even though the name of Windows API suggests that it blurs the background, beginning with 542 /// Windows 8, it does not blur it, but only makes the window semi-transparent. set_transparency(&self, is_transparent: bool) -> Result<()>543 pub fn set_transparency(&self, is_transparent: bool) -> Result<()> { 544 let blur_behind = DWM_BLURBEHIND { 545 dwFlags: DWM_BB_ENABLE, 546 fEnable: if is_transparent { TRUE } else { FALSE }, 547 hRgnBlur: null_mut(), 548 fTransitionOnMaximized: FALSE, 549 }; 550 // Safe because `Window` object won't outlive the HWND, we know `blur_behind` is valid, 551 // and failures are handled below. 552 let errno = unsafe { DwmEnableBlurBehindWindow(self.hwnd, &blur_behind) }; 553 match errno { 554 0 => Ok(()), 555 _ => bail!( 556 "Failed to call DwmEnableBlurBehindWindow() when setting \ 557 window transparency to {} (Error code {})", 558 is_transparent, 559 errno 560 ), 561 } 562 } 563 564 /// Calls `AdjustWindowRectExForDpi()` internally. get_adjusted_window_rect( &self, client_rect: &Rect, dw_style: u32, dw_ex_style: u32, ) -> Result<Rect>565 pub fn get_adjusted_window_rect( 566 &self, 567 client_rect: &Rect, 568 dw_style: u32, 569 dw_ex_style: u32, 570 ) -> Result<Rect> { 571 let mut window_rect: RECT = client_rect.to_sys_rect(); 572 // Safe because `Window` object won't outlive the HWND, we know `window_rect` is valid, 573 // and failures are handled below. 574 unsafe { 575 if AdjustWindowRectExForDpi( 576 &mut window_rect, 577 dw_style, 578 FALSE, 579 dw_ex_style, 580 GetDpiForSystem(), 581 ) == 0 582 { 583 syscall_bail!("Failed to call AdjustWindowRectExForDpi()"); 584 } 585 } 586 Ok(window_rect.to_rect()) 587 } 588 589 /// Calls `GetWindowPlacement()` and `SetWindowPlacement()` internally. set_restored_pos(&self, window_rect: &Rect) -> Result<()>590 pub fn set_restored_pos(&self, window_rect: &Rect) -> Result<()> { 591 let mut window_placement = WINDOWPLACEMENT { 592 length: mem::size_of::<WINDOWPLACEMENT>().try_into().unwrap(), 593 ..Default::default() 594 }; 595 // Safe because `Window` object won't outlive the HWND, we know `window_placement` is valid, 596 // and failures are handled below. 597 unsafe { 598 if GetWindowPlacement(self.hwnd, &mut window_placement) == 0 { 599 syscall_bail!("Failed to call GetWindowPlacement()"); 600 } 601 window_placement.rcNormalPosition = window_rect.to_sys_rect(); 602 if SetWindowPlacement(self.hwnd, &window_placement) == 0 { 603 syscall_bail!("Failed to call SetWindowPlacement()"); 604 } 605 } 606 Ok(()) 607 } 608 609 /// Calls `PostMessageW()` internally. post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()>610 pub fn post_message(&self, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Result<()> { 611 // Safe because `Window` object won't outlive the HWND. 612 unsafe { 613 if PostMessageW(self.hwnd, msg, w_param, l_param) == 0 { 614 syscall_bail!("Failed to call PostMessageW()"); 615 } 616 } 617 Ok(()) 618 } 619 620 /// Calls `DestroyWindow()` internally. destroy(&self) -> Result<()>621 pub fn destroy(&self) -> Result<()> { 622 // Safe because `Window` object won't outlive the HWND. 623 unsafe { 624 if DestroyWindow(self.hwnd) == 0 { 625 syscall_bail!("Failed to call DestroyWindow()"); 626 } 627 } 628 Ok(()) 629 } 630 631 /// Calls `DefWindowProcW()` internally. default_process_message(&self, packet: &MessagePacket) -> LRESULT632 pub fn default_process_message(&self, packet: &MessagePacket) -> LRESULT { 633 // Safe because `Window` object won't outlive the HWND. 634 unsafe { DefWindowProcW(self.hwnd, packet.msg, packet.w_param, packet.l_param) } 635 } 636 637 /// Calls `GetModuleHandleW()` internally. get_current_module_handle() -> HMODULE638 fn get_current_module_handle() -> HMODULE { 639 // Safe because we handle failures below. 640 let hmodule = unsafe { GetModuleHandleW(null_mut()) }; 641 if hmodule.is_null() { 642 // If it fails, we are in a very broken state and it doesn't make sense to keep running. 643 panic!( 644 "Failed to call GetModuleHandleW() for the current module (Error code {})", 645 unsafe { GetLastError() } 646 ); 647 } 648 hmodule 649 } 650 651 /// Calls `LoadIconW()` internally. load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON>652 fn load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON> { 653 // Safe because we handle failures below. 654 unsafe { 655 let hicon = LoadIconW(hinstance, MAKEINTRESOURCEW(resource_id)); 656 if hicon.is_null() { 657 syscall_bail!("Failed to call LoadIconW()"); 658 } 659 Ok(hicon) 660 } 661 } 662 663 /// Calls `LoadCursorW()` internally. load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR>664 fn load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR> { 665 // Safe because we handle failures below. 666 unsafe { 667 let hcursor = LoadCursorW(null_mut(), cursor_id); 668 if hcursor.is_null() { 669 syscall_bail!("Failed to call LoadCursorW()"); 670 } 671 Ok(hcursor) 672 } 673 } 674 675 /// Calls `GetStockObject()` internally. create_opaque_black_brush() -> Result<HBRUSH>676 fn create_opaque_black_brush() -> Result<HBRUSH> { 677 // Safe because we handle failures below. 678 unsafe { 679 let hobject = GetStockObject(BLACK_BRUSH as i32); 680 if hobject.is_null() { 681 syscall_bail!("Failed to call GetStockObject()"); 682 } 683 Ok(hobject as HBRUSH) 684 } 685 } 686 register_window_class( wnd_proc: WNDPROC, hinstance: HINSTANCE, class_name: &str, hicon: HICON, hcursor: HCURSOR, hbrush_background: HBRUSH, ) -> Result<()>687 fn register_window_class( 688 wnd_proc: WNDPROC, 689 hinstance: HINSTANCE, 690 class_name: &str, 691 hicon: HICON, 692 hcursor: HCURSOR, 693 hbrush_background: HBRUSH, 694 ) -> Result<()> { 695 let class_name = win32_wide_string(class_name); 696 let window_class = WNDCLASSEXW { 697 cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32, 698 style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW, 699 lpfnWndProc: wnd_proc, 700 cbClsExtra: 0, 701 cbWndExtra: 0, 702 hInstance: hinstance, 703 hIcon: hicon, 704 hCursor: hcursor, 705 hbrBackground: hbrush_background, 706 lpszMenuName: null_mut(), 707 lpszClassName: class_name.as_ptr(), 708 hIconSm: hicon, 709 }; 710 711 // Safe because we know the lifetime of `window_class`, and we handle failures below. 712 unsafe { 713 if RegisterClassExW(&window_class) == 0 { 714 syscall_bail!("Failed to call RegisterClassExW()"); 715 } 716 Ok(()) 717 } 718 } 719 create_sys_window( hinstance: HINSTANCE, class_name: &str, title: &str, dw_style: DWORD, initial_window_size: &Size2D<i32, HostWindowSpace>, ) -> Result<HWND>720 fn create_sys_window( 721 hinstance: HINSTANCE, 722 class_name: &str, 723 title: &str, 724 dw_style: DWORD, 725 initial_window_size: &Size2D<i32, HostWindowSpace>, 726 ) -> Result<HWND> { 727 // Safe because we handle failures below. 728 unsafe { 729 let hwnd = CreateWindowExW( 730 0, 731 win32_wide_string(class_name).as_ptr(), 732 win32_wide_string(title).as_ptr(), 733 dw_style, 734 0, 735 0, 736 initial_window_size.width, 737 initial_window_size.height, 738 null_mut(), 739 null_mut(), 740 hinstance, 741 null_mut(), 742 ); 743 if hwnd.is_null() { 744 syscall_bail!("Failed to call CreateWindowExW()"); 745 } 746 Ok(hwnd) 747 } 748 } 749 } 750 751 impl Drop for Window { drop(&mut self)752 fn drop(&mut self) { 753 // Safe because it is called from the same thread the created the window. 754 if unsafe { IsWindow(self.hwnd) } == 0 { 755 error!("The underlying HWND is invalid when Window is being dropped!") 756 } 757 } 758 } 759 760 /// If the resolution/orientation of the monitor changes, or if the monitor is unplugged, this must 761 /// be recreated with a valid HMONITOR. 762 pub struct MonitorInfo { 763 pub hmonitor: HMONITOR, 764 pub display_rect: Rect, 765 pub work_rect: Rect, 766 pub dpi: i32, 767 } 768 769 impl MonitorInfo { 770 /// # Safety 771 /// Caller is responsible for ensuring that `hmonitor` is a valid handle. new(hmonitor: HMONITOR) -> Result<Self>772 pub unsafe fn new(hmonitor: HMONITOR) -> Result<Self> { 773 let monitor_info: MONITORINFO = 774 Self::get_monitor_info(hmonitor).context("When creating MonitorInfo")?; 775 Ok(Self { 776 hmonitor, 777 display_rect: monitor_info.rcMonitor.to_rect(), 778 work_rect: monitor_info.rcWork.to_rect(), 779 dpi: Self::get_dpi(hmonitor), 780 }) 781 } 782 783 /// Calls `GetMonitorInfoW()` internally. 784 /// # Safety 785 /// Caller is responsible for ensuring that `hmonitor` is a valid handle. get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO>786 unsafe fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO> { 787 let mut monitor_info = MONITORINFO { 788 cbSize: mem::size_of::<MONITORINFO>().try_into().unwrap(), 789 ..Default::default() 790 }; 791 if GetMonitorInfoW(hmonitor, &mut monitor_info) == 0 { 792 syscall_bail!("Failed to call GetMonitorInfoW()"); 793 } 794 Ok(monitor_info) 795 } 796 797 /// Calls `GetDpiForMonitor()` internally. get_dpi(hmonitor: HMONITOR) -> i32798 fn get_dpi(hmonitor: HMONITOR) -> i32 { 799 let mut dpi_x = 0; 800 let mut dpi_y = 0; 801 // This is always safe since `GetDpiForMonitor` won't crash if HMONITOR is invalid, but 802 // return E_INVALIDARG. 803 unsafe { 804 if GetDpiForMonitor(hmonitor, MDT_RAW_DPI, &mut dpi_x, &mut dpi_y) == S_OK 805 || GetDpiForMonitor(hmonitor, MDT_DEFAULT, &mut dpi_x, &mut dpi_y) == S_OK 806 { 807 // We assume screen pixels are square and DPI in different directions are the same. 808 dpi_x as i32 809 } else { 810 error!("Failed to retrieve DPI with HMONITOR {:p}", hmonitor); 811 DEFAULT_HOST_DPI 812 } 813 } 814 } 815 } 816 817 impl fmt::Debug for MonitorInfo { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result818 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 819 write!( 820 f, 821 "{{hmonitor: {:p}, display_rect: {:?}, work_rect: {:?}, DPI: {}}}", 822 self.hmonitor, self.display_rect, self.work_rect, self.dpi, 823 ) 824 } 825 } 826