diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 5d0fd20696..0d55142ae9 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -19,6 +19,7 @@ pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1; pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; +const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1; pub(crate) fn handle_msg( handle: HWND, @@ -680,29 +681,43 @@ fn handle_calc_client_size( return None; } - let dpi = unsafe { GetDpiForWindow(handle) }; - - let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) }; - let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) }; - let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; - + let is_maximized = state_ptr.state.borrow().is_maximized(); + let insets = get_client_area_insets(handle, is_maximized, state_ptr.windows_version); // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS; let mut requested_client_rect = unsafe { &mut ((*params).rgrc) }; - requested_client_rect[0].right -= frame_x + padding; - requested_client_rect[0].left += frame_x + padding; - requested_client_rect[0].bottom -= frame_y + padding; + requested_client_rect[0].left += insets.left; + requested_client_rect[0].top += insets.top; + requested_client_rect[0].right -= insets.right; + requested_client_rect[0].bottom -= insets.bottom; - if state_ptr.state.borrow().is_maximized() { - requested_client_rect[0].top += frame_y + padding; - } else { - match state_ptr.windows_version { - WindowsVersion::Win10 => {} - WindowsVersion::Win11 => { - // Magic number that calculates the width of the border - let border = (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32; - requested_client_rect[0].top += border; + // Fix auto hide taskbar not showing. This solution is based on the approach + // used by Chrome. However, it may result in one row of pixels being obscured + // in our client area. But as Chrome says, "there seems to be no better solution." + if is_maximized { + if let Some(ref taskbar_position) = state_ptr + .state + .borrow() + .system_settings + .auto_hide_taskbar_position + { + // Fot the auto-hide taskbar, adjust in by 1 pixel on taskbar edge, + // so the window isn't treated as a "fullscreen app", which would cause + // the taskbar to disappear. + match taskbar_position { + AutoHideTaskbarPosition::Left => { + requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Top => { + requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Right => { + requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX + } + AutoHideTaskbarPosition::Bottom => { + requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX + } } } } @@ -742,28 +757,12 @@ fn handle_activate_msg( } fn handle_create_msg(handle: HWND, state_ptr: Rc) -> Option { - let mut size_rect = RECT::default(); - unsafe { GetWindowRect(handle, &mut size_rect).log_err() }; - - let width = size_rect.right - size_rect.left; - let height = size_rect.bottom - size_rect.top; - if state_ptr.hide_title_bar { - unsafe { - SetWindowPos( - handle, - None, - size_rect.left, - size_rect.top, - width, - height, - SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE, - ) - .log_err() - }; + notify_frame_changed(handle); + Some(0) + } else { + None } - - Some(0) } fn handle_dpi_changed_msg( @@ -1099,12 +1098,17 @@ fn handle_system_settings_changed( state_ptr: Rc, ) -> Option { let mut lock = state_ptr.state.borrow_mut(); - // mouse wheel - lock.system_settings.mouse_wheel_settings.update(); + let display = lock.display; + // system settings + lock.system_settings.update(display); // mouse double click lock.click_state.system_update(); // window border offset lock.border_offset.update(handle).log_err(); + drop(lock); + // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide + // taskbar correctly. + notify_frame_changed(handle); Some(0) } @@ -1343,6 +1347,77 @@ pub(crate) fn current_modifiers() -> Modifiers { } } +fn get_client_area_insets( + handle: HWND, + is_maximized: bool, + windows_version: WindowsVersion, +) -> RECT { + // For maximized windows, Windows outdents the window rect from the screen's client rect + // by `frame_thickness` on each edge, meaning `insets` must contain `frame_thickness` + // on all sides (including the top) to avoid the client area extending onto adjacent + // monitors. + // + // For non-maximized windows, things become complicated: + // + // - On Windows 10 + // The top inset must be zero, since if there is any nonclient area, Windows will draw + // a full native titlebar outside the client area. (This doesn't occur in the maximized + // case.) + // + // - On Windows 11 + // The top inset is calculated using an empirical formula that I derived through various + // tests. Without this, the top 1-2 rows of pixels in our window would be obscured. + let dpi = unsafe { GetDpiForWindow(handle) }; + let frame_thickness = get_frame_thickness(dpi); + let top_insets = if is_maximized { + frame_thickness + } else { + match windows_version { + WindowsVersion::Win10 => 0, + WindowsVersion::Win11 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32, + } + }; + RECT { + left: frame_thickness, + top: top_insets, + right: frame_thickness, + bottom: frame_thickness, + } +} + +// there is some additional non-visible space when talking about window +// borders on Windows: +// - SM_CXSIZEFRAME: The resize handle. +// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle. +fn get_frame_thickness(dpi: u32) -> i32 { + let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) }; + let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + resize_frame_thickness + padding_thickness +} + +fn notify_frame_changed(handle: HWND) { + unsafe { + SetWindowPos( + handle, + None, + 0, + 0, + 0, + 0, + SWP_FRAMECHANGED + | SWP_NOACTIVATE + | SWP_NOCOPYBITS + | SWP_NOMOVE + | SWP_NOOWNERZORDER + | SWP_NOREPOSITION + | SWP_NOSENDCHANGING + | SWP_NOSIZE + | SWP_NOZORDER, + ) + .log_err(); + } +} + fn with_input_handler(state_ptr: &Rc, f: F) -> Option where F: FnOnce(&mut PlatformInputHandler) -> R, diff --git a/crates/gpui/src/platform/windows/system_settings.rs b/crates/gpui/src/platform/windows/system_settings.rs index 604191dc4b..8bbc83fe43 100644 --- a/crates/gpui/src/platform/windows/system_settings.rs +++ b/crates/gpui/src/platform/windows/system_settings.rs @@ -1,16 +1,24 @@ use std::ffi::{c_uint, c_void}; -use util::ResultExt; -use windows::Win32::UI::WindowsAndMessaging::{ - SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, - SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, +use ::util::ResultExt; +use windows::Win32::UI::{ + Shell::{SHAppBarMessage, ABM_GETSTATE, ABM_GETTASKBARPOS, ABS_AUTOHIDE, APPBARDATA}, + WindowsAndMessaging::{ + SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, + }, }; +use crate::*; + +use super::WindowsDisplay; + /// Windows settings pulled from SystemParametersInfo /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow #[derive(Default, Debug, Clone, Copy)] pub(crate) struct WindowsSystemSettings { pub(crate) mouse_wheel_settings: MouseWheelSettings, + pub(crate) auto_hide_taskbar_position: Option, } #[derive(Default, Debug, Clone, Copy)] @@ -22,19 +30,20 @@ pub(crate) struct MouseWheelSettings { } impl WindowsSystemSettings { - pub(crate) fn new() -> Self { + pub(crate) fn new(display: WindowsDisplay) -> Self { let mut settings = Self::default(); - settings.init(); + settings.update(display); settings } - fn init(&mut self) { + pub(crate) fn update(&mut self, display: WindowsDisplay) { self.mouse_wheel_settings.update(); + self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten(); } } impl MouseWheelSettings { - pub(crate) fn update(&mut self) { + fn update(&mut self) { self.update_wheel_scroll_chars(); self.update_wheel_scroll_lines(); } @@ -71,3 +80,100 @@ impl MouseWheelSettings { } } } + +#[derive(Debug, Clone, Copy, Default)] +pub(crate) enum AutoHideTaskbarPosition { + Left, + Right, + Top, + #[default] + Bottom, +} + +impl AutoHideTaskbarPosition { + fn new(display: WindowsDisplay) -> anyhow::Result> { + if !check_auto_hide_taskbar_enable() { + // If auto hide taskbar is not enable, we do nothing in this case. + return Ok(None); + } + let mut info = APPBARDATA { + cbSize: std::mem::size_of::() as u32, + ..Default::default() + }; + let ret = unsafe { SHAppBarMessage(ABM_GETTASKBARPOS, &mut info) }; + if ret == 0 { + anyhow::bail!( + "Unable to retrieve taskbar position: {}", + std::io::Error::last_os_error() + ); + } + let taskbar_bounds: Bounds = Bounds::new( + point(info.rc.left.into(), info.rc.top.into()), + size( + (info.rc.right - info.rc.left).into(), + (info.rc.bottom - info.rc.top).into(), + ), + ); + let display_bounds = display.physical_bounds(); + if display_bounds.intersect(&taskbar_bounds) != taskbar_bounds { + // This case indicates that taskbar is not on the current monitor. + return Ok(None); + } + if taskbar_bounds.bottom() == display_bounds.bottom() + && taskbar_bounds.right() == display_bounds.right() + { + if taskbar_bounds.size.height < display_bounds.size.height + && taskbar_bounds.size.width == display_bounds.size.width + { + return Ok(Some(Self::Bottom)); + } + if taskbar_bounds.size.width < display_bounds.size.width + && taskbar_bounds.size.height == display_bounds.size.height + { + return Ok(Some(Self::Right)); + } + log::error!( + "Unrecognized taskbar bounds {:?} give display bounds {:?}", + taskbar_bounds, + display_bounds + ); + return Ok(None); + } + if taskbar_bounds.top() == display_bounds.top() + && taskbar_bounds.left() == display_bounds.left() + { + if taskbar_bounds.size.height < display_bounds.size.height + && taskbar_bounds.size.width == display_bounds.size.width + { + return Ok(Some(Self::Top)); + } + if taskbar_bounds.size.width < display_bounds.size.width + && taskbar_bounds.size.height == display_bounds.size.height + { + return Ok(Some(Self::Left)); + } + log::error!( + "Unrecognized taskbar bounds {:?} give display bounds {:?}", + taskbar_bounds, + display_bounds + ); + return Ok(None); + } + log::error!( + "Unrecognized taskbar bounds {:?} give display bounds {:?}", + taskbar_bounds, + display_bounds + ); + Ok(None) + } +} + +/// Check if auto hide taskbar is enable or not. +fn check_auto_hide_taskbar_enable() -> bool { + let mut info = APPBARDATA { + cbSize: std::mem::size_of::() as u32, + ..Default::default() + }; + let ret = unsafe { SHAppBarMessage(ABM_GETSTATE, &mut info) } as u32; + ret == ABS_AUTOHIDE +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 1b2f72cf42..f03e6a3857 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -92,7 +92,7 @@ impl WindowsWindowState { let input_handler = None; let system_key_handled = false; let click_state = ClickState::new(); - let system_settings = WindowsSystemSettings::new(); + let system_settings = WindowsSystemSettings::new(display); let nc_button_pressed = None; let fullscreen = None;