Path: blob/main/gpu_display/src/gpu_display_win/window.rs
5394 views
// Copyright 2022 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34use std::convert::From;5use std::fmt;6use std::mem;7use std::os::raw::c_void;8use std::ptr::null_mut;910use anyhow::bail;11use anyhow::Context;12use anyhow::Result;13use base::error;14use base::info;15use base::warn;16use euclid::point2;17use euclid::size2;18use euclid::Point2D;19use euclid::Size2D;20use win_util::syscall_bail;21use win_util::win32_wide_string;22use winapi::shared::minwindef::DWORD;23use winapi::shared::minwindef::FALSE;24use winapi::shared::minwindef::HINSTANCE;25use winapi::shared::minwindef::HMODULE;26use winapi::shared::minwindef::LPARAM;27use winapi::shared::minwindef::LRESULT;28use winapi::shared::minwindef::TRUE;29use winapi::shared::minwindef::UINT;30use winapi::shared::minwindef::WORD;31use winapi::shared::minwindef::WPARAM;32use winapi::shared::windef::HBRUSH;33use winapi::shared::windef::HCURSOR;34use winapi::shared::windef::HICON;35use winapi::shared::windef::HMONITOR;36use winapi::shared::windef::HWND;37use winapi::shared::windef::RECT;38use winapi::shared::winerror::S_OK;39use winapi::um::dwmapi::DwmEnableBlurBehindWindow;40use winapi::um::dwmapi::DWM_BB_ENABLE;41use winapi::um::dwmapi::DWM_BLURBEHIND;42use winapi::um::errhandlingapi::GetLastError;43use winapi::um::errhandlingapi::SetLastError;44use winapi::um::libloaderapi::GetModuleHandleW;45use winapi::um::shellscalingapi::GetDpiForMonitor;46use winapi::um::shellscalingapi::MDT_DEFAULT;47use winapi::um::shellscalingapi::MDT_RAW_DPI;48use winapi::um::wingdi::GetStockObject;49use winapi::um::wingdi::BLACK_BRUSH;50use winapi::um::winnt::LPCWSTR;51use winapi::um::winuser::AdjustWindowRectExForDpi;52use winapi::um::winuser::ClientToScreen;53use winapi::um::winuser::CreateWindowExW;54use winapi::um::winuser::DefWindowProcW;55use winapi::um::winuser::DestroyWindow;56use winapi::um::winuser::GetActiveWindow;57use winapi::um::winuser::GetClientRect;58use winapi::um::winuser::GetDpiForSystem;59use winapi::um::winuser::GetForegroundWindow;60use winapi::um::winuser::GetMonitorInfoW;61use winapi::um::winuser::GetSystemMetrics;62use winapi::um::winuser::GetWindowLongPtrW;63use winapi::um::winuser::GetWindowPlacement;64use winapi::um::winuser::GetWindowRect;65use winapi::um::winuser::IsIconic;66use winapi::um::winuser::IsWindow;67use winapi::um::winuser::IsWindowVisible;68use winapi::um::winuser::IsZoomed;69use winapi::um::winuser::LoadCursorW;70use winapi::um::winuser::LoadIconW;71use winapi::um::winuser::MonitorFromWindow;72use winapi::um::winuser::PostMessageW;73use winapi::um::winuser::RegisterRawInputDevices;74use winapi::um::winuser::RegisterTouchWindow;75use winapi::um::winuser::RemovePropW;76use winapi::um::winuser::ScreenToClient;77use winapi::um::winuser::SetForegroundWindow;78use winapi::um::winuser::SetPropW;79use winapi::um::winuser::SetWindowLongPtrW;80use winapi::um::winuser::SetWindowPlacement;81use winapi::um::winuser::SetWindowPos;82use winapi::um::winuser::ShowWindow;83use winapi::um::winuser::GWL_EXSTYLE;84use winapi::um::winuser::HWND_MESSAGE;85use winapi::um::winuser::MAKEINTRESOURCEW;86use winapi::um::winuser::MONITORINFO;87use winapi::um::winuser::MONITOR_DEFAULTTONEAREST;88use winapi::um::winuser::MONITOR_DEFAULTTONULL;89use winapi::um::winuser::MSG;90use winapi::um::winuser::PCRAWINPUTDEVICE;91use winapi::um::winuser::RAWINPUTDEVICE;92use winapi::um::winuser::SM_REMOTESESSION;93use winapi::um::winuser::SWP_FRAMECHANGED;94use winapi::um::winuser::SWP_HIDEWINDOW;95use winapi::um::winuser::SWP_NOACTIVATE;96use winapi::um::winuser::SWP_NOMOVE;97use winapi::um::winuser::SWP_NOSIZE;98use winapi::um::winuser::SWP_NOZORDER;99use winapi::um::winuser::SW_RESTORE;100use winapi::um::winuser::SW_SHOW;101use winapi::um::winuser::WINDOWPLACEMENT;102use winapi::um::winuser::WMSZ_BOTTOM;103use winapi::um::winuser::WMSZ_BOTTOMLEFT;104use winapi::um::winuser::WMSZ_BOTTOMRIGHT;105use winapi::um::winuser::WMSZ_LEFT;106use winapi::um::winuser::WMSZ_RIGHT;107use winapi::um::winuser::WMSZ_TOP;108use winapi::um::winuser::WMSZ_TOPLEFT;109use winapi::um::winuser::WMSZ_TOPRIGHT;110use winapi::um::winuser::WM_ENTERSIZEMOVE;111use winapi::um::winuser::WM_EXITSIZEMOVE;112use winapi::um::winuser::WM_MOVING;113use winapi::um::winuser::WM_SIZING;114115use super::math_util::*;116use super::HostWindowSpace;117118// Windows desktop's default DPI at default scaling settings is 96.119// (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/mpc/pixel-density-and-usability)120pub(crate) const DEFAULT_HOST_DPI: i32 = 96;121122/// Stores a message retrieved from the message pump. We don't include the HWND since it is only123/// used for determining the recipient.124#[derive(Copy, Clone, Debug)]125pub struct MessagePacket {126pub msg: UINT,127pub w_param: WPARAM,128pub l_param: LPARAM,129}130131impl MessagePacket {132pub fn new(msg: UINT, w_param: WPARAM, l_param: LPARAM) -> Self {133Self {134msg,135w_param,136l_param,137}138}139}140141impl From<MSG> for MessagePacket {142fn from(message: MSG) -> Self {143Self::new(message.message, message.wParam, message.lParam)144}145}146147/// The state of window moving or sizing modal loop.148///149/// We do receive `WM_ENTERSIZEMOVE` when the window is about to be resized or moved, but it doesn't150/// tell us whether resizing or moving should be expected. We won't know that until later we receive151/// `WM_SIZING` or `WM_MOVING`. Corner cases are:152/// (1) If the user long presses the title bar, window borders or corners, and then releases without153/// moving the mouse, we would receive both `WM_ENTERSIZEMOVE` and `WM_EXITSIZEMOVE`, but154/// without any `WM_SIZING` or `WM_MOVING` in between.155/// (2) When the window is maximized, if we drag the title bar of it, it will be restored to the156/// normal size and then move along with the cursor. In this case, we would expect157/// `WM_ENTERSIZEMOVE` to be followed by one `WM_SIZING`, and then multiple `WM_MOVING`.158///159/// This enum tracks the modal loop state. Possible state transition:160/// (1) NotInLoop -> WillResizeOrMove -> IsResizing -> NotInLoop. This is for sizing modal loops.161/// (2) NotInLoop -> WillResizeOrMove -> IsMoving -> NotInLoop. This is for moving modal loops.162/// (3) NotInLoop -> WillResizeOrMove -> NotInLoop. This may occur if the user long presses the163/// window title bar, window borders or corners, but doesn't actually resize or move the window.164#[derive(Copy, Clone, Debug, PartialEq, Eq)]165enum SizeMoveLoopState {166/// The window is not in the moving or sizing modal loop.167NotInLoop,168/// We have received `WM_ENTERSIZEMOVE` but haven't received either `WM_SIZING` or `WM_MOVING`,169/// so we don't know if the window is going to be resized or moved at this point.170WillResizeOrMove,171/// We have received `WM_SIZING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is172/// the first `WM_SIZING`.173IsResizing { is_first: bool },174/// We have received `WM_MOVING` after `WM_ENTERSIZEMOVE`. `is_first` indicates whether this is175/// the first `WM_MOVING`.176IsMoving { is_first: bool },177}178179impl SizeMoveLoopState {180pub fn new() -> Self {181Self::NotInLoop182}183184pub fn update(&mut self, msg: UINT, w_param: WPARAM) {185match msg {186WM_ENTERSIZEMOVE => self.on_entering_loop(),187WM_EXITSIZEMOVE => self.on_exiting_loop(),188WM_SIZING => self.on_resizing_window(w_param),189WM_MOVING => self.on_moving_window(),190_ => (),191};192}193194pub fn is_in_loop(&self) -> bool {195*self != Self::NotInLoop196}197198pub fn is_resizing_starting(&self) -> bool {199*self == Self::IsResizing { is_first: true }200}201202fn on_entering_loop(&mut self) {203info!("Entering window sizing/moving modal loop");204*self = Self::WillResizeOrMove;205}206207fn on_exiting_loop(&mut self) {208info!("Exiting window sizing/moving modal loop");209*self = Self::NotInLoop;210}211212fn on_resizing_window(&mut self, w_param: WPARAM) {213match *self {214Self::NotInLoop => (),215Self::WillResizeOrMove => match w_param as u32 {216// In these cases, the user is dragging window borders or corners for resizing.217WMSZ_LEFT | WMSZ_RIGHT | WMSZ_TOP | WMSZ_BOTTOM | WMSZ_TOPLEFT | WMSZ_TOPRIGHT218| WMSZ_BOTTOMLEFT | WMSZ_BOTTOMRIGHT => {219info!("Window is being resized");220*self = Self::IsResizing { is_first: true };221}222// In this case, the user is dragging the title bar of the maximized window. The223// window will be restored to the normal size and then move along with the cursor,224// so we can expect `WM_MOVING` coming and entering the moving modal loop.225_ => info!("Window is being restored"),226},227Self::IsResizing { .. } => *self = Self::IsResizing { is_first: false },228Self::IsMoving { .. } => warn!("WM_SIZING is unexpected in moving modal loops!"),229}230}231232fn on_moving_window(&mut self) {233match *self {234Self::NotInLoop => (),235Self::WillResizeOrMove => {236info!("Window is being moved");237*self = Self::IsMoving { is_first: true };238}239Self::IsMoving { .. } => *self = Self::IsMoving { is_first: false },240Self::IsResizing { .. } => warn!("WM_MOVING is unexpected in sizing modal loops!"),241}242}243}244245/// A trait for basic functionalities that are common to both message-only windows and GUI windows.246/// Implementers must guarantee that when these functions are called, the underlying window object247/// is still alive.248pub(crate) trait BasicWindow {249/// # Safety250/// The returned handle should be used carefully, since it may have become invalid if it251/// outlives the window object.252unsafe fn handle(&self) -> HWND;253254fn is_same_window(&self, hwnd: HWND) -> bool {255// SAFETY:256// Safe because we are just comparing handle values.257hwnd == unsafe { self.handle() }258}259260/// Calls `DefWindowProcW()` internally.261fn default_process_message(&self, packet: &MessagePacket) -> LRESULT {262// SAFETY:263// Safe because the window object won't outlive the HWND.264unsafe { DefWindowProcW(self.handle(), packet.msg, packet.w_param, packet.l_param) }265}266267/// Calls `SetPropW()` internally.268/// # Safety269/// The caller is responsible for keeping the data pointer valid until `remove_property()` is270/// called.271unsafe fn set_property(&self, property: &str, data: *mut c_void) -> Result<()> {272// Partially safe because the window object won't outlive the HWND, and failures are handled273// below. The caller is responsible for the rest of safety.274if SetPropW(self.handle(), win32_wide_string(property).as_ptr(), data) == 0 {275syscall_bail!("Failed to call SetPropW()");276}277Ok(())278}279280/// Calls `RemovePropW()` internally.281#[allow(dead_code)]282fn remove_property(&self, property: &str) -> Result<()> {283// SAFETY:284// Safe because the window object won't outlive the HWND, and failures are handled below.285unsafe {286SetLastError(0);287RemovePropW(self.handle(), win32_wide_string(property).as_ptr());288if GetLastError() != 0 {289syscall_bail!("Failed to call RemovePropW()");290}291}292Ok(())293}294295/// Calls `DestroyWindow()` internally.296fn destroy(&self) -> Result<()> {297// SAFETY:298// Safe because the window object won't outlive the HWND.299if unsafe { DestroyWindow(self.handle()) } == 0 {300syscall_bail!("Failed to call DestroyWindow()");301}302Ok(())303}304}305306/// 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.310pub struct GuiWindow {311hwnd: HWND,312scanout_id: u32,313size_move_loop_state: SizeMoveLoopState,314}315316impl GuiWindow {317/// # Safety318/// The owner of `GuiWindow` object is responsible for dropping it before we finish processing319/// `WM_NCDESTROY`, because the window handle will become invalid afterwards.320pub unsafe fn new(321scanout_id: u32,322class_name: &str,323title: &str,324dw_style: DWORD,325initial_window_size: &Size2D<i32, HostWindowSpace>,326) -> Result<Self> {327info!("Creating GUI window for scanout {}", scanout_id);328329let hwnd = create_sys_window(330get_current_module_handle(),331class_name,332title,333dw_style,334/* hwnd_parent */ null_mut(),335initial_window_size,336)337.context("When creating GuiWindow")?;338let window = Self {339hwnd,340scanout_id,341size_move_loop_state: SizeMoveLoopState::new(),342};343window.register_touch();344Ok(window)345}346347pub fn scanout_id(&self) -> u32 {348self.scanout_id349}350351pub fn update_states(&mut self, msg: UINT, w_param: WPARAM) {352self.size_move_loop_state.update(msg, w_param);353}354355pub fn is_sizing_or_moving(&self) -> bool {356self.size_move_loop_state.is_in_loop()357}358359pub fn is_resizing_loop_starting(&self) -> bool {360self.size_move_loop_state.is_resizing_starting()361}362363/// Calls `IsWindow()` internally. Returns true if the HWND identifies an existing window.364pub fn is_valid(&self) -> bool {365// SAFETY:366// Safe because it is called from the same thread the created the window.367unsafe { IsWindow(self.hwnd) != 0 }368}369370/// Calls `GetWindowLongPtrW()` internally.371pub 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.374unsafe {375// GetWindowLongPtrW() may return zero if we haven't set that attribute before, so we376// need to check if the error code is non-zero.377SetLastError(0);378let value = GetWindowLongPtrW(self.hwnd, index);379if value == 0 && GetLastError() != 0 {380syscall_bail!("Failed to call GetWindowLongPtrW()");381}382Ok(value)383}384}385386/// Calls `SetWindowLongPtrW()` internally.387pub 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.390unsafe {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.393SetLastError(0);394let prev_value = SetWindowLongPtrW(self.hwnd, index, value);395if prev_value == 0 && GetLastError() != 0 {396syscall_bail!("Failed to call SetWindowLongPtrW()");397}398Ok(())399}400}401402/// Calls `GetWindowRect()` internally.403pub fn get_window_rect(&self) -> Result<Rect> {404let mut rect: RECT = Default::default();405// SAFETY:406// Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and407// failures are handled below.408unsafe {409if GetWindowRect(self.hwnd, &mut rect) == 0 {410syscall_bail!("Failed to call GetWindowRect()");411}412}413Ok(rect.to_rect())414}415416/// Calls `GetWindowRect()` internally.417pub fn get_window_origin(&self) -> Result<Point> {418Ok(self.get_window_rect()?.origin)419}420421/// Calls `GetClientRect()` internally.422pub fn get_client_rect(&self) -> Result<Rect> {423let mut rect: RECT = Default::default();424// SAFETY:425// Safe because `GuiWindow` object won't outlive the HWND, we know `rect` is valid, and426// failures are handled below.427unsafe {428if GetClientRect(self.hwnd, &mut rect) == 0 {429syscall_bail!("Failed to call GetClientRect()");430}431}432Ok(rect.to_rect())433}434435/// The system may add adornments around the client area of the window, such as the title bar436/// and borders. This function returns the size of all those paddings. It can be assumed that:437/// window_size = client_size + window_padding_size438pub fn get_window_padding_size(&self, dw_style: u32) -> Result<Size> {439static CONTEXT_MESSAGE: &str = "When calculating window padding";440// The padding is always the same in windowed mode, hence we can use an arbitrary rect.441let client_rect = Rect::new(point2(0, 0), size2(500, 500));442let dw_ex_style = self.get_attribute(GWL_EXSTYLE).context(CONTEXT_MESSAGE)?;443let window_rect: Rect = self444.get_adjusted_window_rect(&client_rect, dw_style, dw_ex_style as u32)445.context(CONTEXT_MESSAGE)?;446Ok(window_rect.size - client_rect.size)447}448449/// Calls `ClientToScreen()` internally. Converts the window client area coordinates of a450/// specified point to screen coordinates.451pub fn client_to_screen(&self, point: &Point) -> Result<Point> {452let mut point = point.to_sys_point();453// SAFETY:454// Safe because `GuiWindow` object won't outlive the HWND, we know `point` is valid, and455// failures are handled below.456unsafe {457if ClientToScreen(self.hwnd, &mut point) == 0 {458syscall_bail!("Failed to call ClientToScreen()");459}460}461Ok(point.to_point())462}463464/// Calls `ScreenToClient()` internally. Converts the screen coordinates to window client area465/// coordinates.466pub fn screen_to_client(&self, point: Point) -> Result<Point> {467let mut point = point.to_sys_point();468469// 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.474unsafe {475let res = ScreenToClient(self.hwnd, point.as_mut_ptr());476if res == 0 {477syscall_bail!("failed to convert cursor position to client coordinates");478}479}480Ok(Point2D::new(point.x, point.y))481}482483/// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,484/// returns the handle to the closest one.485pub fn get_nearest_monitor_handle(&self) -> HMONITOR {486// SAFETY:487// Safe because `GuiWindow` object won't outlive the HWND.488unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONEAREST) }489}490491/// Calls `MonitorFromWindow()` internally. If the window is not on any active display monitor,492/// returns the info of the closest one.493pub fn get_monitor_info(&self) -> Result<MonitorInfo> {494// SAFETY:495// Safe because `get_nearest_monitor_handle()` always returns a valid monitor handle.496unsafe { MonitorInfo::new(self.get_nearest_monitor_handle()) }497}498499/// Calls `MonitorFromWindow()` internally.500pub fn is_on_active_display(&self) -> bool {501// SAFETY:502// Safe because `GuiWindow` object won't outlive the HWND.503unsafe { !MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL).is_null() }504}505506/// Calls `SetWindowPos()` internally.507pub 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.510unsafe {511if SetWindowPos(512self.hwnd,513null_mut(),514window_rect.origin.x,515window_rect.origin.y,516window_rect.size.width,517window_rect.size.height,518flags,519) == 0520{521syscall_bail!("Failed to call SetWindowPos()");522}523Ok(())524}525}526527/// 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.529pub fn flush_window_style_change(&self) -> Result<()> {530// Because of `SWP_NOMOVE` and `SWP_NOSIZE` flags, we can pass in arbitrary window size and531// position as they will be ignored.532self.set_pos(533&Rect::zero(),534SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED,535)536}537538/// Calls `ShowWindow()` internally. Note that it is more preferable to call `set_pos()` with539/// `SWP_SHOWWINDOW` since that would set the error code on failure.540pub fn show(&self) {541// SAFETY:542// Safe because `GuiWindow` object won't outlive the HWND.543unsafe {544ShowWindow(self.hwnd, SW_SHOW);545}546}547548/// Calls `SetWindowPos()` internally. Returns false if the window is already hidden and thus549/// this operation is skipped.550pub fn hide_if_visible(&self) -> Result<bool> {551Ok(if self.is_visible()? {552self.set_pos(553&Rect::zero(),554SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER,555)?;556true557} else {558false559})560}561562/// Calls `ShowWindow()` internally to restore a minimized window.563pub fn restore(&self) {564// SAFETY:565// Safe because `GuiWindow` object won't outlive the HWND.566unsafe {567ShowWindow(self.hwnd, SW_RESTORE);568}569}570571/// Calls `IsZoomed()` internally. Note that the window may carry the WS_MAXIMIZE flag until it572/// is restored. For example, if we have switched from maximized to fullscreen, this function573/// would still return true.574pub fn was_maximized(&self) -> bool {575// SAFETY:576// Safe because `GuiWindow` object won't outlive the HWND.577unsafe { IsZoomed(self.hwnd) != 0 }578}579580/// Calls `IsWindowVisible()` internally. We also require that the window size is nonzero to be581/// considered visible.582pub fn is_visible(&self) -> Result<bool> {583// SAFETY:584// Safe because `GuiWindow` object won't outlive the HWND.585if unsafe { IsWindowVisible(self.hwnd) } != 0 {586let window_rect = self587.get_window_rect()588.context("When querying window visibility")?;589if window_rect.size != Size::zero() {590return Ok(true);591} else {592info!("Window has WS_VISIBLE flag but its size is zero");593}594}595Ok(false)596}597598/// Calls `GetForegroundWindow()` internally. A foreground window is the window with which the599/// user is currently working. It might belong to a different thread/process than the calling600/// thread.601pub fn is_global_foreground_window(&self) -> bool {602// SAFETY:603// Safe because there is no argument.604unsafe { GetForegroundWindow() == self.hwnd }605}606607/// Calls `GetActiveWindow()` internally. An active window is the window with which the user is608/// currently working and is attached to the calling thread's message queue. It is possible that609/// there is no active window if the foreground focus is on another thread/process.610pub fn is_thread_foreground_window(&self) -> bool {611// SAFETY:612// Safe because there is no argument.613unsafe { GetActiveWindow() == self.hwnd }614}615616/// Calls `IsIconic()` internally.617pub fn is_minimized(&self) -> bool {618// SAFETY:619// Safe because `GuiWindow` object won't outlive the HWND.620unsafe { IsIconic(self.hwnd) != 0 }621}622623/// Calls `SetForegroundWindow()` internally. `SetForegroundWindow()` may fail, for example,624/// when the taskbar is in the foreground, hence this is a best-effort call.625pub fn bring_to_foreground(&self) {626// SAFETY:627// Safe because `GuiWindow` object won't outlive the HWND.628if unsafe { SetForegroundWindow(self.hwnd) } == 0 {629info!("Cannot bring the window to foreground.");630}631}632633/// 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 with635/// Windows 8, it does not blur it, but only makes the window semi-transparent.636pub fn set_backgound_transparency(&self, semi_transparent: bool) -> Result<()> {637let blur_behind = DWM_BLURBEHIND {638dwFlags: DWM_BB_ENABLE,639fEnable: if semi_transparent { TRUE } else { FALSE },640hRgnBlur: null_mut(),641fTransitionOnMaximized: 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.646let errno = unsafe { DwmEnableBlurBehindWindow(self.hwnd, &blur_behind) };647match errno {6480 => Ok(()),649_ => bail!(650"Failed to call DwmEnableBlurBehindWindow() when setting \651window background transparency to {} (Error code {})",652semi_transparent,653errno654),655}656}657658/// Calls `AdjustWindowRectExForDpi()` internally.659pub fn get_adjusted_window_rect(660&self,661client_rect: &Rect,662dw_style: u32,663dw_ex_style: u32,664) -> Result<Rect> {665let 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.669unsafe {670if AdjustWindowRectExForDpi(671&mut window_rect,672dw_style,673FALSE,674dw_ex_style,675GetDpiForSystem(),676) == 0677{678syscall_bail!("Failed to call AdjustWindowRectExForDpi()");679}680}681Ok(window_rect.to_rect())682}683684/// Calls `GetWindowPlacement()` and `SetWindowPlacement()` internally.685pub fn set_restored_pos(&self, window_rect: &Rect) -> Result<()> {686let mut window_placement = WINDOWPLACEMENT {687length: 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` is692// valid, and failures are handled below.693unsafe {694if GetWindowPlacement(self.hwnd, &mut window_placement) == 0 {695syscall_bail!("Failed to call GetWindowPlacement()");696}697window_placement.rcNormalPosition = window_rect.to_sys_rect();698if SetWindowPlacement(self.hwnd, &window_placement) == 0 {699syscall_bail!("Failed to call SetWindowPlacement()");700}701}702Ok(())703}704705/// Calls `PostMessageW()` internally.706pub 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.709unsafe {710if PostMessageW(self.hwnd, msg, w_param, l_param) == 0 {711syscall_bail!("Failed to call PostMessageW()");712}713}714Ok(())715}716717/// Calls `LoadIconW()` internally.718pub(crate) fn load_custom_icon(hinstance: HINSTANCE, resource_id: WORD) -> Result<HICON> {719// SAFETY:720// Safe because we handle failures below.721unsafe {722let hicon = LoadIconW(hinstance, MAKEINTRESOURCEW(resource_id));723if hicon.is_null() {724syscall_bail!("Failed to call LoadIconW()");725}726Ok(hicon)727}728}729730/// Calls `LoadCursorW()` internally.731pub(crate) fn load_system_cursor(cursor_id: LPCWSTR) -> Result<HCURSOR> {732// SAFETY:733// Safe because we handle failures below.734unsafe {735let hcursor = LoadCursorW(null_mut(), cursor_id);736if hcursor.is_null() {737syscall_bail!("Failed to call LoadCursorW()");738}739Ok(hcursor)740}741}742743/// Calls `GetStockObject()` internally.744pub(crate) fn create_opaque_black_brush() -> Result<HBRUSH> {745// SAFETY:746// Safe because we handle failures below.747unsafe {748let hobject = GetStockObject(BLACK_BRUSH as i32);749if hobject.is_null() {750syscall_bail!("Failed to call GetStockObject()");751}752Ok(hobject as HBRUSH)753}754}755756/// Calls `RegisterTouchWindow()` internally.757fn register_touch(&self) {758// SAFETY: Safe because `GuiWindow` object won't outlive the HWND.759if 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-safe762warn!("failed to register touch: {}", unsafe { GetLastError() });763}764}765}766767impl BasicWindow for GuiWindow {768/// # Safety769/// The returned handle should be used carefully, since it may have become invalid if it770/// outlives the `GuiWindow` object.771unsafe fn handle(&self) -> HWND {772self.hwnd773}774}775776/// A message-only window is always invisible, and is only responsible for sending and receiving777/// messages. The owner of `MessageOnlyWindow` object is responsible for dropping it before the778/// underlying window is completely gone.779pub(crate) struct MessageOnlyWindow {780hwnd: HWND,781}782783impl MessageOnlyWindow {784/// # Safety785/// The owner of `MessageOnlyWindow` object is responsible for dropping it before we finish786/// processing `WM_NCDESTROY`, because the window handle will become invalid afterwards.787pub unsafe fn new(class_name: &str, title: &str) -> Result<Self> {788info!("Creating message-only window");789static CONTEXT_MESSAGE: &str = "When creating MessageOnlyWindow";790791let window = Self {792hwnd: create_sys_window(793get_current_module_handle(),794class_name,795title,796/* dw_style */ 0,797HWND_MESSAGE,798/* initial_window_size */ &size2(0, 0),799)800.context(CONTEXT_MESSAGE)?,801};802window.register_raw_input_mouse().context(CONTEXT_MESSAGE)?;803Ok(window)804}805806/// 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 we809/// make `MessageOnlyWindow` take on this role and reroute events to the foreground `GuiWindow`.810fn register_raw_input_mouse(&self) -> Result<()> {811let mouse_device = RAWINPUTDEVICE {812usUsagePage: 1, // Generic813usUsage: 2, // Mouse814dwFlags: 0,815// SAFETY: Safe because `self` won't outlive the HWND.816hwndTarget: unsafe { self.handle() },817};818// SAFETY: Safe because `mouse_device` lives longer than this function call.819if unsafe {820RegisterRawInputDevices(821&mouse_device as PCRAWINPUTDEVICE,8221,823mem::size_of::<RAWINPUTDEVICE>() as u32,824)825} == 0826{827syscall_bail!("Relative mouse is broken. Failed to call RegisterRawInputDevices()");828}829Ok(())830}831}832833impl BasicWindow for MessageOnlyWindow {834/// # Safety835/// The returned handle should be used carefully, since it may have become invalid if it836/// outlives the `MessageOnlyWindow` object.837unsafe fn handle(&self) -> HWND {838self.hwnd839}840}841842/// Calls `CreateWindowExW()` internally.843fn create_sys_window(844hinstance: HINSTANCE,845class_name: &str,846title: &str,847dw_style: DWORD,848hwnd_parent: HWND,849initial_window_size: &Size2D<i32, HostWindowSpace>,850) -> Result<HWND> {851// SAFETY:852// Safe because we handle failures below.853let hwnd = unsafe {854CreateWindowExW(855/* dwExStyle */ 0,856win32_wide_string(class_name).as_ptr(),857win32_wide_string(title).as_ptr(),858dw_style,859/* x */ 0,860/* y */ 0,861initial_window_size.width,862initial_window_size.height,863hwnd_parent,864/* hMenu */ null_mut(),865hinstance,866/* lpParam */ null_mut(),867)868};869if hwnd.is_null() {870syscall_bail!("Failed to call CreateWindowExW()");871}872info!("Created window {:p}", hwnd);873Ok(hwnd)874}875876/// Calls `GetModuleHandleW()` internally.877pub(crate) fn get_current_module_handle() -> HMODULE {878// SAFETY:879// Safe because we handle failures below.880let hmodule = unsafe { GetModuleHandleW(null_mut()) };881if hmodule.is_null() {882// If it fails, we are in a very broken state and it doesn't make sense to keep running.883panic!(884"Failed to call GetModuleHandleW() for the current module (Error code {})",885// SAFETY: trivially safe886unsafe { GetLastError() }887);888}889hmodule890}891892/// If the resolution/orientation of the monitor changes, or if the monitor is unplugged, this must893/// be recreated with a valid HMONITOR.894pub struct MonitorInfo {895pub hmonitor: HMONITOR,896pub display_rect: Rect,897pub work_rect: Rect,898raw_dpi: i32,899// Whether we are running in a Remote Desktop Protocol (RDP) session. The monitor DPI returned900// by `GetDpiForMonitor()` may not make sense in that case. For example, the DPI is always 25901// under Chrome Remote Desktop, which is way lower than the standard DPI 96. This might be a902// flaw in RDP itself. We have to override the DPI in that case, otherwise the guest DPI903// 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-rdp905is_rdp_session: bool,906}907908impl MonitorInfo {909/// # Safety910/// Caller is responsible for ensuring that `hmonitor` is a valid handle.911pub unsafe fn new(hmonitor: HMONITOR) -> Result<Self> {912let monitor_info: MONITORINFO =913Self::get_monitor_info(hmonitor).context("When creating MonitorInfo")?;914// Docs state that apart from `GetSystemMetrics(SM_REMOTESESSION)`, we also need to check915// 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-environment917// 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-641de79f09d2919// So, we are only calling `GetSystemMetrics(SM_REMOTESESSION)` here until this changes in920// the future.921// SAFETY:922// Safe because no memory management is needed for arguments.923let is_rdp_session = unsafe { GetSystemMetrics(SM_REMOTESESSION) != 0 };924Ok(Self {925hmonitor,926display_rect: monitor_info.rcMonitor.to_rect(),927work_rect: monitor_info.rcWork.to_rect(),928raw_dpi: Self::get_monitor_dpi(hmonitor),929is_rdp_session,930})931}932933pub fn get_dpi(&self) -> i32 {934if self.is_rdp_session {935// Override the DPI since the system may not tell us the correct value in RDP sessions.936DEFAULT_HOST_DPI937} else {938self.raw_dpi939}940}941942/// Calls `GetMonitorInfoW()` internally.943/// # Safety944/// Caller is responsible for ensuring that `hmonitor` is a valid handle.945unsafe fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFO> {946let mut monitor_info = MONITORINFO {947cbSize: mem::size_of::<MONITORINFO>().try_into().unwrap(),948..Default::default()949};950if GetMonitorInfoW(hmonitor, &mut monitor_info) == 0 {951syscall_bail!("Failed to call GetMonitorInfoW()");952}953Ok(monitor_info)954}955956/// Calls `GetDpiForMonitor()` internally.957fn get_monitor_dpi(hmonitor: HMONITOR) -> i32 {958let mut dpi_x = 0;959let mut dpi_y = 0;960// SAFETY:961// This is always safe since `GetDpiForMonitor` won't crash if HMONITOR is invalid, but962// return E_INVALIDARG.963unsafe {964if GetDpiForMonitor(hmonitor, MDT_RAW_DPI, &mut dpi_x, &mut dpi_y) == S_OK965|| GetDpiForMonitor(hmonitor, MDT_DEFAULT, &mut dpi_x, &mut dpi_y) == S_OK966{967// We assume screen pixels are square and DPI in different directions are the same.968dpi_x as i32969} else {970error!("Failed to retrieve DPI with HMONITOR {:p}", hmonitor);971DEFAULT_HOST_DPI972}973}974}975}976977impl fmt::Debug for MonitorInfo {978fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {979write!(980f,981"{{hmonitor: {:p}, display_rect: {:?}, work_rect: {:?}, DPI: {}{}}}",982self.hmonitor,983self.display_rect,984self.work_rect,985self.get_dpi(),986if self.is_rdp_session {987format!(" (raw value: {}, overriden due to RDP)", self.raw_dpi)988} else {989String::new()990}991)992}993}994995996