winit/platform_impl/windows/
util.rs

1use std::ffi::{c_void, OsStr, OsString};
2use std::iter::once;
3use std::ops::BitAnd;
4use std::os::windows::prelude::{OsStrExt, OsStringExt};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::{io, mem, ptr};
7
8use crate::utils::Lazy;
9use windows_sys::core::{HRESULT, PCWSTR};
10use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, POINT, RECT};
11use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR};
12use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
13use windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
14use windows_sys::Win32::UI::HiDpi::{
15    DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
16};
17use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
18use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_PEN_INFO, POINTER_TOUCH_INFO};
19use windows_sys::Win32::UI::WindowsAndMessaging::{
20    ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement,
21    GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP,
22    IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT,
23    SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE,
24    WINDOWPLACEMENT,
25};
26
27use crate::window::CursorIcon;
28
29pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
30    string.as_ref().encode_wide().chain(once(0)).collect()
31}
32
33pub fn decode_wide(mut wide_c_string: &[u16]) -> OsString {
34    if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) {
35        wide_c_string = &wide_c_string[..null_pos];
36    }
37
38    OsString::from_wide(wide_c_string)
39}
40
41pub fn has_flag<T>(bitset: T, flag: T) -> bool
42where
43    T: Copy + PartialEq + BitAnd<T, Output = T>,
44{
45    bitset & flag == flag
46}
47
48pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> {
49    if result != false.into() {
50        Ok(())
51    } else {
52        Err(io::Error::last_os_error())
53    }
54}
55
56pub enum WindowArea {
57    Outer,
58    Inner,
59}
60
61impl WindowArea {
62    pub fn get_rect(self, hwnd: HWND) -> Result<RECT, io::Error> {
63        let mut rect = unsafe { mem::zeroed() };
64
65        match self {
66            WindowArea::Outer => {
67                win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?;
68            },
69            WindowArea::Inner => unsafe {
70                let mut top_left = mem::zeroed();
71
72                win_to_err(ClientToScreen(hwnd, &mut top_left))?;
73                win_to_err(GetClientRect(hwnd, &mut rect))?;
74                rect.left += top_left.x;
75                rect.top += top_left.y;
76                rect.right += top_left.x;
77                rect.bottom += top_left.y;
78            },
79        }
80
81        Ok(rect)
82    }
83}
84
85pub fn is_maximized(window: HWND) -> bool {
86    unsafe {
87        let mut placement: WINDOWPLACEMENT = mem::zeroed();
88        placement.length = mem::size_of::<WINDOWPLACEMENT>() as u32;
89        GetWindowPlacement(window, &mut placement);
90        placement.showCmd == SW_MAXIMIZE as u32
91    }
92}
93
94pub fn set_cursor_hidden(hidden: bool) {
95    static HIDDEN: AtomicBool = AtomicBool::new(false);
96    let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
97    if changed {
98        unsafe { ShowCursor(BOOL::from(!hidden)) };
99    }
100}
101
102pub fn get_cursor_position() -> Result<POINT, io::Error> {
103    unsafe {
104        let mut point: POINT = mem::zeroed();
105        win_to_err(GetCursorPos(&mut point)).map(|_| point)
106    }
107}
108
109pub fn get_cursor_clip() -> Result<RECT, io::Error> {
110    unsafe {
111        let mut rect: RECT = mem::zeroed();
112        win_to_err(GetClipCursor(&mut rect)).map(|_| rect)
113    }
114}
115
116/// Sets the cursor's clip rect.
117///
118/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
119pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
120    unsafe {
121        let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
122        win_to_err(ClipCursor(rect_ptr))
123    }
124}
125
126pub fn get_desktop_rect() -> RECT {
127    unsafe {
128        let left = GetSystemMetrics(SM_XVIRTUALSCREEN);
129        let top = GetSystemMetrics(SM_YVIRTUALSCREEN);
130        RECT {
131            left,
132            top,
133            right: left + GetSystemMetrics(SM_CXVIRTUALSCREEN),
134            bottom: top + GetSystemMetrics(SM_CYVIRTUALSCREEN),
135        }
136    }
137}
138
139pub fn is_focused(window: HWND) -> bool {
140    window == unsafe { GetActiveWindow() }
141}
142
143pub fn is_minimized(window: HWND) -> bool {
144    unsafe { IsIconic(window) != false.into() }
145}
146
147pub fn get_instance_handle() -> HMODULE {
148    // Gets the instance handle by taking the address of the
149    // pseudo-variable created by the microsoft linker:
150    // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
151
152    // This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
153    // https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance
154
155    extern "C" {
156        static __ImageBase: IMAGE_DOS_HEADER;
157    }
158
159    unsafe { &__ImageBase as *const _ as _ }
160}
161
162pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
163    match cursor {
164        CursorIcon::Default => IDC_ARROW,
165        CursorIcon::Pointer => IDC_HAND,
166        CursorIcon::Crosshair => IDC_CROSS,
167        CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM,
168        CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO,
169        CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => {
170            IDC_SIZEALL
171        },
172        CursorIcon::EResize
173        | CursorIcon::WResize
174        | CursorIcon::EwResize
175        | CursorIcon::ColResize => IDC_SIZEWE,
176        CursorIcon::NResize
177        | CursorIcon::SResize
178        | CursorIcon::NsResize
179        | CursorIcon::RowResize => IDC_SIZENS,
180        CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW,
181        CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE,
182        CursorIcon::Wait => IDC_WAIT,
183        CursorIcon::Progress => IDC_APPSTARTING,
184        CursorIcon::Help => IDC_HELP,
185        _ => IDC_ARROW, // use arrow for the missing cases.
186    }
187}
188
189// Helper function to dynamically load function pointer as some functions
190// may not be available on all Windows platforms supported by winit.
191//
192// `library` and `function` must be zero-terminated.
193pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
194    assert_eq!(library.chars().last(), Some('\0'));
195    assert_eq!(function.chars().last(), Some('\0'));
196
197    // Library names we will use are ASCII so we can use the A version to avoid string conversion.
198    let module = unsafe { LoadLibraryA(library.as_ptr()) };
199    if module == 0 {
200        return None;
201    }
202
203    unsafe { GetProcAddress(module, function.as_ptr()) }.map(|function_ptr| function_ptr as _)
204}
205
206macro_rules! get_function {
207    ($lib:expr, $func:ident) => {
208        crate::platform_impl::platform::util::get_function_impl(
209            concat!($lib, '\0'),
210            concat!(stringify!($func), '\0'),
211        )
212        .map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
213    };
214}
215
216pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL;
217pub type SetProcessDpiAwareness =
218    unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT;
219pub type SetProcessDpiAwarenessContext =
220    unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL;
221pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
222pub type GetDpiForMonitor = unsafe extern "system" fn(
223    hmonitor: HMONITOR,
224    dpi_type: MONITOR_DPI_TYPE,
225    dpi_x: *mut u32,
226    dpi_y: *mut u32,
227) -> HRESULT;
228pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
229pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
230    rect: *mut RECT,
231    dwStyle: u32,
232    bMenu: BOOL,
233    dwExStyle: u32,
234    dpi: u32,
235) -> BOOL;
236
237pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
238    pointerId: u32,
239    entriesCount: *mut u32,
240    pointerCount: *mut u32,
241    pointerInfo: *mut POINTER_INFO,
242) -> BOOL;
243
244pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: u32) -> BOOL;
245pub type GetPointerDeviceRects = unsafe extern "system" fn(
246    device: HANDLE,
247    pointerDeviceRect: *mut RECT,
248    displayRect: *mut RECT,
249) -> BOOL;
250
251pub type GetPointerTouchInfo =
252    unsafe extern "system" fn(pointerId: u32, touchInfo: *mut POINTER_TOUCH_INFO) -> BOOL;
253
254pub type GetPointerPenInfo =
255    unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL;
256
257pub(crate) static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
258    Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
259pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy<Option<AdjustWindowRectExForDpi>> =
260    Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi));
261pub(crate) static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
262    Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));
263pub(crate) static ENABLE_NON_CLIENT_DPI_SCALING: Lazy<Option<EnableNonClientDpiScaling>> =
264    Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling));
265pub(crate) static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy<Option<SetProcessDpiAwarenessContext>> =
266    Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext));
267pub(crate) static SET_PROCESS_DPI_AWARENESS: Lazy<Option<SetProcessDpiAwareness>> =
268    Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
269pub(crate) static SET_PROCESS_DPI_AWARE: Lazy<Option<SetProcessDPIAware>> =
270    Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware));
271pub(crate) static GET_POINTER_FRAME_INFO_HISTORY: Lazy<Option<GetPointerFrameInfoHistory>> =
272    Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
273pub(crate) static SKIP_POINTER_FRAME_MESSAGES: Lazy<Option<SkipPointerFrameMessages>> =
274    Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
275pub(crate) static GET_POINTER_DEVICE_RECTS: Lazy<Option<GetPointerDeviceRects>> =
276    Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects));
277pub(crate) static GET_POINTER_TOUCH_INFO: Lazy<Option<GetPointerTouchInfo>> =
278    Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo));
279pub(crate) static GET_POINTER_PEN_INFO: Lazy<Option<GetPointerPenInfo>> =
280    Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo));