diff --git a/.changes/windows-crash.md b/.changes/windows-crash.md new file mode 100644 index 000000000..3b138f9d1 --- /dev/null +++ b/.changes/windows-crash.md @@ -0,0 +1,6 @@ +--- +"tauri": "patch:bug" +"tauri-runtime-wry": "patch:bug" +--- + +Fix crash on Windows because of missing functions on older Windows systems, regression in 2.3.0 diff --git a/Cargo.lock b/Cargo.lock index 46396d09e..2f4000332 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8797,6 +8797,7 @@ dependencies = [ "objc2 0.6.0", "objc2-app-kit", "objc2-foundation 0.3.0", + "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index 2c5a9ba1a..efdae8dd8 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -35,6 +35,7 @@ log = "0.4.21" [target."cfg(windows)".dependencies] webview2-com = "0.36" softbuffer = { version = "0.4", default-features = false } +once_cell = "1.20" [target."cfg(windows)".dependencies.windows] version = "0.60" diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index a865c0310..07626250b 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -134,6 +134,7 @@ type IpcHandler = dyn Fn(Request) + 'static; target_os = "openbsd" ))] mod undecorated_resizing; +mod util; mod webview; mod window; diff --git a/crates/tauri-runtime-wry/src/undecorated_resizing.rs b/crates/tauri-runtime-wry/src/undecorated_resizing.rs index 82fe127d4..62a1d1439 100644 --- a/crates/tauri-runtime-wry/src/undecorated_resizing.rs +++ b/crates/tauri-runtime-wry/src/undecorated_resizing.rs @@ -78,11 +78,12 @@ fn hit_test( #[cfg(windows)] mod windows { + use crate::util; + use super::{hit_test, HitTestResult}; use windows::core::*; use windows::Win32::System::LibraryLoader::*; - use windows::Win32::UI::HiDpi::{GetDpiForWindow, GetSystemMetricsForDpi}; use windows::Win32::UI::WindowsAndMessaging::*; use windows::Win32::{Foundation::*, UI::Shell::SetWindowSubclass}; use windows::Win32::{Graphics::Gdi::*, UI::Shell::DefSubclassProc}; @@ -302,9 +303,9 @@ mod windows { let (cx, cy) = (GET_X_LPARAM(lparam) as i32, GET_Y_LPARAM(lparam) as i32); - let dpi = unsafe { GetDpiForWindow(child) }; - let border_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi); - let border_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi); + let dpi = unsafe { util::hwnd_dpi(child) }; + let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi); + let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi); let res = hit_test( rect.left, @@ -348,9 +349,9 @@ mod windows { return DefWindowProcW(child, msg, wparam, lparam); } - let padded_border = GetSystemMetrics(SM_CXPADDEDBORDER); - let border_x = GetSystemMetrics(SM_CXFRAME) + padded_border; - let border_y = GetSystemMetrics(SM_CYFRAME) + padded_border; + let dpi = unsafe { util::hwnd_dpi(child) }; + let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi); + let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi); hit_test( rect.left, @@ -415,9 +416,9 @@ mod windows { // and so we need create a cut out in the middle for the parent and other child // windows like the webview can receive mouse events. - let dpi = unsafe { GetDpiForWindow(hwnd) }; - let border_x = GetSystemMetricsForDpi(SM_CXFRAME, dpi); - let border_y = GetSystemMetricsForDpi(SM_CYFRAME, dpi); + let dpi = unsafe { util::hwnd_dpi(hwnd) }; + let border_x = util::get_system_metrics_for_dpi(SM_CXFRAME, dpi); + let border_y = util::get_system_metrics_for_dpi(SM_CYFRAME, dpi); let hrgn1 = CreateRectRgn(0, 0, width, height); diff --git a/crates/tauri-runtime-wry/src/util.rs b/crates/tauri-runtime-wry/src/util.rs new file mode 100644 index 000000000..8270ec3c3 --- /dev/null +++ b/crates/tauri-runtime-wry/src/util.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg_attr(not(windows), allow(unused_imports))] +pub use imp::*; + +#[cfg(not(windows))] +mod imp {} + +#[cfg(windows)] +mod imp { + use std::{iter::once, os::windows::ffi::OsStrExt}; + + use once_cell::sync::Lazy; + use windows::{ + core::{HRESULT, PCSTR, PCWSTR}, + Win32::{ + Foundation::*, + Graphics::Gdi::*, + System::LibraryLoader::{GetProcAddress, LoadLibraryW}, + UI::{HiDpi::*, WindowsAndMessaging::*}, + }, + }; + + pub fn encode_wide(string: impl AsRef) -> Vec { + string.as_ref().encode_wide().chain(once(0)).collect() + } + + // Helper function to dynamically load function pointer. + // `library` and `function` must be zero-terminated. + pub(super) fn get_function_impl(library: &str, function: &str) -> FARPROC { + let library = encode_wide(library); + assert_eq!(function.chars().last(), Some('\0')); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryW(PCWSTR::from_raw(library.as_ptr())) }.unwrap_or_default(); + if module.is_invalid() { + return None; + } + + unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) } + } + + macro_rules! get_function { + ($lib:expr, $func:ident) => { + $crate::util::get_function_impl($lib, concat!(stringify!($func), '\0')) + .map(|f| unsafe { std::mem::transmute::<_, $func>(f) }) + }; + } + + type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32; + type GetDpiForMonitor = unsafe extern "system" fn( + hmonitor: HMONITOR, + dpi_type: MONITOR_DPI_TYPE, + dpi_x: *mut u32, + dpi_y: *mut u32, + ) -> HRESULT; + type GetSystemMetricsForDpi = + unsafe extern "system" fn(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32; + + static GET_DPI_FOR_WINDOW: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); + static GET_DPI_FOR_MONITOR: Lazy> = + Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); + static GET_SYSTEM_METRICS_FOR_DPI: Lazy> = + Lazy::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); + + #[allow(non_snake_case)] + pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { + let hdc = GetDC(Some(hwnd)); + if hdc.is_invalid() { + return USER_DEFAULT_SCREEN_DPI; + } + if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { + // We are on Windows 10 Anniversary Update (1607) or later. + match GetDpiForWindow(hwnd) { + 0 => USER_DEFAULT_SCREEN_DPI, // 0 is returned if hwnd is invalid + dpi => dpi, + } + } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + if monitor.is_invalid() { + return USER_DEFAULT_SCREEN_DPI; + } + + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() { + dpi_x + } else { + USER_DEFAULT_SCREEN_DPI + } + } else { + // We are on Vista or later. + if IsProcessDPIAware().as_bool() { + // If the process is DPI aware, then scaling must be handled by the application using + // this DPI value. + GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 + } else { + // If the process is DPI unaware, then scaling is performed by the OS; we thus return + // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the + // application and the WM. + USER_DEFAULT_SCREEN_DPI + } + } + } + + #[allow(non_snake_case)] + pub unsafe fn get_system_metrics_for_dpi(nindex: SYSTEM_METRICS_INDEX, dpi: u32) -> i32 { + if let Some(GetSystemMetricsForDpi) = *GET_SYSTEM_METRICS_FOR_DPI { + GetSystemMetricsForDpi(nindex, dpi) + } else { + GetSystemMetrics(nindex) + } + } +}