Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(windows): tray icon rect scaled by dpi #164

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/windows-rect-dpi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tray-icon": "patch"
---

Fix tray icon rect scaled by dpi on Windows
32 changes: 10 additions & 22 deletions src/platform_impl/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,7 @@ impl TrayIcon {
}

pub fn rect(&self) -> Option<Rect> {
get_tray_rect(self.internal_id, self.hwnd).map(|rect| {
let dpi = unsafe { util::hwnd_dpi(self.hwnd) };
let scale_factor = util::dpi_to_scale_factor(dpi);
Rect::from_win32(rect, scale_factor)
})
get_tray_rect(self.internal_id, self.hwnd).map(Into::into)
}
}

Expand Down Expand Up @@ -344,14 +340,11 @@ unsafe extern "system" fn tray_proc(
return 0;
}

let dpi = util::hwnd_dpi(hwnd);
let scale_factor = util::dpi_to_scale_factor(dpi);

let id = userdata.id.clone();
let position = PhysicalPosition::new(cursor.x as f64, cursor.y as f64);

let rect = match get_tray_rect(userdata.internal_id, hwnd) {
Some(rect) => Rect::from_win32(rect, scale_factor),
Some(rect) => Rect::from(rect),
None => return 0,
};

Expand Down Expand Up @@ -445,14 +438,11 @@ unsafe extern "system" fn tray_proc(
let in_y = (rect.top..rect.bottom).contains(&cursor.y);

if !in_x || !in_y {
let dpi = util::hwnd_dpi(hwnd);
let scale_factor = util::dpi_to_scale_factor(dpi);

KillTimer(hwnd, WM_USER_LEAVE_TIMER_ID as _);

TrayIconEvent::send(TrayIconEvent::Leave {
id: userdata.id.clone(),
rect: Rect::from_win32(rect, scale_factor),
rect: rect.into(),
position,
});
}
Expand Down Expand Up @@ -562,16 +552,14 @@ fn get_tray_rect(id: u32, hwnd: HWND) -> Option<RECT> {
}
}

impl Rect {
fn from_win32(rect: RECT, scale_factor: f64) -> Self {
impl From<RECT> for Rect {
fn from(rect: RECT) -> Self {
Self {
position: crate::dpi::LogicalPosition::new(rect.left, rect.top)
.to_physical(scale_factor),
size: crate::dpi::LogicalSize::new(
rect.right.saturating_sub(rect.left),
rect.bottom.saturating_sub(rect.top),
)
.to_physical(scale_factor),
position: crate::dpi::PhysicalPosition::new(rect.left.into(), rect.top.into()),
size: crate::dpi::PhysicalSize::new(
rect.right.saturating_sub(rect.left) as u32,
rect.bottom.saturating_sub(rect.top) as u32,
),
}
}
}
92 changes: 3 additions & 89 deletions src/platform_impl/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,9 @@

use std::ops::{Deref, DerefMut};

use once_cell::sync::Lazy;
use windows_sys::{
core::HRESULT,
Win32::{
Foundation::{FARPROC, HWND, S_OK},
Graphics::Gdi::{
GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
},
System::LibraryLoader::{GetProcAddress, LoadLibraryW},
UI::{
HiDpi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE},
WindowsAndMessaging::{IsProcessDPIAware, ACCEL, WINDOW_LONG_PTR_INDEX},
},
},
use windows_sys::Win32::{
Foundation::HWND,
UI::WindowsAndMessaging::{ACCEL, WINDOW_LONG_PTR_INDEX},
};

pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
Expand Down Expand Up @@ -72,81 +61,6 @@ pub fn get_instance_handle() -> windows_sys::Win32::Foundation::HMODULE {
unsafe { &__ImageBase as *const _ as _ }
}

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(library.as_ptr()) };
if module == 0 {
return None;
}

unsafe { GetProcAddress(module, function.as_ptr()) }
}

macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::platform_impl::platform::util::get_function_impl(
$lib,
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
};
}

pub type GetDpiForMonitor = unsafe extern "system" fn(
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut u32,
dpi_y: *mut u32,
) -> HRESULT;

static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));

pub const BASE_DPI: u32 = 96;
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}

#[allow(non_snake_case)]
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor == 0 {
return BASE_DPI;
}

let mut dpi_x = 0;
let mut dpi_y = 0;
#[allow(clippy::unnecessary_cast)]
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
} else {
let hdc = GetDC(hwnd);
if hdc == 0 {
return BASE_DPI;
}

// We are on Vista or later.
if IsProcessDPIAware() == 1 {
// If the process is DPI aware, then scaling must be handled by the application using
// this DPI value.
GetDeviceCaps(hdc, LOGPIXELSX as _) 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.
BASE_DPI
}
}
}

#[inline(always)]
pub unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
#[cfg(target_pointer_width = "64")]
Expand Down
Loading