Skip to content

Commit e103e87

Browse files
authored
fix(windows): ensure APIs exist before using it (#12848)
* fix(windows): ensure APIs exist before using it * fix build on other platforms * clippy * use GetSystemMetricsForDpi
1 parent bca0296 commit e103e87

File tree

6 files changed

+138
-10
lines changed

6 files changed

+138
-10
lines changed

Diff for: .changes/windows-crash.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": "patch:bug"
3+
"tauri-runtime-wry": "patch:bug"
4+
---
5+
6+
Fix crash on Windows because of missing functions on older Windows systems, regression in 2.3.0

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crates/tauri-runtime-wry/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ log = "0.4.21"
3535
[target."cfg(windows)".dependencies]
3636
webview2-com = "0.36"
3737
softbuffer = { version = "0.4", default-features = false }
38+
once_cell = "1.20"
3839

3940
[target."cfg(windows)".dependencies.windows]
4041
version = "0.60"

Diff for: crates/tauri-runtime-wry/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ type IpcHandler = dyn Fn(Request<String>) + 'static;
134134
target_os = "openbsd"
135135
))]
136136
mod undecorated_resizing;
137+
mod util;
137138
mod webview;
138139
mod window;
139140

Diff for: crates/tauri-runtime-wry/src/undecorated_resizing.rs

+11-10
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ fn hit_test(
7878

7979
#[cfg(windows)]
8080
mod windows {
81+
use crate::util;
82+
8183
use super::{hit_test, HitTestResult};
8284

8385
use windows::core::*;
8486
use windows::Win32::System::LibraryLoader::*;
85-
use windows::Win32::UI::HiDpi::{GetDpiForWindow, GetSystemMetricsForDpi};
8687
use windows::Win32::UI::WindowsAndMessaging::*;
8788
use windows::Win32::{Foundation::*, UI::Shell::SetWindowSubclass};
8889
use windows::Win32::{Graphics::Gdi::*, UI::Shell::DefSubclassProc};
@@ -302,9 +303,9 @@ mod windows {
302303

303304
let (cx, cy) = (GET_X_LPARAM(lparam) as i32, GET_Y_LPARAM(lparam) as i32);
304305

305-
let dpi = unsafe { GetDpiForWindow(child) };
306-
let border_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi);
307-
let border_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi);
306+
let dpi = unsafe { util::hwnd_dpi(child) };
307+
let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);
308+
let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);
308309

309310
let res = hit_test(
310311
rect.left,
@@ -348,9 +349,9 @@ mod windows {
348349
return DefWindowProcW(child, msg, wparam, lparam);
349350
}
350351

351-
let padded_border = GetSystemMetrics(SM_CXPADDEDBORDER);
352-
let border_x = GetSystemMetrics(SM_CXFRAME) + padded_border;
353-
let border_y = GetSystemMetrics(SM_CYFRAME) + padded_border;
352+
let dpi = unsafe { util::hwnd_dpi(child) };
353+
let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);
354+
let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);
354355

355356
hit_test(
356357
rect.left,
@@ -415,9 +416,9 @@ mod windows {
415416
// and so we need create a cut out in the middle for the parent and other child
416417
// windows like the webview can receive mouse events.
417418

418-
let dpi = unsafe { GetDpiForWindow(hwnd) };
419-
let border_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi);
420-
let border_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi);
419+
let dpi = unsafe { util::hwnd_dpi(hwnd) };
420+
let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi);
421+
let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi);
421422

422423
let hrgn1 = CreateRectRgn(0, 0, width, height);
423424

Diff for: crates/tauri-runtime-wry/src/util.rs

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
#[cfg_attr(not(windows), allow(unused_imports))]
6+
pub use imp::*;
7+
8+
#[cfg(not(windows))]
9+
mod imp {}
10+
11+
#[cfg(windows)]
12+
mod imp {
13+
use std::{iter::once, os::windows::ffi::OsStrExt};
14+
15+
use once_cell::sync::Lazy;
16+
use windows::{
17+
core::{HRESULT, PCSTR, PCWSTR},
18+
Win32::{
19+
Foundation::*,
20+
Graphics::Gdi::*,
21+
System::LibraryLoader::{GetProcAddress, LoadLibraryW},
22+
UI::{HiDpi::*, WindowsAndMessaging::*},
23+
},
24+
};
25+
26+
pub fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
27+
string.as_ref().encode_wide().chain(once(0)).collect()
28+
}
29+
30+
// Helper function to dynamically load function pointer.
31+
// `library` and `function` must be zero-terminated.
32+
pub(super) fn get_function_impl(library: &str, function: &str) -> FARPROC {
33+
let library = encode_wide(library);
34+
assert_eq!(function.chars().last(), Some('\0'));
35+
36+
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
37+
let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default();
38+
if module.is_invalid() {
39+
return None;
40+
}
41+
42+
unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
43+
}
44+
45+
macro_rules! get_function {
46+
($lib:expr, $func:ident) => {
47+
$crate::util::get_function_impl($lib, concat!(stringify!($func), '\0'))
48+
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
49+
};
50+
}
51+
52+
type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
53+
type GetDpiForMonitor = unsafe extern "system" fn(
54+
hmonitor: HMONITOR,
55+
dpi_type: MONITOR_DPI_TYPE,
56+
dpi_x: *mut u32,
57+
dpi_y: *mut u32,
58+
) -> HRESULT;
59+
type GetSystemMetricsForDpi =
60+
unsafe extern "system" fn(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32;
61+
62+
static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
63+
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
64+
static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
65+
Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));
66+
static GET_SYSTEM_METRICS_FOR_DPI: Lazy<Option<GetSystemMetricsForDpi>> =
67+
Lazy::new(|| get_function!("user32.dll", GetSystemMetricsForDpi));
68+
69+
#[allow(non_snake_case)]
70+
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
71+
let hdc = GetDC(Some(hwnd));
72+
if hdc.is_invalid() {
73+
return USER_DEFAULT_SCREEN_DPI;
74+
}
75+
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
76+
// We are on Windows 10 Anniversary Update (1607) or later.
77+
match GetDpiForWindow(hwnd) {
78+
0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid
79+
dpi => dpi,
80+
}
81+
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
82+
// We are on Windows 8.1 or later.
83+
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
84+
if monitor.is_invalid() {
85+
return USER_DEFAULT_SCREEN_DPI;
86+
}
87+
88+
let mut dpi_x = 0;
89+
let mut dpi_y = 0;
90+
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() {
91+
dpi_x
92+
} else {
93+
USER_DEFAULT_SCREEN_DPI
94+
}
95+
} else {
96+
// We are on Vista or later.
97+
if IsProcessDPIAware().as_bool() {
98+
// If the process is DPI aware, then scaling must be handled by the application using
99+
// this DPI value.
100+
GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32
101+
} else {
102+
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
103+
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
104+
// application and the WM.
105+
USER_DEFAULT_SCREEN_DPI
106+
}
107+
}
108+
}
109+
110+
#[allow(non_snake_case)]
111+
pub unsafe fn get_system_metrics_for_dpi(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32 {
112+
if let Some(GetSystemMetricsForDpi) = *GET_SYSTEM_METRICS_FOR_DPI {
113+
GetSystemMetricsForDpi(nindex, dpi)
114+
} else {
115+
GetSystemMetrics(nindex)
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)