muda 0.20.0

Menu Utilities for Desktop Applications
Documentation
// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

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

// Copied from the `windows-core` crate. Remove this compatibility shim once `Owned` and `Free`
// are available from the `windows_sys::core` module.
/// A handle that knows how to release its owned native resource.
pub(super) trait Free {
    /// Releases the handle.
    ///
    /// # Safety
    ///
    /// The handle must be owned by the caller and valid to release.
    unsafe fn free(&mut self);
}

/// An owned native handle that releases itself when dropped.
#[repr(transparent)]
pub(super) struct Owned<T: Free>(T);

impl<T: Free> Owned<T> {
    /// Takes ownership of a native handle.
    ///
    /// # Safety
    ///
    /// The handle must be uniquely owned and valid to release with its [`Free`] implementation.
    pub(super) unsafe fn new(handle: T) -> Self {
        Self(handle)
    }
}

impl<T: Free> Drop for Owned<T> {
    fn drop(&mut self) {
        unsafe { self.0.free() };
    }
}

impl<T: Free> std::ops::Deref for Owned<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: Free> std::ops::DerefMut for Owned<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Free for HBITMAP {
    unsafe fn free(&mut self) {
        if !self.is_null() {
            DeleteObject(*self);
        }
    }
}

pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
    std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
        .chain(std::iter::once(0))
        .collect()
}

#[allow(non_snake_case)]
pub fn LOWORD(dword: u32) -> u16 {
    (dword & 0xFFFF) as u16
}

pub fn decode_wide(w_str: *mut u16) -> String {
    let len = unsafe { windows_sys::Win32::Globalization::lstrlenW(w_str) } as usize;
    let w_str_slice = unsafe { std::slice::from_raw_parts(w_str, len) };
    String::from_utf16_lossy(w_str_slice)
}

// taken from winit's code base
// https://github.com/rust-windowing/winit/blob/ee88e38f13fbc86a7aafae1d17ad3cd4a1e761df/src/platform_impl/windows/util.rs#L138
pub fn get_instance_handle() -> windows_sys::Win32::Foundation::HMODULE {
    // Gets the instance handle by taking the address of the
    // pseudo-variable created by the microsoft linker:
    // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483

    // This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
    // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance

    extern "C" {
        static __ImageBase: windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
    }

    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.is_null() {
        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 GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
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_WINDOW: Lazy<Option<GetDpiForWindow>> =
    Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
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(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
        // We are on Windows 10 Anniversary Update (1607) or later.
        match GetDpiForWindow(hwnd) {
            0 => BASE_DPI, // 0 is returned if hwnd is invalid
            #[allow(clippy::unnecessary_cast)]
            dpi => dpi as u32,
        }
    } 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_null() {
            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.is_null() {
            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
        }
    }
}

pub unsafe fn cast_mut<'a, T>(data: usize) -> &'a mut T {
    unsafe { &mut *(data as *mut T) }
}