Simplify calculation of Windows client area size (#49249)

John Tur created

Delete all the hacks and do things properly instead.

Closes https://github.com/zed-industries/zed/issues/45574

Before you mark this PR as ready for review, make sure that you have:
- [X] Added a solid test coverage and/or screenshots from doing manual
testing
- [X] Done a self-review taking into account security and performance
aspects

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/events.rs          |  90 +---------
crates/gpui/src/platform/windows/platform.rs        |   5 
crates/gpui/src/platform/windows/system_settings.rs | 127 --------------
crates/gpui/src/platform/windows/util.rs            |  21 --
crates/gpui/src/platform/windows/window.rs          |  20 +
5 files changed, 31 insertions(+), 232 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/events.rs 🔗

@@ -29,7 +29,6 @@ pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;
 pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8;
 
 const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
-const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
 
 impl WindowsWindowInner {
     pub(crate) fn handle_msg(
@@ -668,7 +667,6 @@ impl WindowsWindowInner {
         }
     }
 
-    /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
     fn handle_calc_client_size(
         &self,
         handle: HWND,
@@ -679,43 +677,17 @@ impl WindowsWindowInner {
             return None;
         }
 
-        let is_maximized = self.state.is_maximized();
-        let insets = get_client_area_insets(handle, is_maximized, self.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].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
-            && let Some(taskbar_position) = self.system_settings().auto_hide_taskbar_position.get()
-        {
-            // For 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
-                }
+        unsafe {
+            let params = lparam.0 as *mut NCCALCSIZE_PARAMS;
+            let saved_top = (*params).rgrc[0].top;
+            let result = DefWindowProcW(handle, WM_NCCALCSIZE, wparam, lparam);
+            (*params).rgrc[0].top = saved_top;
+            if self.state.is_maximized() {
+                let dpi = GetDpiForWindow(handle);
+                (*params).rgrc[0].top += get_frame_thicknessx(dpi);
             }
+            Some(result.0 as isize)
         }
-
-        Some(0)
     }
 
     fn handle_activate_msg(self: &Rc<Self>, wparam: WPARAM) -> Option<isize> {
@@ -1064,18 +1036,14 @@ impl WindowsWindowInner {
         lparam: LPARAM,
     ) -> Option<isize> {
         if wparam.0 != 0 {
-            let display = self.state.display.get();
             self.state.click_state.system_update(wparam.0);
             self.state.border_offset.update(handle).log_err();
             // system settings may emit a window message which wants to take the refcell self.state, so drop it
 
-            self.system_settings().update(display, wparam.0);
+            self.system_settings().update(wparam.0);
         } else {
             self.handle_system_theme_changed(handle, lparam)?;
         };
-        // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide
-        // taskbar correctly.
-        notify_frame_changed(handle);
 
         Some(0)
     }
@@ -1513,44 +1481,6 @@ pub(crate) fn current_capslock() -> Capslock {
     Capslock { on }
 }
 
-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_thicknessx(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.

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -39,7 +39,6 @@ pub(crate) struct WindowsPlatform {
     foreground_executor: ForegroundExecutor,
     text_system: Arc<dyn PlatformTextSystem>,
     direct_write_text_system: Option<Arc<DirectWriteTextSystem>>,
-    windows_version: WindowsVersion,
     drop_target_helper: Option<IDropTargetHelper>,
     /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices
     /// as resizing them has failed, causing us to have lost at least the render target.
@@ -179,7 +178,6 @@ impl WindowsPlatform {
         } else {
             HICON::default()
         };
-        let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
 
         Ok(Self {
             inner,
@@ -192,7 +190,6 @@ impl WindowsPlatform {
             text_system,
             direct_write_text_system,
             disable_direct_composition,
-            windows_version,
             drop_target_helper,
             invalidate_devices: Arc::new(AtomicBool::new(false)),
         })
@@ -221,7 +218,6 @@ impl WindowsPlatform {
             icon: self.icon,
             executor: self.foreground_executor.clone(),
             current_cursor: self.inner.state.current_cursor.get(),
-            windows_version: self.windows_version,
             drop_target_helper: self.drop_target_helper.clone().unwrap(),
             validation_number: self.inner.validation_number,
             main_receiver: self.inner.main_receiver.clone(),
@@ -1007,7 +1003,6 @@ pub(crate) struct WindowCreationInfo {
     pub(crate) icon: HICON,
     pub(crate) executor: ForegroundExecutor,
     pub(crate) current_cursor: Option<HCURSOR>,
-    pub(crate) windows_version: WindowsVersion,
     pub(crate) drop_target_helper: IDropTargetHelper,
     pub(crate) validation_number: usize,
     pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,

crates/gpui/src/platform/windows/system_settings.rs 🔗

@@ -4,24 +4,16 @@ use std::{
 };
 
 use ::util::ResultExt;
-use windows::Win32::UI::{
-    Shell::{ABM_GETSTATE, ABM_GETTASKBARPOS, ABS_AUTOHIDE, APPBARDATA, SHAppBarMessage},
-    WindowsAndMessaging::{
-        SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SPI_SETWORKAREA,
-        SYSTEM_PARAMETERS_INFO_ACTION, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, SystemParametersInfoW,
-    },
+use windows::Win32::UI::WindowsAndMessaging::{
+    SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_ACTION,
+    SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, SystemParametersInfoW,
 };
 
-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)]
 pub(crate) struct WindowsSystemSettings {
     pub(crate) mouse_wheel_settings: MouseWheelSettings,
-    pub(crate) auto_hide_taskbar_position: Cell<Option<AutoHideTaskbarPosition>>,
 }
 
 #[derive(Default, Debug, Clone)]
@@ -33,21 +25,18 @@ pub(crate) struct MouseWheelSettings {
 }
 
 impl WindowsSystemSettings {
-    pub(crate) fn new(display: WindowsDisplay) -> Self {
+    pub(crate) fn new() -> Self {
         let mut settings = Self::default();
-        settings.init(display);
+        settings.init();
         settings
     }
 
-    fn init(&mut self, display: WindowsDisplay) {
+    fn init(&mut self) {
         self.mouse_wheel_settings.update();
-        self.auto_hide_taskbar_position
-            .set(AutoHideTaskbarPosition::new(display).log_err().flatten());
     }
 
-    pub(crate) fn update(&self, display: WindowsDisplay, wparam: usize) {
+    pub(crate) fn update(&self, wparam: usize) {
         match SYSTEM_PARAMETERS_INFO_ACTION(wparam as u32) {
-            SPI_SETWORKAREA => self.update_taskbar_position(display),
             SPI_GETWHEELSCROLLLINES | SPI_GETWHEELSCROLLCHARS => self.update_mouse_wheel_settings(),
             _ => {}
         }
@@ -56,11 +45,6 @@ impl WindowsSystemSettings {
     fn update_mouse_wheel_settings(&self) {
         self.mouse_wheel_settings.update();
     }
-
-    fn update_taskbar_position(&self, display: WindowsDisplay) {
-        self.auto_hide_taskbar_position
-            .set(AutoHideTaskbarPosition::new(display).log_err().flatten());
-    }
 }
 
 impl MouseWheelSettings {
@@ -101,100 +85,3 @@ 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
-}

crates/gpui/src/platform/windows/util.rs 🔗

@@ -7,7 +7,6 @@ use windows::{
         Color,
         ViewManagement::{UIColorType, UISettings},
     },
-    Wdk::System::SystemServices::RtlGetVersion,
     Win32::{
         Foundation::*, Graphics::Dwm::*, System::LibraryLoader::LoadLibraryA,
         UI::WindowsAndMessaging::*,
@@ -17,26 +16,6 @@ use windows::{
 
 use crate::*;
 
-#[derive(Debug, Clone, Copy)]
-pub(crate) enum WindowsVersion {
-    Win10,
-    Win11,
-}
-
-impl WindowsVersion {
-    pub(crate) fn new() -> anyhow::Result<Self> {
-        let mut version = unsafe { std::mem::zeroed() };
-        let status = unsafe { RtlGetVersion(&mut version) };
-
-        status.ok()?;
-        if version.dwBuildNumber >= 22000 {
-            Ok(WindowsVersion::Win11)
-        } else {
-            Ok(WindowsVersion::Win10)
-        }
-    }
-}
-
 pub(crate) trait HiLoWord {
     fn hiword(&self) -> u16;
     fn loword(&self) -> u16;

crates/gpui/src/platform/windows/window.rs 🔗

@@ -80,7 +80,6 @@ pub(crate) struct WindowsWindowInner {
     pub(crate) hide_title_bar: bool,
     pub(crate) is_movable: bool,
     pub(crate) executor: ForegroundExecutor,
-    pub(crate) windows_version: WindowsVersion,
     pub(crate) validation_number: usize,
     pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
     pub(crate) platform_window_handle: HWND,
@@ -239,11 +238,10 @@ impl WindowsWindowInner {
             hide_title_bar: context.hide_title_bar,
             is_movable: context.is_movable,
             executor: context.executor.clone(),
-            windows_version: context.windows_version,
             validation_number: context.validation_number,
             main_receiver: context.main_receiver.clone(),
             platform_window_handle: context.platform_window_handle,
-            system_settings: WindowsSystemSettings::new(context.display),
+            system_settings: WindowsSystemSettings::new(),
             parent_hwnd: context.parent_hwnd,
         }))
     }
@@ -293,6 +291,7 @@ impl WindowsWindowInner {
                         }
                     }
                 };
+                set_non_rude_hwnd(this.hwnd, !this.state.is_fullscreen());
                 unsafe { set_window_long(this.hwnd, GWL_STYLE, style.0 as isize) };
                 unsafe {
                     SetWindowPos(
@@ -363,7 +362,6 @@ struct WindowCreateContext {
     min_size: Option<Size<Pixels>>,
     executor: ForegroundExecutor,
     current_cursor: Option<HCURSOR>,
-    windows_version: WindowsVersion,
     drop_target_helper: IDropTargetHelper,
     validation_number: usize,
     main_receiver: PriorityQueueReceiver<RunnableVariant>,
@@ -385,7 +383,6 @@ impl WindowsWindow {
             icon,
             executor,
             current_cursor,
-            windows_version,
             drop_target_helper,
             validation_number,
             main_receiver,
@@ -465,7 +462,6 @@ impl WindowsWindow {
             min_size: params.window_min_size,
             executor,
             current_cursor,
-            windows_version,
             drop_target_helper,
             validation_number,
             main_receiver,
@@ -500,6 +496,7 @@ impl WindowsWindow {
         let this = this.unwrap();
 
         register_drag_drop(&this)?;
+        set_non_rude_hwnd(hwnd, true);
         configure_dwm_dark_mode(hwnd, appearance);
         this.state.border_offset.update(hwnd)?;
         let placement = retrieve_window_placement(
@@ -1465,6 +1462,17 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
     }
 }
 
+// When the platform title bar is hidden, Windows may think that our application is meant to appear 'fullscreen'
+// and will stop the taskbar from appearing on top of our window. Prevent this.
+// https://devblogs.microsoft.com/oldnewthing/20250522-00/?p=111211
+fn set_non_rude_hwnd(hwnd: HWND, non_rude: bool) {
+    if non_rude {
+        unsafe { SetPropW(hwnd, w!("NonRudeHWND"), Some(HANDLE(1 as _))) }.log_err();
+    } else {
+        unsafe { RemovePropW(hwnd, w!("NonRudeHWND")) }.log_err();
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::ClickState;