winapi_easy/
ui.rs

1//! UI functionality.
2
3use std::sync::Mutex;
4use std::{
5    io,
6    ptr,
7};
8
9use window::WindowHandle;
10use windows::Win32::Foundation::{
11    POINT,
12    RECT,
13};
14use windows::Win32::Graphics::Gdi::{
15    CombineRgn,
16    CreateRectRgn,
17    GDI_REGION_TYPE,
18    HRGN,
19    RGN_COMBINE_MODE,
20    RGN_COPY,
21    RGN_DIFF,
22    RGN_ERROR,
23};
24use windows::Win32::System::Console::{
25    AllocConsole,
26    FreeConsole,
27};
28use windows::Win32::System::Shutdown::LockWorkStation;
29use windows::Win32::UI::HiDpi::{
30    DPI_AWARENESS_CONTEXT,
31    SetProcessDpiAwarenessContext,
32    SetThreadDpiAwarenessContext,
33};
34pub use windows::Win32::UI::HiDpi::{
35    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
36    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
37    DPI_AWARENESS_CONTEXT_SYSTEM_AWARE,
38    DPI_AWARENESS_CONTEXT_UNAWARE,
39    DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED,
40};
41use windows::Win32::UI::Magnification::{
42    MagInitialize,
43    MagSetFullscreenTransform,
44    MagShowSystemCursor,
45};
46use windows::Win32::UI::WindowsAndMessaging::{
47    ClipCursor,
48    GetCursorPos,
49    GetSystemMetrics,
50    SM_CXVIRTUALSCREEN,
51    SM_CYVIRTUALSCREEN,
52    SM_XVIRTUALSCREEN,
53    SM_YVIRTUALSCREEN,
54    SetCursorPos,
55};
56use windows::core::{
57    BOOL,
58    Free,
59};
60
61use crate::internal::{
62    ResultExt,
63    ReturnValue,
64};
65
66pub mod desktop;
67pub mod menu;
68pub mod message_box;
69pub mod messaging;
70pub mod resource;
71pub mod taskbar;
72pub mod window;
73
74/// DPI-scaled virtual coordinates.
75pub type Point = POINT;
76/// DPI-scaled virtual coordinates of a rectangle.
77pub type Rectangle = RECT;
78
79trait RectTransform {
80    #[expect(dead_code)]
81    fn as_point_array(&self) -> &[POINT];
82    fn as_point_array_mut(&mut self) -> &mut [POINT];
83}
84impl RectTransform for RECT {
85    fn as_point_array(&self) -> &[POINT] {
86        let data = ptr::from_ref(self).cast::<POINT>();
87        unsafe { std::slice::from_raw_parts(data, 2) }
88    }
89
90    fn as_point_array_mut(&mut self) -> &mut [POINT] {
91        let data = ptr::from_mut(self).cast::<POINT>();
92        unsafe { std::slice::from_raw_parts_mut(data, 2) }
93    }
94}
95
96impl ReturnValue for GDI_REGION_TYPE {
97    const NULL_VALUE: Self = RGN_ERROR;
98}
99
100impl ReturnValue for HRGN {
101    const NULL_VALUE: Self = HRGN(ptr::null_mut());
102}
103
104/// A (non-null) handle to a region.
105#[derive(Eq, PartialEq, Debug)]
106pub struct Region {
107    raw_handle: HRGN,
108    forget_handle: bool,
109}
110
111impl Region {
112    pub fn from_rect(rect: Rectangle) -> io::Result<Self> {
113        let raw_handle = unsafe { CreateRectRgn(rect.left, rect.top, rect.right, rect.bottom) }
114            .if_null_to_error(|| io::ErrorKind::Other.into())?;
115        Ok(Self::from_non_null(raw_handle))
116    }
117
118    pub(crate) fn from_non_null(handle: HRGN) -> Self {
119        Self {
120            raw_handle: handle,
121            forget_handle: false,
122        }
123    }
124
125    pub fn duplicate(&self) -> io::Result<Self> {
126        self.combine(None, RGN_COPY)
127    }
128
129    pub fn and_not_in(&self, other: &Region) -> io::Result<Self> {
130        self.combine(Some(other), RGN_DIFF)
131    }
132
133    fn combine(&self, other: Option<&Region>, mode: RGN_COMBINE_MODE) -> io::Result<Self> {
134        let dest = Self::from_rect(Default::default())?;
135        unsafe {
136            CombineRgn(
137                Some(dest.raw_handle),
138                Some(self.raw_handle),
139                other.map(|x| x.raw_handle),
140                mode,
141            )
142            .if_null_get_last_error_else_drop()?;
143        }
144        Ok(dest)
145    }
146
147    fn into_raw(mut self) -> HRGN {
148        self.forget_handle = true;
149        self.raw_handle
150    }
151}
152
153impl Drop for Region {
154    fn drop(&mut self) {
155        if !self.forget_handle {
156            unsafe {
157                self.raw_handle.free();
158            }
159        }
160    }
161}
162
163impl From<Region> for HRGN {
164    fn from(value: Region) -> Self {
165        value.into_raw()
166    }
167}
168
169impl From<&Region> for HRGN {
170    fn from(value: &Region) -> Self {
171        value.raw_handle
172    }
173}
174
175#[derive(Debug)]
176#[must_use]
177pub struct CursorConfinement(Rectangle);
178
179impl CursorConfinement {
180    /// Globally confines the cursor to a rectangular area on the screen.
181    ///
182    /// The confinement will be automatically released when [`CursorConfinement`] is dropped.
183    pub fn new(bounding_area: Rectangle) -> io::Result<Self> {
184        Self::apply(bounding_area)?;
185        Ok(Self(bounding_area))
186    }
187
188    /// Reapply the corsor clipping.
189    ///
190    /// This can be necessary since some operations automatically unclip the cursor.
191    pub fn reapply(&self) -> io::Result<()> {
192        Self::apply(self.0)
193    }
194
195    fn apply(bounding_area: Rectangle) -> io::Result<()> {
196        unsafe {
197            ClipCursor(Some(&raw const bounding_area))?;
198        }
199        Ok(())
200    }
201
202    pub fn remove() -> io::Result<()> {
203        unsafe {
204            ClipCursor(None)?;
205        }
206        Ok(())
207    }
208}
209
210impl Drop for CursorConfinement {
211    fn drop(&mut self) {
212        Self::remove().unwrap_or_default_and_print_error();
213    }
214}
215
216#[derive(Debug)]
217#[must_use]
218pub struct UnmagnifiedCursorConcealment(());
219
220impl UnmagnifiedCursorConcealment {
221    /// Globally hides the unmagnified system cursor.
222    ///
223    /// The cursor will be automatically visible again when [`UnmagnifiedCursorConcealment`] is dropped.
224    pub fn new() -> io::Result<Self> {
225        init_magnifier()?;
226        unsafe {
227            MagShowSystemCursor(false).if_null_get_last_error_else_drop()?;
228        }
229        Ok(Self(()))
230    }
231
232    pub fn remove() -> io::Result<()> {
233        unsafe {
234            MagShowSystemCursor(true).if_null_get_last_error_else_drop()?;
235        }
236        Ok(())
237    }
238}
239
240impl Drop for UnmagnifiedCursorConcealment {
241    fn drop(&mut self) {
242        Self::remove().unwrap_or_default_and_print_error();
243    }
244}
245
246pub fn get_cursor_pos() -> io::Result<Point> {
247    let mut point = Point::default();
248    unsafe { GetCursorPos(&raw mut point)? }
249    Ok(point)
250}
251
252pub fn set_cursor_pos(coords: Point) -> io::Result<()> {
253    unsafe { SetCursorPos(coords.x, coords.y)? }
254    Ok(())
255}
256
257pub fn get_virtual_screen_rect() -> Rectangle {
258    let left = unsafe { GetSystemMetrics(SM_XVIRTUALSCREEN) };
259    let top = unsafe { GetSystemMetrics(SM_YVIRTUALSCREEN) };
260    let width = unsafe { GetSystemMetrics(SM_CXVIRTUALSCREEN) };
261    let height = unsafe { GetSystemMetrics(SM_CYVIRTUALSCREEN) };
262    Rectangle {
263        left,
264        top,
265        right: left + width,
266        bottom: top + height,
267    }
268}
269
270fn init_magnifier() -> io::Result<()> {
271    static MAGNIFIER_INITIALIZED: Mutex<bool> = const { Mutex::new(false) };
272
273    let mut initialized = MAGNIFIER_INITIALIZED.lock().unwrap();
274    if *initialized {
275        Ok(())
276    } else {
277        let result = unsafe { MagInitialize().if_null_get_last_error_else_drop() };
278        *initialized = true;
279        result
280    }
281}
282
283pub fn set_fullscreen_magnification(mag_factor: f32, offset: Point) -> io::Result<()> {
284    init_magnifier()?;
285    unsafe {
286        MagSetFullscreenTransform(mag_factor, offset.x, offset.y).if_null_get_last_error_else_drop()
287    }
288}
289
290pub fn remove_fullscreen_magnification() -> io::Result<()> {
291    set_fullscreen_magnification(1.0, Point { x: 0, y: 0 })
292}
293
294pub fn set_fullscreen_magnification_use_bitmap_smoothing(use_smoothing: bool) -> io::Result<()> {
295    #[link(
296        name = "magnification.dll",
297        kind = "raw-dylib",
298        modifiers = "+verbatim"
299    )]
300    unsafe extern "system" {
301        fn MagSetFullscreenUseBitmapSmoothing(use_smoothing: BOOL) -> BOOL;
302    }
303
304    init_magnifier()?;
305    unsafe {
306        MagSetFullscreenUseBitmapSmoothing(use_smoothing.into()).if_null_get_last_error_else_drop()
307    }
308}
309
310pub fn set_process_dpi_awareness_context(context: DPI_AWARENESS_CONTEXT) -> io::Result<()> {
311    unsafe {
312        SetProcessDpiAwarenessContext(context)?;
313    }
314    Ok(())
315}
316
317pub fn set_thread_dpi_awareness_context(context: DPI_AWARENESS_CONTEXT) -> io::Result<()> {
318    unsafe {
319        SetThreadDpiAwarenessContext(context)
320            .0
321            .if_null_get_last_error_else_drop()?;
322    }
323    Ok(())
324}
325
326/// Creates a console window for the current process if there is none.
327pub fn allocate_console() -> io::Result<()> {
328    unsafe {
329        AllocConsole()?;
330    }
331    Ok(())
332}
333
334/// Detaches the current process from its console.
335///
336/// If no other processes use the console, it will be destroyed.
337pub fn detach_console() -> io::Result<()> {
338    unsafe {
339        FreeConsole()?;
340    }
341    Ok(())
342}
343
344/// Locks the computer, same as the user action.
345pub fn lock_workstation() -> io::Result<()> {
346    // Because the function executes asynchronously, a nonzero return value indicates that the operation has been initiated.
347    // It does not indicate whether the workstation has been successfully locked.
348    unsafe { LockWorkStation()? };
349    Ok(())
350}