winapi_easy/
ui.rs

1//! UI components: Windows, taskbar.
2
3use std::error::Error;
4use std::fmt::{
5    Display,
6    Formatter,
7};
8use std::marker::PhantomData;
9use std::mem;
10use std::ptr::NonNull;
11use std::{
12    io,
13    vec,
14};
15
16use num_enum::{
17    IntoPrimitive,
18    TryFromPrimitive,
19};
20use windows::core::{
21    GUID,
22    PCWSTR,
23};
24use windows::Win32::Foundation::{
25    GetLastError,
26    SetLastError,
27    BOOL,
28    HWND,
29    LPARAM,
30    NO_ERROR,
31    POINT,
32    RECT,
33    WPARAM,
34};
35use windows::Win32::System::Console::{
36    AllocConsole,
37    FreeConsole,
38    GetConsoleWindow,
39};
40use windows::Win32::System::Shutdown::LockWorkStation;
41use windows::Win32::UI::Input::KeyboardAndMouse::SetActiveWindow;
42use windows::Win32::UI::Shell::{
43    ITaskbarList3,
44    Shell_NotifyIconW,
45    TaskbarList,
46    NIF_GUID,
47    NIF_ICON,
48    NIF_INFO,
49    NIF_MESSAGE,
50    NIF_SHOWTIP,
51    NIF_STATE,
52    NIF_TIP,
53    NIM_ADD,
54    NIM_DELETE,
55    NIM_MODIFY,
56    NIM_SETVERSION,
57    NIS_HIDDEN,
58    NOTIFYICONDATAW,
59    NOTIFYICON_VERSION_4,
60    NOTIFY_ICON_INFOTIP_FLAGS,
61    NOTIFY_ICON_STATE,
62    TBPFLAG,
63};
64use windows::Win32::UI::Shell::{
65    NIIF_ERROR,
66    NIIF_INFO,
67    NIIF_NONE,
68    NIIF_WARNING,
69    TBPF_ERROR,
70    TBPF_INDETERMINATE,
71    TBPF_NOPROGRESS,
72    TBPF_NORMAL,
73    TBPF_PAUSED,
74};
75use windows::Win32::UI::WindowsAndMessaging::{
76    CreateWindowExW,
77    DestroyWindow,
78    EnumWindows,
79    FlashWindowEx,
80    GetClassNameW,
81    GetDesktopWindow,
82    GetForegroundWindow,
83    GetWindowLongPtrW,
84    GetWindowPlacement,
85    GetWindowTextLengthW,
86    GetWindowTextW,
87    IsWindow,
88    IsWindowVisible,
89    RegisterClassExW,
90    SendMessageW,
91    SetForegroundWindow,
92    SetWindowLongPtrW,
93    SetWindowPlacement,
94    SetWindowTextW,
95    ShowWindow,
96    UnregisterClassW,
97    CW_USEDEFAULT,
98    FLASHWINFO,
99    FLASHWINFO_FLAGS,
100    FLASHW_ALL,
101    FLASHW_CAPTION,
102    FLASHW_STOP,
103    FLASHW_TIMER,
104    FLASHW_TIMERNOFG,
105    FLASHW_TRAY,
106    GWLP_USERDATA,
107    HICON,
108    SC_CLOSE,
109    SC_MAXIMIZE,
110    SC_MINIMIZE,
111    SC_MONITORPOWER,
112    SC_RESTORE,
113    SHOW_WINDOW_CMD,
114    SW_HIDE,
115    SW_MAXIMIZE,
116    SW_MINIMIZE,
117    SW_RESTORE,
118    SW_SHOW,
119    SW_SHOWMINIMIZED,
120    SW_SHOWMINNOACTIVE,
121    SW_SHOWNA,
122    SW_SHOWNOACTIVATE,
123    SW_SHOWNORMAL,
124    WINDOWPLACEMENT,
125    WM_SYSCOMMAND,
126    WNDCLASSEXW,
127    WPF_SETMINPOSITION,
128    WS_OVERLAPPEDWINDOW,
129};
130
131use crate::com::ComInterfaceExt;
132use crate::internal::{
133    custom_err_with_code,
134    with_sync_closure_to_callback2,
135    ReturnValue,
136};
137#[cfg(feature = "process")]
138use crate::process::{
139    ProcessId,
140    ThreadId,
141};
142use crate::string::{
143    to_wide_chars_iter,
144    FromWideString,
145    ToWideString,
146};
147use crate::ui::messaging::{
148    generic_window_proc,
149    WindowMessageListener,
150};
151use crate::ui::resource::{
152    Brush,
153    BuiltinColor,
154    BuiltinCursor,
155    BuiltinIcon,
156    Cursor,
157    Icon,
158};
159
160pub mod menu;
161pub mod message_box;
162pub mod messaging;
163pub mod resource;
164
165/// A (non-null) handle to a window.
166///
167/// # Multithreading
168///
169/// This handle is not [`Send`] and [`Sync`] because if the window was not created by this thread,
170/// then it is not guaranteed that the handle continues pointing to the same window because the underlying handles
171/// can get invalid or even recycled.
172///
173/// # Mutability
174///
175/// Even though various functions on this type are mutating, they all take non-mut references since
176/// it would be too hard to guarantee exclusive references when window messages are involved. The problem
177/// in that case is that the windows API will call back into Rust code and that code would then need
178/// exclusive references, which would at least make the API rather cumbersome. If an elegant solution
179/// to this problem is found, this API may change.
180#[derive(Eq, PartialEq, Debug)]
181pub struct WindowHandle {
182    raw_handle: HWND,
183    marker: PhantomData<*mut ()>,
184}
185
186impl WindowHandle {
187    /// Returns the console window associated with the current process, if there is one.
188    pub fn get_console_window() -> Option<Self> {
189        let handle = unsafe { GetConsoleWindow() };
190        Self::from_maybe_null(handle)
191    }
192
193    /// Returns the current foreground window, if any.
194    pub fn get_foreground_window() -> Option<Self> {
195        let handle = unsafe { GetForegroundWindow() };
196        Self::from_maybe_null(handle)
197    }
198
199    /// Returns the 'desktop' window.
200    pub fn get_desktop_window() -> io::Result<Self> {
201        let handle = unsafe { GetDesktopWindow() };
202        handle
203            .if_null_to_error(|| io::ErrorKind::Other.into())
204            .map(Self::from_non_null)
205    }
206
207    /// Returns all top-level windows of desktop apps.
208    pub fn get_toplevel_windows() -> io::Result<Vec<Self>> {
209        let mut result: Vec<WindowHandle> = Vec::new();
210        let mut callback = |handle: HWND, _app_value: LPARAM| -> BOOL {
211            let window_handle =
212                Self::from_maybe_null(handle).expect("Window handle should not be null");
213            result.push(window_handle);
214            true.into()
215        };
216        let acceptor = |raw_callback| unsafe { EnumWindows(Some(raw_callback), LPARAM::default()) };
217        with_sync_closure_to_callback2(&mut callback, acceptor)?;
218        Ok(result)
219    }
220
221    pub(crate) fn from_non_null(handle: HWND) -> Self {
222        Self {
223            raw_handle: handle,
224            marker: PhantomData,
225        }
226    }
227
228    pub(crate) fn from_maybe_null(handle: HWND) -> Option<Self> {
229        if !handle.is_null() {
230            Some(Self {
231                raw_handle: handle,
232                marker: PhantomData,
233            })
234        } else {
235            None
236        }
237    }
238
239    /// Checks if the handle points to an existing window.
240    pub fn is_window(&self) -> bool {
241        let result = unsafe { IsWindow(self.raw_handle) };
242        result.as_bool()
243    }
244
245    pub fn is_visible(&self) -> bool {
246        let result = unsafe { IsWindowVisible(self.raw_handle) };
247        result.as_bool()
248    }
249
250    /// Returns the window caption text, converted to UTF-8 in a potentially lossy way.
251    pub fn get_caption_text(&self) -> String {
252        let required_length = unsafe { GetWindowTextLengthW(self.raw_handle) };
253        let required_length = if required_length <= 0 {
254            return String::new();
255        } else {
256            1 + required_length
257        };
258
259        let mut buffer: Vec<u16> = vec![0; required_length as usize];
260        let copied_chars = unsafe { GetWindowTextW(self.raw_handle, buffer.as_mut()) };
261        if copied_chars <= 0 {
262            return String::new();
263        }
264        // Normally unnecessary, but the text length can theoretically change between the 2 API calls
265        buffer.truncate(copied_chars as usize);
266        buffer.to_string_lossy()
267    }
268
269    /// Sets the window caption text.
270    pub fn set_caption_text(&self, text: &str) -> io::Result<()> {
271        let ret_val = unsafe {
272            SetWindowTextW(
273                self.raw_handle,
274                PCWSTR::from_raw(text.to_wide_string().as_ptr()),
275            )
276        };
277        ret_val?;
278        Ok(())
279    }
280
281    /// Brings the window to the foreground.
282    pub fn set_as_foreground(&self) -> io::Result<()> {
283        unsafe {
284            SetForegroundWindow(self.raw_handle).if_null_to_error_else_drop(|| {
285                io::Error::new(
286                    io::ErrorKind::PermissionDenied,
287                    "Cannot bring window to foreground",
288                )
289            })?;
290        }
291        Ok(())
292    }
293
294    /// Sets the window as the currently active (selected) window.
295    pub fn set_as_active(&self) -> io::Result<()> {
296        unsafe {
297            SetActiveWindow(self.raw_handle)?;
298        }
299        Ok(())
300    }
301
302    /// Changes the window show state.
303    pub fn set_show_state(&self, state: WindowShowState) -> io::Result<()> {
304        if self.is_window() {
305            unsafe {
306                let _ = ShowWindow(self.raw_handle, state.into());
307            }
308            Ok(())
309        } else {
310            Err(io::Error::new(
311                io::ErrorKind::NotFound,
312                "Cannot set show state because window does not exist",
313            ))
314        }
315    }
316
317    /// Returns the window's show state and positions.
318    pub fn get_placement(&self) -> io::Result<WindowPlacement> {
319        let mut raw_placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
320            length: mem::size_of::<WINDOWPLACEMENT>().try_into().unwrap(),
321            ..Default::default()
322        };
323        unsafe { GetWindowPlacement(self.raw_handle, &mut raw_placement)? };
324        Ok(WindowPlacement { raw_placement })
325    }
326
327    /// Sets the window's show state and positions.
328    pub fn set_placement(&self, placement: &WindowPlacement) -> io::Result<()> {
329        unsafe { SetWindowPlacement(self.raw_handle, &placement.raw_placement)? };
330        Ok(())
331    }
332
333    /// Returns the class name of the window's associated [`WindowClass`].
334    pub fn get_class_name(&self) -> io::Result<String> {
335        const BUFFER_SIZE: usize = WindowClass::MAX_WINDOW_CLASS_NAME_CHARS + 1;
336        let mut buffer: Vec<u16> = vec![0; BUFFER_SIZE];
337        let chars_copied = unsafe { GetClassNameW(self.raw_handle, buffer.as_mut()) };
338        chars_copied.if_null_get_last_error()?;
339        buffer.truncate(chars_copied as usize);
340        Ok(buffer.to_string_lossy())
341    }
342
343    /// Sends a command to the window, same as if one of the symbols in its top right were clicked.
344    pub fn send_command(&self, action: WindowCommand) -> io::Result<()> {
345        let result = unsafe {
346            SendMessageW(
347                self.raw_handle,
348                WM_SYSCOMMAND,
349                WPARAM(action.to_usize()),
350                LPARAM::default(),
351            )
352        };
353        result
354            .if_non_null_to_error(|| custom_err_with_code("Cannot perform window action", result.0))
355    }
356
357    /// Flashes the window using default flash settings.
358    ///
359    /// Same as [`Self::flash_custom`] using [`Default::default`] for all parameters.
360    #[inline(always)]
361    pub fn flash(&self) {
362        self.flash_custom(Default::default(), Default::default(), Default::default())
363    }
364
365    /// Flashes the window, allowing various customization parameters.
366    pub fn flash_custom(
367        &self,
368        element: FlashElement,
369        duration: FlashDuration,
370        frequency: FlashInterval,
371    ) {
372        let (count, flags) = match duration {
373            FlashDuration::Count(count) => (count, Default::default()),
374            FlashDuration::CountUntilForeground(count) => (count, FLASHW_TIMERNOFG),
375            FlashDuration::ContinuousUntilForeground => (0, FLASHW_TIMERNOFG),
376            FlashDuration::Continuous => (0, FLASHW_TIMER),
377        };
378        let flags = flags | element.to_flashwinfo_flags();
379        let raw_config = FLASHWINFO {
380            cbSize: mem::size_of::<FLASHWINFO>().try_into().unwrap(),
381            hwnd: self.into(),
382            dwFlags: flags,
383            uCount: count,
384            dwTimeout: match frequency {
385                FlashInterval::DefaultCursorBlinkInterval => 0,
386                FlashInterval::Milliseconds(ms) => ms,
387            },
388        };
389        unsafe {
390            let _ = FlashWindowEx(&raw_config);
391        };
392    }
393
394    /// Stops the window from flashing.
395    pub fn flash_stop(&self) {
396        let raw_config = FLASHWINFO {
397            cbSize: mem::size_of::<FLASHWINFO>().try_into().unwrap(),
398            hwnd: self.into(),
399            dwFlags: FLASHW_STOP,
400            ..Default::default()
401        };
402        unsafe {
403            let _ = FlashWindowEx(&raw_config);
404        };
405    }
406
407    /// Returns the thread ID that created this window.
408    #[cfg(feature = "process")]
409    #[inline(always)]
410    pub fn get_creator_thread_id(&self) -> ThreadId {
411        self.get_creator_thread_process_ids().0
412    }
413
414    /// Returns the process ID that created this window.
415    #[cfg(feature = "process")]
416    #[inline(always)]
417    pub fn get_creator_process_id(&self) -> ProcessId {
418        self.get_creator_thread_process_ids().1
419    }
420
421    #[cfg(feature = "process")]
422    fn get_creator_thread_process_ids(&self) -> (ThreadId, ProcessId) {
423        use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
424        let mut process_id: u32 = 0;
425        let thread_id = unsafe { GetWindowThreadProcessId(self.raw_handle, Some(&mut process_id)) };
426        (ThreadId(thread_id), ProcessId(process_id))
427    }
428
429    /// Returns all top-level (non-child) windows created by the thread.
430    #[cfg(feature = "process")]
431    pub fn get_nonchild_windows(thread_id: ThreadId) -> Vec<Self> {
432        use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
433        let mut result: Vec<WindowHandle> = Vec::new();
434        let mut callback = |handle: HWND, _app_value: LPARAM| -> BOOL {
435            let window_handle =
436                WindowHandle::from_maybe_null(handle).expect("Window handle should not be null");
437            result.push(window_handle);
438            true.into()
439        };
440        let acceptor = |raw_callback| {
441            let _ =
442                unsafe { EnumThreadWindows(thread_id.0, Some(raw_callback), LPARAM::default()) };
443        };
444        with_sync_closure_to_callback2(&mut callback, acceptor);
445        result
446    }
447
448    /// Turns the monitor on or off.
449    ///
450    /// Windows requires this command to be sent through a window, e.g. using
451    /// [`WindowHandle::get_foreground_window`].
452    pub fn set_monitor_power(&self, level: MonitorPower) -> io::Result<()> {
453        let result = unsafe {
454            SendMessageW(
455                self.raw_handle,
456                WM_SYSCOMMAND,
457                WPARAM(SC_MONITORPOWER.try_into().unwrap()),
458                LPARAM(level.into()),
459            )
460        };
461        result.if_non_null_to_error(|| {
462            custom_err_with_code("Cannot set monitor power using window", result.0)
463        })
464    }
465
466    pub(crate) unsafe fn get_user_data_ptr<T>(&self) -> Option<NonNull<T>> {
467        let ptr_value = GetWindowLongPtrW(self.raw_handle, GWLP_USERDATA);
468        NonNull::new(ptr_value as *mut T)
469    }
470
471    pub(crate) unsafe fn set_user_data_ptr<T>(&self, ptr: *const T) -> io::Result<()> {
472        SetLastError(NO_ERROR);
473        let ret_val = SetWindowLongPtrW(self.raw_handle, GWLP_USERDATA, ptr as isize);
474        if ret_val == 0 {
475            let err_val = GetLastError();
476            if err_val != NO_ERROR {
477                return Err(custom_err_with_code(
478                    "Cannot set window procedure",
479                    err_val.0,
480                ));
481            }
482        }
483        Ok(())
484    }
485}
486
487impl From<WindowHandle> for HWND {
488    /// Returns the underlying raw window handle used by [`windows`].
489    fn from(value: WindowHandle) -> Self {
490        value.raw_handle
491    }
492}
493
494impl From<&WindowHandle> for HWND {
495    /// Returns the underlying raw window handle used by [`windows`].
496    fn from(value: &WindowHandle) -> Self {
497        value.raw_handle
498    }
499}
500
501impl TryFrom<HWND> for WindowHandle {
502    type Error = TryFromHWNDError;
503
504    /// Returns a new window handle from a raw handle if it is non-null.
505    fn try_from(value: HWND) -> Result<Self, Self::Error> {
506        WindowHandle::from_maybe_null(value).ok_or(TryFromHWNDError(()))
507    }
508}
509
510#[derive(Copy, Clone, PartialEq, Eq, Debug)]
511pub struct TryFromHWNDError(pub(crate) ());
512
513impl Display for TryFromHWNDError {
514    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
515        write!(f, "HWND value must not be null")
516    }
517}
518
519impl Error for TryFromHWNDError {}
520
521/// Window class serving as a base for [`Window`].
522#[derive(Debug)]
523pub struct WindowClass<'res, WML> {
524    atom: u16,
525    icon_handle: HICON,
526    phantom: PhantomData<(WML, &'res ())>,
527}
528
529impl WindowClass<'_, ()> {
530    const MAX_WINDOW_CLASS_NAME_CHARS: usize = 256;
531}
532
533impl<'res, WML: WindowMessageListener> WindowClass<'res, WML> {
534    /// Registers a new class.
535    ///
536    /// This class can then be used to create instances of [`Window`].
537    ///
538    /// The class name will be generated from the given prefix by adding a random base64 encoded UUID
539    /// to ensure uniqueness. This means that the maximum length of the class name prefix is a little less
540    /// than the standard 256 characters for class names.
541    pub fn register_new<B, I, C>(
542        class_name_prefix: &str,
543        appearance: WindowClassAppearance<B, I, C>,
544    ) -> io::Result<Self>
545    where
546        B: Brush + 'res,
547        I: Icon + 'res,
548        C: Cursor + 'res,
549    {
550        use base64::engine::general_purpose::URL_SAFE_NO_PAD;
551        use base64::Engine;
552
553        let base64_uuid = URL_SAFE_NO_PAD.encode(uuid::Uuid::new_v4().as_bytes());
554        let class_name = class_name_prefix.to_string() + "_" + &base64_uuid;
555
556        let class_name_wide = class_name.to_wide_string();
557
558        let icon_handle = appearance
559            .icon
560            .map(|x| x.as_handle())
561            .unwrap_or(Ok(Default::default()))?;
562        // No need to reserve extra window memory if we only need a single pointer
563        let class_def = WNDCLASSEXW {
564            cbSize: mem::size_of::<WNDCLASSEXW>().try_into().unwrap(),
565            lpfnWndProc: Some(generic_window_proc::<WML>),
566            hIcon: icon_handle,
567            hCursor: appearance
568                .cursor
569                .map(|x| x.as_handle())
570                .unwrap_or(Ok(Default::default()))?,
571            hbrBackground: appearance
572                .background_brush
573                .map(|x| x.as_handle())
574                .unwrap_or(Ok(Default::default()))?,
575            lpszClassName: PCWSTR::from_raw(class_name_wide.as_ptr()),
576            ..Default::default()
577        };
578        let atom = unsafe { RegisterClassExW(&class_def).if_null_get_last_error()? };
579        Ok(WindowClass {
580            atom,
581            icon_handle,
582            phantom: PhantomData,
583        })
584    }
585}
586
587impl<WML> Drop for WindowClass<'_, WML> {
588    /// Unregisters the class on drop.
589    fn drop(&mut self) {
590        unsafe {
591            UnregisterClassW(PCWSTR(self.atom as *const u16), None).unwrap();
592        }
593    }
594}
595
596#[derive(Clone, Debug)]
597pub struct WindowClassAppearance<B, I, C> {
598    pub background_brush: Option<B>,
599    pub icon: Option<I>,
600    pub cursor: Option<C>,
601}
602
603impl WindowClassAppearance<BuiltinColor, BuiltinIcon, BuiltinCursor> {
604    pub fn empty() -> Self {
605        Self {
606            background_brush: None,
607            icon: None,
608            cursor: None,
609        }
610    }
611}
612
613impl Default for WindowClassAppearance<BuiltinColor, BuiltinIcon, BuiltinCursor> {
614    fn default() -> Self {
615        Self {
616            background_brush: Some(Default::default()),
617            icon: Some(Default::default()),
618            cursor: Some(Default::default()),
619        }
620    }
621}
622
623/// A window based on a [`WindowClass`].
624#[derive(Debug)]
625pub struct Window<'class, 'listener, WML> {
626    class: &'class WindowClass<'class, WML>,
627    handle: WindowHandle,
628    phantom: PhantomData<&'listener mut WML>,
629}
630
631impl<'class, 'listener, WML: WindowMessageListener> Window<'class, 'listener, WML> {
632    /// Creates a new window.
633    ///
634    /// User interaction with the window will result in messages sent to the [`WindowMessageListener`] provided here.
635    pub fn create_new(
636        class: &'class WindowClass<WML>,
637        listener: &'listener WML,
638        window_name: &str,
639    ) -> io::Result<Self> {
640        let h_wnd: HWND = unsafe {
641            CreateWindowExW(
642                Default::default(),
643                PCWSTR(class.atom as *const u16),
644                PCWSTR::from_raw(window_name.to_wide_string().as_ptr()),
645                WS_OVERLAPPEDWINDOW,
646                CW_USEDEFAULT,
647                0,
648                CW_USEDEFAULT,
649                0,
650                None,
651                None,
652                None,
653                None,
654            )?
655        };
656        let handle = WindowHandle::from_non_null(h_wnd);
657        unsafe {
658            handle.set_user_data_ptr(listener)?;
659        }
660        Ok(Window {
661            class,
662            handle,
663            phantom: PhantomData,
664        })
665    }
666
667    /// Changes the [`WindowMessageListener`].
668    ///
669    /// Doing this is likely only possible using a [`WindowMessageListener`] that doesn't contain any closures
670    /// since [`Window`] requires the listener to be of a specific type and closures in Rust each have
671    /// their own (new) type.
672    pub fn set_listener(&self, listener: &'listener WML) -> io::Result<()> {
673        unsafe { self.handle.set_user_data_ptr(listener) }
674    }
675
676    /// Adds a notification icon.
677    ///
678    /// The window's [`WindowMessageListener`] will receive messages when user interactions with the icon occur.
679    pub fn add_notification_icon<'a, NI: Icon + 'a>(
680        &'a self,
681        options: NotificationIconOptions<NI, &'a str>,
682    ) -> io::Result<NotificationIcon<'a, WML>> {
683        // For GUID handling maybe look at generating it from the executable path:
684        // https://stackoverflow.com/questions/7432319/notifyicondata-guid-problem
685        let chosen_icon_handle = if let Some(icon) = options.icon {
686            icon.as_handle()?
687        } else {
688            self.class.icon_handle
689        };
690        let call_data = get_notification_call_data(
691            &self.handle,
692            options.icon_id,
693            true,
694            Some(chosen_icon_handle),
695            options.tooltip_text,
696            Some(!options.visible),
697            None,
698        );
699        unsafe {
700            Shell_NotifyIconW(NIM_ADD, &call_data).if_null_to_error_else_drop(|| {
701                io::Error::new(io::ErrorKind::Other, "Cannot add notification icon")
702            })?;
703            Shell_NotifyIconW(NIM_SETVERSION, &call_data).if_null_to_error_else_drop(|| {
704                io::Error::new(io::ErrorKind::Other, "Cannot set notification version")
705            })?;
706        };
707        Ok(NotificationIcon {
708            id: options.icon_id,
709            window: self,
710        })
711    }
712}
713
714impl<WML> Drop for Window<'_, '_, WML> {
715    fn drop(&mut self) {
716        unsafe {
717            if self.handle.is_window() {
718                DestroyWindow(self.handle.raw_handle).unwrap();
719            }
720        }
721    }
722}
723
724impl<WML> AsRef<WindowHandle> for Window<'_, '_, WML> {
725    fn as_ref(&self) -> &WindowHandle {
726        &self.handle
727    }
728}
729
730impl<WML> AsMut<WindowHandle> for Window<'_, '_, WML> {
731    fn as_mut(&mut self) -> &mut WindowHandle {
732        &mut self.handle
733    }
734}
735
736/// Window show state such as 'minimized' or 'hidden'.
737///
738/// Changing this state for a window can be done with [`WindowHandle::set_show_state`].
739///
740/// [`WindowHandle::get_placement`] and [`WindowPlacement::get_show_state`] can be used to read the state.
741#[derive(IntoPrimitive, TryFromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
742#[repr(i32)]
743pub enum WindowShowState {
744    Hide = SW_HIDE.0,
745    Maximize = SW_MAXIMIZE.0,
746    Minimize = SW_MINIMIZE.0,
747    Restore = SW_RESTORE.0,
748    Show = SW_SHOW.0,
749    ShowMinimized = SW_SHOWMINIMIZED.0,
750    ShowMinNoActivate = SW_SHOWMINNOACTIVE.0,
751    ShowNoActivate = SW_SHOWNA.0,
752    ShowNormalNoActivate = SW_SHOWNOACTIVATE.0,
753    ShowNormal = SW_SHOWNORMAL.0,
754}
755
756impl From<WindowShowState> for SHOW_WINDOW_CMD {
757    fn from(value: WindowShowState) -> Self {
758        SHOW_WINDOW_CMD(value.into())
759    }
760}
761
762/// DPI-scaled virtual coordinates.
763pub type Point = POINT;
764/// DPI-scaled virtual coordinates of a rectangle.
765pub type Rectangle = RECT;
766
767/// Window show state plus positions.
768#[derive(Copy, Clone, Debug)]
769pub struct WindowPlacement {
770    raw_placement: WINDOWPLACEMENT,
771}
772
773impl WindowPlacement {
774    pub fn get_show_state(&self) -> Option<WindowShowState> {
775        i32::try_from(self.raw_placement.showCmd)
776            .ok()?
777            .try_into()
778            .ok()
779    }
780
781    pub fn set_show_state(&mut self, state: WindowShowState) {
782        self.raw_placement.showCmd = i32::from(state).try_into().unwrap();
783    }
784
785    pub fn get_minimized_position(&self) -> Point {
786        self.raw_placement.ptMinPosition
787    }
788
789    pub fn set_minimized_position(&mut self, coords: Point) {
790        self.raw_placement.ptMinPosition = coords;
791        self.raw_placement.flags |= WPF_SETMINPOSITION;
792    }
793
794    pub fn get_maximized_position(&self) -> Point {
795        self.raw_placement.ptMaxPosition
796    }
797
798    pub fn set_maximized_position(&mut self, coords: Point) {
799        self.raw_placement.ptMaxPosition = coords;
800    }
801
802    pub fn get_restored_position(&self) -> Rectangle {
803        self.raw_placement.rcNormalPosition
804    }
805
806    pub fn set_restored_position(&mut self, rectangle: Rectangle) {
807        self.raw_placement.rcNormalPosition = rectangle;
808    }
809}
810
811/// Window command corresponding to its buttons in the top right corner.
812#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
813#[non_exhaustive]
814#[repr(u32)]
815pub enum WindowCommand {
816    Close = SC_CLOSE,
817    Maximize = SC_MAXIMIZE,
818    Minimize = SC_MINIMIZE,
819    Restore = SC_RESTORE,
820}
821
822impl WindowCommand {
823    fn to_usize(self) -> usize {
824        usize::try_from(u32::from(self)).unwrap()
825    }
826}
827
828/// The target of the flash animation.
829#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
830#[repr(u32)]
831pub enum FlashElement {
832    Caption = FLASHW_CAPTION.0,
833    Taskbar = FLASHW_TRAY.0,
834    #[default]
835    CaptionPlusTaskbar = FLASHW_ALL.0,
836}
837
838impl FlashElement {
839    fn to_flashwinfo_flags(self) -> FLASHWINFO_FLAGS {
840        FLASHWINFO_FLAGS(u32::from(self))
841    }
842}
843
844/// The amount of times the window should be flashed.
845#[derive(Copy, Clone, Eq, PartialEq, Debug)]
846pub enum FlashDuration {
847    Count(u32),
848    CountUntilForeground(u32),
849    ContinuousUntilForeground,
850    Continuous,
851}
852
853impl Default for FlashDuration {
854    fn default() -> Self {
855        FlashDuration::CountUntilForeground(5)
856    }
857}
858
859/// The interval between flashes.
860#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
861pub enum FlashInterval {
862    #[default]
863    DefaultCursorBlinkInterval,
864    Milliseconds(u32),
865}
866
867/// Monitor power state.
868///
869/// Can be set using [`WindowHandle::set_monitor_power`].
870#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
871#[repr(isize)]
872pub enum MonitorPower {
873    #[default]
874    On = -1,
875    Low = 1,
876    Off = 2,
877}
878
879/// An icon in the Windows notification area.
880///
881/// This icon is always associated with a window and can be used in conjunction with [`menu::PopupMenu`].
882pub struct NotificationIcon<'a, WML> {
883    id: NotificationIconId,
884    window: &'a Window<'a, 'a, WML>,
885}
886
887impl<'a, WML> NotificationIcon<'a, WML> {
888    /// Sets the icon graphics.
889    pub fn set_icon(&mut self, icon: &'a impl Icon) -> io::Result<()> {
890        let call_data = get_notification_call_data(
891            &self.window.handle,
892            self.id,
893            false,
894            Some(icon.as_handle()?),
895            None,
896            None,
897            None,
898        );
899        unsafe {
900            Shell_NotifyIconW(NIM_MODIFY, &call_data).if_null_to_error_else_drop(|| {
901                io::Error::new(io::ErrorKind::Other, "Cannot set notification icon")
902            })?;
903        };
904        Ok(())
905    }
906
907    /// Allows showing or hiding the icon in the notification area.
908    pub fn set_icon_hidden_state(&mut self, hidden: bool) -> io::Result<()> {
909        let call_data = get_notification_call_data(
910            &self.window.handle,
911            self.id,
912            false,
913            None,
914            None,
915            Some(hidden),
916            None,
917        );
918        unsafe {
919            Shell_NotifyIconW(NIM_MODIFY, &call_data).if_null_to_error_else_drop(|| {
920                io::Error::new(
921                    io::ErrorKind::Other,
922                    "Cannot set notification icon hidden state",
923                )
924            })?;
925        };
926        Ok(())
927    }
928
929    /// Sets the tooltip text when hovering over the icon with the mouse.
930    pub fn set_tooltip_text(&mut self, text: &str) -> io::Result<()> {
931        let call_data = get_notification_call_data(
932            &self.window.handle,
933            self.id,
934            false,
935            None,
936            Some(text),
937            None,
938            None,
939        );
940        unsafe {
941            Shell_NotifyIconW(NIM_MODIFY, &call_data).if_null_to_error_else_drop(|| {
942                io::Error::new(
943                    io::ErrorKind::Other,
944                    "Cannot set notification icon tooltip text",
945                )
946            })?;
947        };
948        Ok(())
949    }
950
951    /// Triggers a balloon notification above the notification icon.
952    pub fn set_balloon_notification(
953        &mut self,
954        notification: Option<BalloonNotification>,
955    ) -> io::Result<()> {
956        let call_data = get_notification_call_data(
957            &self.window.handle,
958            self.id,
959            false,
960            None,
961            None,
962            None,
963            Some(notification),
964        );
965        unsafe {
966            Shell_NotifyIconW(NIM_MODIFY, &call_data).if_null_to_error_else_drop(|| {
967                io::Error::new(
968                    io::ErrorKind::Other,
969                    "Cannot set notification icon balloon text",
970                )
971            })?;
972        };
973        Ok(())
974    }
975}
976
977impl<WML> Drop for NotificationIcon<'_, WML> {
978    fn drop(&mut self) {
979        let call_data =
980            get_notification_call_data(&self.window.handle, self.id, false, None, None, None, None);
981        unsafe {
982            Shell_NotifyIconW(NIM_DELETE, &call_data)
983                .if_null_to_error_else_drop(|| {
984                    io::Error::new(io::ErrorKind::Other, "Cannot remove notification icon")
985                })
986                .unwrap();
987        }
988    }
989}
990
991fn get_notification_call_data(
992    window_handle: &WindowHandle,
993    icon_id: NotificationIconId,
994    set_callback_message: bool,
995    maybe_icon: Option<HICON>,
996    maybe_tooltip_str: Option<&str>,
997    icon_hidden_state: Option<bool>,
998    maybe_balloon_text: Option<Option<BalloonNotification>>,
999) -> NOTIFYICONDATAW {
1000    let mut icon_data = NOTIFYICONDATAW {
1001        cbSize: mem::size_of::<NOTIFYICONDATAW>()
1002            .try_into()
1003            .expect("NOTIFYICONDATAW size conversion failed"),
1004        hWnd: window_handle.into(),
1005        ..Default::default()
1006    };
1007    icon_data.Anonymous.uVersion = NOTIFYICON_VERSION_4;
1008    match icon_id {
1009        NotificationIconId::GUID(id) => {
1010            icon_data.guidItem = id;
1011            icon_data.uFlags |= NIF_GUID;
1012        }
1013        NotificationIconId::Simple(simple_id) => icon_data.uID = simple_id.into(),
1014    };
1015    if set_callback_message {
1016        icon_data.uCallbackMessage = messaging::RawMessage::ID_NOTIFICATION_ICON_MSG;
1017        icon_data.uFlags |= NIF_MESSAGE;
1018    }
1019    if let Some(icon) = maybe_icon {
1020        icon_data.hIcon = icon;
1021        icon_data.uFlags |= NIF_ICON;
1022    }
1023    if let Some(tooltip_str) = maybe_tooltip_str {
1024        let chars = to_wide_chars_iter(tooltip_str)
1025            .take(icon_data.szTip.len() - 1)
1026            .chain(std::iter::once(0))
1027            .enumerate();
1028        for (i, w_char) in chars {
1029            icon_data.szTip[i] = w_char;
1030        }
1031        icon_data.uFlags |= NIF_TIP;
1032        // Standard tooltip is normally suppressed on NOTIFYICON_VERSION_4
1033        icon_data.uFlags |= NIF_SHOWTIP;
1034    }
1035    if let Some(hidden_state) = icon_hidden_state {
1036        if hidden_state {
1037            icon_data.dwState = NOTIFY_ICON_STATE(icon_data.dwState.0 | NIS_HIDDEN.0);
1038            icon_data.dwStateMask |= NIS_HIDDEN;
1039        }
1040        icon_data.uFlags |= NIF_STATE;
1041    }
1042    if let Some(set_balloon_notification) = maybe_balloon_text {
1043        if let Some(balloon) = set_balloon_notification {
1044            let body_chars = to_wide_chars_iter(balloon.body)
1045                .take(icon_data.szInfo.len() - 1)
1046                .chain(std::iter::once(0))
1047                .enumerate();
1048            for (i, w_char) in body_chars {
1049                icon_data.szInfo[i] = w_char;
1050            }
1051            let title_chars = to_wide_chars_iter(balloon.title)
1052                .take(icon_data.szInfoTitle.len() - 1)
1053                .chain(std::iter::once(0))
1054                .enumerate();
1055            for (i, w_char) in title_chars {
1056                icon_data.szInfoTitle[i] = w_char;
1057            }
1058            icon_data.dwInfoFlags =
1059                NOTIFY_ICON_INFOTIP_FLAGS(icon_data.dwInfoFlags.0 | u32::from(balloon.icon));
1060        }
1061        icon_data.uFlags |= NIF_INFO;
1062    }
1063    icon_data
1064}
1065
1066/// Notification icon ID given to the Windows API.
1067#[derive(Copy, Clone, Eq, PartialEq, Debug)]
1068pub enum NotificationIconId {
1069    /// A simple ID.
1070    Simple(u16),
1071    /// A GUID that allows Windows to track the icon between applidation restarts.
1072    ///
1073    /// This way the user can set preferences for icon visibility and position.
1074    GUID(GUID),
1075}
1076
1077impl Default for NotificationIconId {
1078    fn default() -> Self {
1079        NotificationIconId::Simple(0)
1080    }
1081}
1082
1083/// Options for a new notification icon used by [`Window::add_notification_icon`].
1084#[derive(Eq, PartialEq, Default, Debug)]
1085pub struct NotificationIconOptions<I, S> {
1086    pub icon_id: NotificationIconId,
1087    pub icon: Option<I>,
1088    pub tooltip_text: Option<S>,
1089    pub visible: bool,
1090}
1091
1092/// A Balloon notification above the Windows notification area.
1093///
1094/// Used with [`NotificationIcon::set_balloon_notification`].
1095#[derive(Copy, Clone, Default, Debug)]
1096pub struct BalloonNotification<'a> {
1097    pub title: &'a str,
1098    pub body: &'a str,
1099    pub icon: BalloonNotificationStandardIcon,
1100}
1101
1102/// Built-in Windows standard icons for balloon notifications.
1103#[derive(IntoPrimitive, Copy, Clone, Default, Debug)]
1104#[repr(u32)]
1105pub enum BalloonNotificationStandardIcon {
1106    #[default]
1107    None = NIIF_NONE.0,
1108    Info = NIIF_INFO.0,
1109    Warning = NIIF_WARNING.0,
1110    Error = NIIF_ERROR.0,
1111}
1112
1113/// Taskbar progress state animation type.
1114#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
1115#[repr(i32)]
1116pub enum ProgressState {
1117    /// Stops displaying progress and returns the button to its normal state.
1118    #[default]
1119    NoProgress = TBPF_NOPROGRESS.0,
1120    /// Shows a "working" animation without indicating a completion percentage.
1121    Indeterminate = TBPF_INDETERMINATE.0,
1122    /// Shows a progress indicator displaying the amount of work being completed.
1123    Normal = TBPF_NORMAL.0,
1124    /// The progress indicator turns red to show that an error has occurred. This is a determinate state.
1125    /// If the progress indicator is in the indeterminate state, it switches to a red determinate display
1126    /// of a generic percentage not indicative of actual progress.
1127    Error = TBPF_ERROR.0,
1128    /// The progress indicator turns yellow to show that progress is currently stopped. This is a determinate state.
1129    /// If the progress indicator is in the indeterminate state, it switches to a yellow determinate display
1130    /// of a generic percentage not indicative of actual progress.
1131    Paused = TBPF_PAUSED.0,
1132}
1133
1134impl From<ProgressState> for TBPFLAG {
1135    fn from(value: ProgressState) -> Self {
1136        TBPFLAG(value.into())
1137    }
1138}
1139
1140/// Taskbar functionality.
1141pub struct Taskbar {
1142    taskbar_list_3: ITaskbarList3,
1143}
1144
1145impl Taskbar {
1146    pub fn new() -> io::Result<Self> {
1147        let result = Taskbar {
1148            taskbar_list_3: ITaskbarList3::new_instance()?,
1149        };
1150        Ok(result)
1151    }
1152
1153    /// Sets the progress state taskbar animation of a window.
1154    ///
1155    /// See also: [Microsoft docs](https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist3-setprogressstate)
1156    ///
1157    /// # Examples
1158    ///
1159    /// ```no_run
1160    /// use winapi_easy::ui::{
1161    ///     ProgressState,
1162    ///     Taskbar,
1163    ///     WindowHandle,
1164    /// };
1165    ///
1166    /// use std::thread;
1167    /// use std::time::Duration;
1168    ///
1169    /// let window = WindowHandle::get_console_window().expect("Cannot get console window");
1170    /// let taskbar = Taskbar::new()?;
1171    ///
1172    /// taskbar.set_progress_state(&window, ProgressState::Indeterminate)?;
1173    /// thread::sleep(Duration::from_millis(3000));
1174    /// taskbar.set_progress_state(&window, ProgressState::NoProgress)?;
1175    ///
1176    /// # Result::<(), std::io::Error>::Ok(())
1177    /// ```
1178    pub fn set_progress_state(
1179        &self,
1180        window: &WindowHandle,
1181        state: ProgressState,
1182    ) -> io::Result<()> {
1183        let ret_val = unsafe {
1184            self.taskbar_list_3
1185                .SetProgressState(HWND::from(window), state.into())
1186        };
1187        ret_val.map_err(|err| custom_err_with_code("Error setting progress state", err.code()))
1188    }
1189
1190    /// Sets the completion amount of the taskbar progress state animation.
1191    pub fn set_progress_value(
1192        &self,
1193        window: &WindowHandle,
1194        completed: u64,
1195        total: u64,
1196    ) -> io::Result<()> {
1197        let ret_val = unsafe {
1198            self.taskbar_list_3
1199                .SetProgressValue(HWND::from(window), completed, total)
1200        };
1201        ret_val.map_err(|err| custom_err_with_code("Error setting progress value", err.code()))
1202    }
1203}
1204
1205impl ComInterfaceExt for ITaskbarList3 {
1206    const CLASS_GUID: GUID = TaskbarList;
1207}
1208
1209/// Creates a console window for the current process if there is none.
1210pub fn allocate_console() -> io::Result<()> {
1211    unsafe {
1212        AllocConsole()?;
1213    }
1214    Ok(())
1215}
1216
1217/// Detaches the current process from its console.
1218///
1219/// If no other processes use the console, it will be destroyed.
1220pub fn detach_console() -> io::Result<()> {
1221    unsafe {
1222        FreeConsole()?;
1223    }
1224    Ok(())
1225}
1226
1227/// Locks the computer, same as the user action.
1228pub fn lock_workstation() -> io::Result<()> {
1229    // Because the function executes asynchronously, a nonzero return value indicates that the operation has been initiated.
1230    // It does not indicate whether the workstation has been successfully locked.
1231    unsafe { LockWorkStation()? };
1232    Ok(())
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237    use more_asserts::*;
1238
1239    use super::*;
1240
1241    #[test]
1242    fn check_toplevel_windows() -> io::Result<()> {
1243        let all_windows = WindowHandle::get_toplevel_windows()?;
1244        assert_gt!(all_windows.len(), 0);
1245        for window in all_windows {
1246            assert!(window.is_window());
1247            assert!(window.get_placement().is_ok());
1248            assert!(window.get_class_name().is_ok());
1249            std::hint::black_box(&window.get_caption_text());
1250            #[cfg(feature = "process")]
1251            std::hint::black_box(&window.get_creator_thread_process_ids());
1252        }
1253        Ok(())
1254    }
1255
1256    #[test]
1257    fn new_window_with_class() -> io::Result<()> {
1258        struct MyListener;
1259        impl WindowMessageListener for MyListener {}
1260        const CLASS_NAME_PREFIX: &str = "myclass1";
1261        const WINDOW_NAME: &str = "mywindow1";
1262        const CAPTION_TEXT: &str = "Testwindow";
1263
1264        let listener = MyListener;
1265        let icon: BuiltinIcon = Default::default();
1266        let class: WindowClass<MyListener> = WindowClass::register_new(
1267            CLASS_NAME_PREFIX,
1268            WindowClassAppearance {
1269                icon: Some(icon),
1270                ..Default::default()
1271            },
1272        )?;
1273        let window = Window::create_new(&class, &listener, WINDOW_NAME)?;
1274        let notification_icon_options = NotificationIconOptions {
1275            icon: Some(icon),
1276            tooltip_text: Some("A tooltip!"),
1277            visible: false,
1278            ..Default::default()
1279        };
1280        let mut notification_icon = window.add_notification_icon(notification_icon_options)?;
1281        let balloon_notification = BalloonNotification::default();
1282        notification_icon.set_balloon_notification(Some(balloon_notification))?;
1283
1284        assert_eq!(window.as_ref().get_caption_text(), WINDOW_NAME);
1285        window.as_ref().set_caption_text(CAPTION_TEXT)?;
1286        assert_eq!(window.as_ref().get_caption_text(), CAPTION_TEXT);
1287        assert!(window
1288            .as_ref()
1289            .get_class_name()?
1290            .starts_with(CLASS_NAME_PREFIX));
1291
1292        Ok(())
1293    }
1294}