windows: Fix autohide taskbar dosen't automatically appear when Zed is maximized (#16806)

Closes #12313

This PR introduces the following improvements:
1. Fixed the issue where the auto-hide taskbar wouldn't automatically
appear when Zed is maximized.
2. Refactored the `WM_NCCALCSIZE` code, making it more human-readable.

Release Notes:

- Fixed auto-hide taskbar would refuse to show itself when `Zed` is
maximized on
Winodws([#12313](https://github.com/zed-industries/zed/issues/12313)).

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
张小白 2024-08-29 11:36:28 +08:00 committed by GitHub
parent 77c6243aa8
commit 403fdd6018
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 230 additions and 49 deletions

View File

@ -19,6 +19,7 @@ pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2; pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
pub(crate) fn handle_msg( pub(crate) fn handle_msg(
handle: HWND, handle: HWND,
@ -680,29 +681,43 @@ fn handle_calc_client_size(
return None; return None;
} }
let dpi = unsafe { GetDpiForWindow(handle) }; let is_maximized = state_ptr.state.borrow().is_maximized();
let insets = get_client_area_insets(handle, is_maximized, state_ptr.windows_version);
let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
// wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS; let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
let mut requested_client_rect = unsafe { &mut ((*params).rgrc) }; let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
requested_client_rect[0].right -= frame_x + padding; requested_client_rect[0].left += insets.left;
requested_client_rect[0].left += frame_x + padding; requested_client_rect[0].top += insets.top;
requested_client_rect[0].bottom -= frame_y + padding; requested_client_rect[0].right -= insets.right;
requested_client_rect[0].bottom -= insets.bottom;
if state_ptr.state.borrow().is_maximized() { // Fix auto hide taskbar not showing. This solution is based on the approach
requested_client_rect[0].top += frame_y + padding; // used by Chrome. However, it may result in one row of pixels being obscured
} else { // in our client area. But as Chrome says, "there seems to be no better solution."
match state_ptr.windows_version { if is_maximized {
WindowsVersion::Win10 => {} if let Some(ref taskbar_position) = state_ptr
WindowsVersion::Win11 => { .state
// Magic number that calculates the width of the border .borrow()
let border = (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32; .system_settings
requested_client_rect[0].top += border; .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> { 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 { if state_ptr.hide_title_bar {
unsafe { notify_frame_changed(handle);
SetWindowPos( Some(0)
handle, } else {
None, None
size_rect.left,
size_rect.top,
width,
height,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
)
.log_err()
};
} }
Some(0)
} }
fn handle_dpi_changed_msg( fn handle_dpi_changed_msg(
@ -1099,12 +1098,17 @@ fn handle_system_settings_changed(
state_ptr: Rc<WindowsWindowStatePtr>, state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> { ) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut(); let mut lock = state_ptr.state.borrow_mut();
// mouse wheel let display = lock.display;
lock.system_settings.mouse_wheel_settings.update(); // system settings
lock.system_settings.update(display);
// mouse double click // mouse double click
lock.click_state.system_update(); lock.click_state.system_update();
// window border offset // window border offset
lock.border_offset.update(handle).log_err(); 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) 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> fn with_input_handler<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Option<R>
where where
F: FnOnce(&mut PlatformInputHandler) -> R, F: FnOnce(&mut PlatformInputHandler) -> R,

View File

@ -1,16 +1,24 @@
use std::ffi::{c_uint, c_void}; use std::ffi::{c_uint, c_void};
use util::ResultExt; use ::util::ResultExt;
use windows::Win32::UI::WindowsAndMessaging::{ use windows::Win32::UI::{
SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, Shell::{SHAppBarMessage, ABM_GETSTATE, ABM_GETTASKBARPOS, ABS_AUTOHIDE, APPBARDATA},
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WindowsAndMessaging::{
SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES,
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
},
}; };
use crate::*;
use super::WindowsDisplay;
/// Windows settings pulled from SystemParametersInfo /// Windows settings pulled from SystemParametersInfo
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub(crate) struct WindowsSystemSettings { pub(crate) struct WindowsSystemSettings {
pub(crate) mouse_wheel_settings: MouseWheelSettings, pub(crate) mouse_wheel_settings: MouseWheelSettings,
pub(crate) auto_hide_taskbar_position: Option<AutoHideTaskbarPosition>,
} }
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
@ -22,19 +30,20 @@ pub(crate) struct MouseWheelSettings {
} }
impl WindowsSystemSettings { impl WindowsSystemSettings {
pub(crate) fn new() -> Self { pub(crate) fn new(display: WindowsDisplay) -> Self {
let mut settings = Self::default(); let mut settings = Self::default();
settings.init(); settings.update(display);
settings settings
} }
fn init(&mut self) { pub(crate) fn update(&mut self, display: WindowsDisplay) {
self.mouse_wheel_settings.update(); self.mouse_wheel_settings.update();
self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
} }
} }
impl MouseWheelSettings { impl MouseWheelSettings {
pub(crate) fn update(&mut self) { fn update(&mut self) {
self.update_wheel_scroll_chars(); self.update_wheel_scroll_chars();
self.update_wheel_scroll_lines(); 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
}

View File

@ -92,7 +92,7 @@ impl WindowsWindowState {
let input_handler = None; let input_handler = None;
let system_key_handled = false; let system_key_handled = false;
let click_state = ClickState::new(); let click_state = ClickState::new();
let system_settings = WindowsSystemSettings::new(); let system_settings = WindowsSystemSettings::new(display);
let nc_button_pressed = None; let nc_button_pressed = None;
let fullscreen = None; let fullscreen = None;