|
| 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