use crate::wrappers::win32::window::HWnd;
use crate::wrappers::win32::{
Dpi, DpiAwarenessContext, DpiAwarenessContextType, DpiAwarenessGuard, ExtendedShCore,
ExtendedUser32, LazyLibraryModule, LibraryModule, ProcessDpiAwareness,
};
use std::cell::LazyCell;
use std::ops::Deref;
#[derive(Copy, Clone, Default)]
pub(crate) struct DpiScalingStrategy {
pub assume_96_dpi: bool,
pub thread_dpi_awareness_context: Option<DpiAwarenessContext>,
}
impl DpiScalingStrategy {
pub fn get(
user32: Option<&ExtendedUser32>, parent: Option<HWnd>,
#[cfg(feature = "opengl")] settings: &crate::WindowSettings,
) -> Self {
let _span = crate::debug_span!("DpiScalingStrategy");
let shcore = LibraryModule::<ExtendedShCore>::lazy();
#[cfg(feature = "opengl")]
if settings.gl_config.is_some() {
crate::debug!("OpenGL context requested: bypassing DPI awareness checking, using process DPI awareness instead.");
return Self::get_from_process(user32, &shcore);
}
let Some(user32_lib) = user32 else {
crate::debug!("user32.dll is unavailable: falling back to legacy Windows 8 APIs for DPI awareness detection.");
return Self::get_from_process_legacy(&shcore);
};
if let Some(parent) = parent {
let Some(parent_dpi_ctx) = parent.get_dpi_awareness_context(user32_lib) else {
crate::debug!("Could not get DPI Awareness Context from parent, falling back to process DPI Awareness.");
return Self::get_from_process(user32, &shcore);
};
if parent.supports_mixed_dpi_hosting_behavior(user32_lib) {
Self::get_best_matching_with_dpi_parent_awareness_context(
parent_dpi_ctx,
user32_lib,
&shcore,
)
} else {
Self::get_from_specific_dpi_awareness_context(parent_dpi_ctx, user32_lib)
}
} else {
Self::get_best_supported(user32_lib, &shcore)
}
}
fn get_from_process(
user32: Option<&ExtendedUser32>, shcore: &LazyLibraryModule<ExtendedShCore>,
) -> Self {
let Some(user32) = user32 else {
return Self::get_from_process_legacy(shcore);
};
let Some(dpi_awareness_context) = DpiAwarenessContext::get_from_process(user32) else {
return Self::get_from_process_legacy(shcore);
};
Self::get_from_specific_dpi_awareness_context(dpi_awareness_context, user32)
}
fn get_best_matching_with_dpi_parent_awareness_context(
parent_dpi_awareness_context: DpiAwarenessContext, user32: &ExtendedUser32,
shcore: &LazyLibraryModule<ExtendedShCore>,
) -> Self {
use DpiAwarenessContextType::*;
let dpi_awareness_type = parent_dpi_awareness_context.get_type(user32);
crate::debug!(
"Parent DPI hosting behavior is mixed, parent DPI Awareness Context type is {:?}.",
dpi_awareness_type
);
if matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled | SystemDpiAware)) {
crate::debug!("Parent has DPI Awareness Context with Per-Monitor DPI awareness, falling back to System DPI Awareness.");
return Self {
assume_96_dpi: false,
thread_dpi_awareness_context: Some(SystemDpiAware.into()),
};
}
Self::get_best_supported(user32, shcore)
}
fn get_from_specific_dpi_awareness_context(
dpi_awareness_context: DpiAwarenessContext, user32: &ExtendedUser32,
) -> Self {
use DpiAwarenessContextType::*;
let dpi_awareness_type = dpi_awareness_context.get_type(user32);
crate::debug!("Using DPI Awareness Context of type {:?}.", dpi_awareness_type);
let assume_96_dpi = matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled));
Self { assume_96_dpi, thread_dpi_awareness_context: Some(dpi_awareness_context) }
}
fn get_from_process_legacy(shcore: &LazyLibraryModule<ExtendedShCore>) -> Self {
use ProcessDpiAwareness::*;
let Some(shcore) = LazyCell::deref(shcore) else { return Self::completely_unaware() };
let awareness = ProcessDpiAwareness::get(shcore);
crate::debug!("Using legacy Process DPI Awareness: {:?}", awareness);
let assume_96_dpi = matches!(awareness, None | Some(Unaware));
Self { assume_96_dpi, thread_dpi_awareness_context: None }
}
fn completely_unaware() -> Self {
Self { assume_96_dpi: true, thread_dpi_awareness_context: None }
}
fn get_best_supported(
user32: &ExtendedUser32, shcore: &LazyLibraryModule<ExtendedShCore>,
) -> Self {
let Some(best_supported) = DpiAwarenessContextType::best_supported(user32) else {
crate::debug!("No DPI Awareness Context types are available. Falling back to legacy Windows 8 APIs.");
return Self::get_from_process_legacy(shcore);
};
Self::get_from_specific_dpi_awareness_context(best_supported.into(), user32)
}
}
impl DpiScalingStrategy {
pub fn get_dpi_for_window(&self, own_window: HWnd, user32: &ExtendedUser32) -> Option<Dpi> {
if self.assume_96_dpi {
return Some(Dpi::default());
}
DpiAwarenessGuard::with_guard_optional(user32, *self, || {
if let Some(dpi) = own_window.get_dpi(user32) {
return Some(dpi);
}
if let Some(dpi) =
own_window.get_dpi_awareness_context(user32).and_then(|d| d.dpi(user32))
{
return Some(dpi);
}
let shcore = LibraryModule::<ExtendedShCore>::lazy();
if let Some(dpi) =
shcore.as_ref().and_then(|shcore| own_window.get_dpi_from_monitor(shcore))
{
return Some(dpi);
}
Dpi::get_system(user32)
})
}
}
pub(crate) fn set_process_dpi_awareness() {
if !set_process_dpi_awareness_context() {
set_process_dpi_awareness_legacy()
}
}
fn set_process_dpi_awareness_context() -> bool {
let user32 = match LibraryModule::<ExtendedUser32>::load() {
Ok(user32) => user32,
Err(e) => {
crate::warn!("Failed to load user32.dll: {}", e);
return false;
}
};
let Some(supported) = DpiAwarenessContextType::best_supported(&user32) else { return false };
match DpiAwarenessContext::from(supported).set_process(&user32) {
None => false,
Some(Ok(())) => true,
Some(Err(e)) => {
crate::warn!("Failed to set Process DPI Awareness Context: {}", e);
false
}
}
}
fn set_process_dpi_awareness_legacy() {
let shcore = match LibraryModule::<ExtendedShCore>::load() {
Ok(user32) => user32,
Err(e) => {
crate::warn!("Failed to load api-ms-win-shcore-scaling-l1-1-1.dll: {}", e);
return;
}
};
if let Err(e) = ProcessDpiAwareness::PerMonitorDpiAware.set(&shcore) {
crate::warn!("Failed to set Process DPI Awareness: {}", e);
}
}