Skip to content

Commit 5cc9298

Browse files
authored
1 parent 94afde9 commit 5cc9298

File tree

9 files changed

+282
-161
lines changed

9 files changed

+282
-161
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": patch
3+
---
4+
5+
On Windows, fix regression `Window::inner_size` reporting larger size than what's visible for undecorated window.
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tao": patch
3+
---
4+
5+
On Windows, undecorated window with shadows, now have native resize handles outside of the window client area.

src/platform_impl/windows/dark_mode.rs

+44-57
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,16 @@ use std::ffi::c_void;
2020

2121
use crate::window::Theme;
2222

23+
use super::util;
24+
2325
static HUXTHEME: Lazy<isize> =
2426
Lazy::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ });
2527

26-
static WIN10_BUILD_VERSION: Lazy<Option<u32>> = Lazy::new(|| {
27-
let version = windows_version::OsVersion::current();
28-
if version.major == 10 && version.minor == 0 {
29-
Some(version.build)
30-
} else {
31-
None
32-
}
33-
});
34-
3528
static DARK_MODE_SUPPORTED: Lazy<bool> = Lazy::new(|| {
3629
// We won't try to do anything for windows versions < 17763
3730
// (Windows 10 October 2018 update)
38-
match *WIN10_BUILD_VERSION {
39-
Some(v) => v >= 17763,
40-
None => false,
41-
}
31+
let v = *util::WIN_VERSION;
32+
v.major == 10 && v.minor == 0 && v.build >= 17763
4233
});
4334

4435
pub fn allow_dark_mode_for_app(is_dark_mode: bool) {
@@ -79,19 +70,17 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) {
7970
.map(|handle| std::mem::transmute(handle))
8071
});
8172

82-
if let Some(ver) = *WIN10_BUILD_VERSION {
83-
if ver < 18362 {
84-
if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP {
85-
unsafe { _allow_dark_mode_for_app(is_dark_mode) };
86-
}
87-
} else if let Some(_set_preferred_app_mode) = *SET_PREFERRED_APP_MODE {
88-
let mode = if is_dark_mode {
89-
PreferredAppMode::AllowDark
90-
} else {
91-
PreferredAppMode::Default
92-
};
93-
unsafe { _set_preferred_app_mode(mode) };
73+
if util::WIN_VERSION.build < 18362 {
74+
if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP {
75+
unsafe { _allow_dark_mode_for_app(is_dark_mode) };
9476
}
77+
} else if let Some(_set_preferred_app_mode) = *SET_PREFERRED_APP_MODE {
78+
let mode = if is_dark_mode {
79+
PreferredAppMode::AllowDark
80+
} else {
81+
PreferredAppMode::Default
82+
};
83+
unsafe { _set_preferred_app_mode(mode) };
9584
}
9685

9786
refresh_immersive_color_policy_state();
@@ -168,39 +157,37 @@ pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) {
168157
}
169158

170159
fn refresh_titlebar_theme_color(hwnd: HWND, is_dark_mode: bool, redraw_title_bar: bool) {
171-
if let Some(ver) = *WIN10_BUILD_VERSION {
172-
if ver < 17763 {
173-
let mut is_dark_mode_bigbool: i32 = is_dark_mode.into();
174-
unsafe {
175-
let _ = SetPropW(
176-
hwnd,
177-
w!("UseImmersiveDarkModeColors"),
178-
Some(HANDLE(&mut is_dark_mode_bigbool as *mut _ as _)),
179-
);
180-
}
160+
if util::WIN_VERSION.build < 17763 {
161+
let mut is_dark_mode_bigbool: i32 = is_dark_mode.into();
162+
unsafe {
163+
let _ = SetPropW(
164+
hwnd,
165+
w!("UseImmersiveDarkModeColors"),
166+
Some(HANDLE(&mut is_dark_mode_bigbool as *mut _ as _)),
167+
);
168+
}
169+
} else {
170+
// https://github.com/MicrosoftDocs/sdk-api/pull/966/files
171+
let dwmwa_use_immersive_dark_mode = if util::WIN_VERSION.build > 18985 {
172+
DWMWINDOWATTRIBUTE(20)
181173
} else {
182-
// https://github.com/MicrosoftDocs/sdk-api/pull/966/files
183-
let dwmwa_use_immersive_dark_mode = if ver > 18985 {
184-
DWMWINDOWATTRIBUTE(20)
185-
} else {
186-
DWMWINDOWATTRIBUTE(19)
187-
};
188-
let dark_mode = BOOL::from(is_dark_mode);
189-
unsafe {
190-
let _ = DwmSetWindowAttribute(
191-
hwnd,
192-
dwmwa_use_immersive_dark_mode,
193-
&dark_mode as *const BOOL as *const c_void,
194-
std::mem::size_of::<BOOL>() as u32,
195-
);
196-
if redraw_title_bar {
197-
if GetActiveWindow() == hwnd {
198-
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
199-
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default());
200-
} else {
201-
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default());
202-
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
203-
}
174+
DWMWINDOWATTRIBUTE(19)
175+
};
176+
let dark_mode = BOOL::from(is_dark_mode);
177+
unsafe {
178+
let _ = DwmSetWindowAttribute(
179+
hwnd,
180+
dwmwa_use_immersive_dark_mode,
181+
&dark_mode as *const BOOL as *const c_void,
182+
std::mem::size_of::<BOOL>() as u32,
183+
);
184+
if redraw_title_bar {
185+
if GetActiveWindow() == hwnd {
186+
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
187+
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default());
188+
} else {
189+
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM(true.into()), LPARAM::default());
190+
DefWindowProcW(hwnd, WM_NCACTIVATE, WPARAM::default(), LPARAM::default());
204191
}
205192
}
206193
}

src/platform_impl/windows/dpi.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@ pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
6464
None
6565
}
6666

67-
pub const BASE_DPI: u32 = 96;
6867
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
69-
dpi as f64 / BASE_DPI as f64
68+
dpi as f64 / USER_DEFAULT_SCREEN_DPI as f64
7069
}
7170

7271
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
@@ -77,22 +76,22 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
7776
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
7877
// We are on Windows 10 Anniversary Update (1607) or later.
7978
match GetDpiForWindow(hwnd) {
80-
0 => BASE_DPI, // 0 is returned if hwnd is invalid
79+
0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid
8180
dpi => dpi as u32,
8281
}
8382
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
8483
// We are on Windows 8.1 or later.
8584
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
8685
if monitor.is_invalid() {
87-
return BASE_DPI;
86+
return USER_DEFAULT_SCREEN_DPI;
8887
}
8988

9089
let mut dpi_x = 0;
9190
let mut dpi_y = 0;
9291
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() {
9392
dpi_x as u32
9493
} else {
95-
BASE_DPI
94+
USER_DEFAULT_SCREEN_DPI
9695
}
9796
} else {
9897
// We are on Vista or later.
@@ -104,7 +103,7 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
104103
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
105104
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
106105
// application and the WM.
107-
BASE_DPI
106+
USER_DEFAULT_SCREEN_DPI
108107
}
109108
}
110109
}

src/platform_impl/windows/event_loop.rs

+41-22
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use windows::{
3333
},
3434
UI::{
3535
Controls::{self as win32c, HOVER_DEFAULT},
36+
HiDpi::GetSystemMetricsForDpi,
3637
Input::{KeyboardAndMouse::*, Pointer::*, Touch::*, *},
3738
Shell::{
3839
DefSubclassProc, RemoveWindowSubclass, SHAppBarMessage, SetWindowSubclass, ABE_BOTTOM,
@@ -66,6 +67,8 @@ use crate::{
6667
};
6768
use runner::{EventLoopRunner, EventLoopRunnerShared};
6869

70+
use super::dpi::hwnd_dpi;
71+
6972
type GetPointerFrameInfoHistory = unsafe extern "system" fn(
7073
pointerId: u32,
7174
entriesCount: *mut u32,
@@ -2151,10 +2154,13 @@ unsafe fn public_window_callback_inner<T: 'static>(
21512154
}
21522155
} else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) && !is_fullscreen {
21532156
let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS);
2154-
params.rgrc[0].top += 1;
2155-
params.rgrc[0].bottom += 1;
2156-
params.rgrc[0].left += 1;
2157-
params.rgrc[0].right += 1;
2157+
2158+
let insets = util::calculate_window_insets(window);
2159+
2160+
params.rgrc[0].left += insets.left;
2161+
params.rgrc[0].top += insets.top;
2162+
params.rgrc[0].right -= insets.right;
2163+
params.rgrc[0].bottom -= insets.bottom;
21582164
}
21592165
result = ProcResult::Value(LRESULT(0)); // return 0 here to make the window borderless
21602166
}
@@ -2176,25 +2182,38 @@ unsafe fn public_window_callback_inner<T: 'static>(
21762182
util::GET_Y_LPARAM(lparam) as i32,
21772183
);
21782184

2179-
let mut rect = RECT::default();
2180-
let _ = GetWindowRect(window, &mut rect);
2181-
2182-
let padded_border = GetSystemMetrics(SM_CXPADDEDBORDER);
2183-
let border_x = GetSystemMetrics(SM_CXFRAME) + padded_border;
2184-
let border_y = GetSystemMetrics(SM_CYFRAME) + padded_border;
2185-
2186-
let hit_result = crate::window::hit_test(
2187-
(rect.left, rect.top, rect.right, rect.bottom),
2188-
cx,
2189-
cy,
2190-
border_x,
2191-
border_y,
2192-
)
2193-
.map(|d| d.to_win32());
2185+
let dpi = hwnd_dpi(window);
2186+
let border_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi);
21942187

2195-
result = hit_result
2196-
.map(|r| ProcResult::Value(LRESULT(r as _)))
2197-
.unwrap_or(ProcResult::DefSubclassProc);
2188+
// if we have undecorated shadows, we only need to handle the top edge
2189+
if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) {
2190+
let mut cursor_pt = POINT { x: cx, y: cy };
2191+
if ScreenToClient(window, &mut cursor_pt).as_bool()
2192+
&& cursor_pt.y >= 0
2193+
&& cursor_pt.y <= border_y
2194+
{
2195+
result = ProcResult::Value(LRESULT(HTTOP as _));
2196+
}
2197+
} else {
2198+
//otherwise do full hit testing
2199+
let border_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi);
2200+
2201+
let mut rect = RECT::default();
2202+
let _ = GetWindowRect(window, &mut rect);
2203+
2204+
let hit_result = crate::window::hit_test(
2205+
(rect.left, rect.top, rect.right, rect.bottom),
2206+
cx,
2207+
cy,
2208+
border_x,
2209+
border_y,
2210+
)
2211+
.map(|d| d.to_win32());
2212+
2213+
result = hit_result
2214+
.map(|r| ProcResult::Value(LRESULT(r as _)))
2215+
.unwrap_or(ProcResult::DefSubclassProc);
2216+
}
21982217
} else {
21992218
result = ProcResult::DefSubclassProc;
22002219
}

src/platform_impl/windows/monitor.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use windows::{
77
Win32::{
88
Foundation::{BOOL, HWND, LPARAM, POINT, RECT},
99
Graphics::Gdi::*,
10+
UI::WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI,
1011
},
1112
};
1213

@@ -216,7 +217,7 @@ impl MonitorHandle {
216217

217218
#[inline]
218219
pub fn scale_factor(&self) -> f64 {
219-
dpi_to_scale_factor(get_monitor_dpi(self.hmonitor()).unwrap_or(96))
220+
dpi_to_scale_factor(get_monitor_dpi(self.hmonitor()).unwrap_or(USER_DEFAULT_SCREEN_DPI))
220221
}
221222

222223
#[inline]

src/platform_impl/windows/util.rs

+70
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
window::CursorIcon,
1818
};
1919

20+
use once_cell::sync::Lazy;
2021
use windows::{
2122
core::{HRESULT, PCSTR, PCWSTR},
2223
Win32::{
@@ -425,3 +426,72 @@ pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE {
425426

426427
windows::Win32::Foundation::HMODULE(unsafe { &__ImageBase as *const _ as _ })
427428
}
429+
430+
pub static WIN_VERSION: Lazy<windows_version::OsVersion> =
431+
Lazy::new(windows_version::OsVersion::current);
432+
433+
pub fn get_frame_thickness(dpi: u32) -> i32 {
434+
let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
435+
let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
436+
resize_frame_thickness + padding_thickness
437+
}
438+
439+
pub fn calculate_insets_for_dpi(dpi: u32) -> RECT {
440+
// - On Windows 10
441+
// The top inset must be zero, since if there is any nonclient area, Windows will draw
442+
// a full native titlebar outside the client area. (This doesn't occur in the maximized
443+
// case.)
444+
//
445+
// - On Windows 11
446+
// The top inset is calculated using an empirical formula that I derived through various
447+
// tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
448+
449+
let frame_thickness = get_frame_thickness(dpi);
450+
451+
let top_inset = match WIN_VERSION.build {
452+
v if v >= 22000 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32,
453+
_ => 0,
454+
};
455+
456+
RECT {
457+
left: frame_thickness,
458+
top: top_inset,
459+
right: frame_thickness,
460+
bottom: frame_thickness,
461+
}
462+
}
463+
464+
/// Calcuclate window insets, used in WM_NCCALCSIZE
465+
///
466+
/// Derived of GPUI implementation
467+
/// see <https://github.com/zed-industries/zed/blob/7bddb390cabefb177d9996dc580749d64e6ca3b6/crates/gpui/src/platform/windows/events.rs#L1418-L1454>
468+
pub fn calculate_window_insets(window: HWND) -> RECT {
469+
let dpi = unsafe { super::dpi::hwnd_dpi(window) };
470+
calculate_insets_for_dpi(dpi)
471+
}
472+
473+
pub fn window_rect(hwnd: HWND) -> RECT {
474+
unsafe {
475+
let mut rect = RECT::default();
476+
if GetWindowRect(hwnd, &mut rect).is_err() {
477+
panic!(
478+
"Unexpected GetWindowRect failure: please report this error to \
479+
tauri-apps/tao"
480+
)
481+
}
482+
rect
483+
}
484+
}
485+
486+
pub fn client_rect(hwnd: HWND) -> RECT {
487+
unsafe {
488+
let mut rect = RECT::default();
489+
if GetClientRect(hwnd, &mut rect).is_err() {
490+
panic!(
491+
"Unexpected GetClientRect failure: please report this error to \
492+
tauri-apps/tao"
493+
)
494+
}
495+
rect
496+
}
497+
}

0 commit comments

Comments
 (0)