@@ -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;
-
- 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;
+ 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;
+
+ // 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<WindowsWindowStatePtr>) -> Option<isize> {
- 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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Option<R>
where
F: FnOnce(&mut PlatformInputHandler) -> R,
@@ -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<AutoHideTaskbarPosition>,
}
#[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<Option<Self>> {
+ 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::<APPBARDATA>() 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<DevicePixels> = 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::<APPBARDATA>() as u32,
+ ..Default::default()
+ };
+ let ret = unsafe { SHAppBarMessage(ABM_GETSTATE, &mut info) } as u32;
+ ret == ABS_AUTOHIDE
+}