1use std::any::Any;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::error::Error;
7use std::ffi::c_void;
8use std::fmt::{
9 Display,
10 Formatter,
11};
12use std::marker::PhantomData;
13use std::ops::{
14 BitOr,
15 Deref,
16};
17use std::ptr::NonNull;
18use std::rc::Rc;
19use std::{
20 io,
21 mem,
22 ptr,
23 vec,
24};
25
26use num_enum::{
27 IntoPrimitive,
28 TryFromPrimitive,
29};
30use windows::Win32::Foundation::{
31 ERROR_SUCCESS,
32 GetLastError,
33 HWND,
34 LPARAM,
35 NO_ERROR,
36 SetLastError,
37 WPARAM,
38};
39use windows::Win32::Graphics::Dwm::{
40 DWMWA_CLOAKED,
41 DwmGetWindowAttribute,
42};
43use windows::Win32::Graphics::Gdi::{
44 GetWindowRgn,
45 InvalidateRect,
46 MapWindowPoints,
47 RGN_ERROR,
48 SetWindowRgn,
49};
50use windows::Win32::System::Console::GetConsoleWindow;
51use windows::Win32::UI::Input::KeyboardAndMouse::SetActiveWindow;
52use windows::Win32::UI::Magnification::{
53 MAGTRANSFORM,
54 MS_SHOWMAGNIFIEDCURSOR,
55 MagSetWindowSource,
56 MagSetWindowTransform,
57 WC_MAGNIFIER,
58};
59use windows::Win32::UI::Shell::{
60 NIF_GUID,
61 NIF_ICON,
62 NIF_INFO,
63 NIF_MESSAGE,
64 NIF_SHOWTIP,
65 NIF_STATE,
66 NIF_TIP,
67 NIIF_ERROR,
68 NIIF_INFO,
69 NIIF_NONE,
70 NIIF_WARNING,
71 NIM_ADD,
72 NIM_DELETE,
73 NIM_MODIFY,
74 NIM_SETVERSION,
75 NIS_HIDDEN,
76 NOTIFY_ICON_INFOTIP_FLAGS,
77 NOTIFY_ICON_STATE,
78 NOTIFYICON_VERSION_4,
79 NOTIFYICONDATAW,
80 NOTIFYICONIDENTIFIER,
81 Shell_NotifyIconGetRect,
82 Shell_NotifyIconW,
83};
84use windows::Win32::UI::WindowsAndMessaging::{
85 CW_USEDEFAULT,
86 CreateWindowExW,
87 DestroyWindow,
88 EnumWindows,
89 FLASHW_ALL,
90 FLASHW_CAPTION,
91 FLASHW_STOP,
92 FLASHW_TIMER,
93 FLASHW_TIMERNOFG,
94 FLASHW_TRAY,
95 FLASHWINFO,
96 FLASHWINFO_FLAGS,
97 FlashWindowEx,
98 GWLP_USERDATA,
99 GetClassNameW,
100 GetClientRect,
101 GetDesktopWindow,
102 GetForegroundWindow,
103 GetWindowLongPtrW,
104 GetWindowPlacement,
105 GetWindowTextLengthW,
106 GetWindowTextW,
107 HICON,
108 HWND_BOTTOM,
109 HWND_NOTOPMOST,
110 HWND_TOP,
111 HWND_TOPMOST,
112 IsWindow,
113 IsWindowVisible,
114 KillTimer,
115 LWA_ALPHA,
116 RegisterClassExW,
117 SC_CLOSE,
118 SC_MAXIMIZE,
119 SC_MINIMIZE,
120 SC_MONITORPOWER,
121 SC_RESTORE,
122 SHOW_WINDOW_CMD,
123 SW_HIDE,
124 SW_MAXIMIZE,
125 SW_MINIMIZE,
126 SW_RESTORE,
127 SW_SHOW,
128 SW_SHOWMINIMIZED,
129 SW_SHOWMINNOACTIVE,
130 SW_SHOWNA,
131 SW_SHOWNOACTIVATE,
132 SW_SHOWNORMAL,
133 SWP_NOSIZE,
134 SendMessageW,
135 SetForegroundWindow,
136 SetLayeredWindowAttributes,
137 SetMenu,
138 SetTimer,
139 SetWindowLongPtrW,
140 SetWindowPlacement,
141 SetWindowPos,
142 SetWindowTextW,
143 ShowWindow,
144 UnregisterClassW,
145 WINDOW_EX_STYLE,
146 WINDOW_STYLE,
147 WINDOWPLACEMENT,
148 WM_SYSCOMMAND,
149 WNDCLASSEXW,
150 WPF_SETMINPOSITION,
151 WS_CHILD,
152 WS_CLIPCHILDREN,
153 WS_EX_COMPOSITED,
154 WS_EX_LAYERED,
155 WS_EX_LEFT,
156 WS_EX_NOACTIVATE,
157 WS_EX_TOPMOST,
158 WS_EX_TRANSPARENT,
159 WS_OVERLAPPED,
160 WS_OVERLAPPEDWINDOW,
161 WS_POPUP,
162 WS_VISIBLE,
163};
164use windows::core::{
165 BOOL,
166 GUID,
167 PCWSTR,
168};
169
170use super::{
171 Point,
172 RectTransform,
173 Rectangle,
174 Region,
175 init_magnifier,
176};
177use crate::internal::{
178 RawBox,
179 ResultExt,
180 ReturnValue,
181 custom_err_with_code,
182 with_sync_closure_to_callback2,
183};
184#[cfg(feature = "process")]
185use crate::process::{
186 ProcessId,
187 ThreadId,
188};
189use crate::string::{
190 FromWideString,
191 ZeroTerminatedWideString,
192 to_wide_chars_iter,
193};
194use crate::ui::menu::MenuBar;
195use crate::ui::messaging::{
196 CustomUserMessage,
197 ListenerAnswer,
198 ListenerMessage,
199 RawMessage,
200 WmlOpaqueClosure,
201 generic_window_proc,
202};
203use crate::ui::resource::{
204 Brush,
205 Cursor,
206 Icon,
207 ImageKindInternal,
208};
209
210#[derive(Clone, Copy, Eq, PartialEq, Debug)]
212pub struct WindowHandle {
213 raw_handle: HWND,
214}
215
216unsafe impl Send for WindowHandle {}
218unsafe impl Sync for WindowHandle {}
219
220impl WindowHandle {
221 pub fn get_console_window() -> Option<Self> {
223 let handle = unsafe { GetConsoleWindow() };
224 Self::from_maybe_null(handle)
225 }
226
227 pub fn get_foreground_window() -> Option<Self> {
229 let handle = unsafe { GetForegroundWindow() };
230 Self::from_maybe_null(handle)
231 }
232
233 pub fn get_desktop_window() -> io::Result<Self> {
235 let handle = unsafe { GetDesktopWindow() };
236 handle
237 .if_null_to_error(|| io::ErrorKind::Other.into())
238 .map(Self::from_non_null)
239 }
240
241 pub fn get_toplevel_windows() -> io::Result<Vec<Self>> {
243 let mut result: Vec<WindowHandle> = Vec::new();
244 let callback = |handle: HWND, _app_value: LPARAM| -> BOOL {
245 let window_handle = Self::from_maybe_null(handle).unwrap_or_else(|| {
246 unreachable!("Window handle passed to callback should never be null")
247 });
248 result.push(window_handle);
249 true.into()
250 };
251 let acceptor = |raw_callback| unsafe { EnumWindows(Some(raw_callback), LPARAM::default()) };
252 with_sync_closure_to_callback2(callback, acceptor)?;
253 Ok(result)
254 }
255
256 pub(crate) fn from_non_null(handle: HWND) -> Self {
257 Self { raw_handle: handle }
258 }
259
260 pub(crate) fn from_maybe_null(handle: HWND) -> Option<Self> {
261 if handle.is_null() {
262 None
263 } else {
264 Some(Self { raw_handle: handle })
265 }
266 }
267
268 pub fn is_window(self) -> bool {
270 let result = unsafe { IsWindow(Some(self.raw_handle)) };
271 result.as_bool()
272 }
273
274 pub fn is_visible(self) -> bool {
275 let result = unsafe { IsWindowVisible(self.raw_handle) };
276 result.as_bool()
277 }
278
279 pub fn is_cloaked(self) -> io::Result<bool> {
283 let mut is_cloaked = BOOL::default();
284 unsafe {
285 DwmGetWindowAttribute(
286 self.raw_handle,
287 DWMWA_CLOAKED,
288 (&raw mut is_cloaked).cast::<c_void>(),
289 mem::size_of::<BOOL>()
290 .try_into()
291 .unwrap_or_else(|_| unreachable!()),
292 )
293 }?;
294 Ok(is_cloaked.as_bool())
295 }
296
297 pub fn get_caption_text(self) -> String {
299 let required_length: usize = unsafe { GetWindowTextLengthW(self.raw_handle) }
300 .try_into()
301 .unwrap_or_else(|_| unreachable!());
302 let required_length = if required_length == 0 {
303 return String::new();
304 } else {
305 1 + required_length
306 };
307
308 let mut buffer: Vec<u16> = vec![0; required_length as usize];
309 let copied_chars = unsafe { GetWindowTextW(self.raw_handle, buffer.as_mut()) }
310 .try_into()
311 .unwrap_or_else(|_| unreachable!());
312 if copied_chars == 0 {
313 String::new()
314 } else {
315 buffer.truncate(copied_chars);
317 buffer.to_string_lossy()
318 }
319 }
320
321 pub fn set_caption_text(self, text: &str) -> io::Result<()> {
323 let ret_val = unsafe {
324 SetWindowTextW(
325 self.raw_handle,
326 ZeroTerminatedWideString::from_os_str(text).as_raw_pcwstr(),
327 )
328 };
329 ret_val?;
330 Ok(())
331 }
332
333 pub(crate) fn set_menu(self, menu: Option<&MenuBar>) -> io::Result<()> {
334 let maybe_raw_handle = menu.map(|x| x.as_handle().as_raw_handle());
335 unsafe { SetMenu(self.raw_handle, maybe_raw_handle) }?;
336 Ok(())
337 }
338
339 pub fn set_as_foreground(self) -> io::Result<()> {
343 unsafe {
344 SetForegroundWindow(self.raw_handle).if_null_to_error_else_drop(|| {
345 io::Error::other("Cannot bring window to foreground")
346 })?;
347 }
348 Ok(())
349 }
350
351 pub fn set_as_active(self) -> io::Result<()> {
353 unsafe {
354 SetActiveWindow(self.raw_handle)?;
355 }
356 Ok(())
357 }
358
359 pub fn set_show_state(self, state: WindowShowState) -> io::Result<()> {
361 if self.is_window() {
362 unsafe {
363 let _ = ShowWindow(self.raw_handle, state.into());
364 }
365 Ok(())
366 } else {
367 Err(io::Error::new(
368 io::ErrorKind::NotFound,
369 "Cannot set show state because window does not exist",
370 ))
371 }
372 }
373
374 pub fn get_placement(self) -> io::Result<WindowPlacement> {
376 let mut raw_placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
377 length: mem::size_of::<WINDOWPLACEMENT>()
378 .try_into()
379 .unwrap_or_else(|_| unreachable!()),
380 ..Default::default()
381 };
382 unsafe { GetWindowPlacement(self.raw_handle, &raw mut raw_placement)? };
383 Ok(WindowPlacement { raw_placement })
384 }
385
386 pub fn set_placement(self, placement: &WindowPlacement) -> io::Result<()> {
388 unsafe { SetWindowPlacement(self.raw_handle, &raw const placement.raw_placement)? };
389 Ok(())
390 }
391
392 pub fn modify_placement_with<F>(self, f: F) -> io::Result<()>
393 where
394 F: FnOnce(&mut WindowPlacement) -> io::Result<()>,
395 {
396 let mut placement = self.get_placement()?;
397 f(&mut placement)?;
398 self.set_placement(&placement)?;
399 Ok(())
400 }
401
402 pub fn set_z_position(self, z_position: WindowZPosition) -> io::Result<()> {
403 unsafe {
404 SetWindowPos(
405 self.raw_handle,
406 Some(z_position.to_raw_hwnd()),
407 0,
408 0,
409 0,
410 0,
411 SWP_NOSIZE,
412 )?;
413 }
414 Ok(())
415 }
416
417 pub fn get_client_area_coords(self) -> io::Result<Rectangle> {
419 let mut result_rect: Rectangle = Default::default();
420 unsafe { GetClientRect(self.raw_handle, &raw mut result_rect) }?;
421 self.map_points(None, result_rect.as_point_array_mut())?;
422 Ok(result_rect)
423 }
424
425 pub(crate) fn map_points(
426 self,
427 other_window: Option<Self>,
428 points: &mut [Point],
429 ) -> io::Result<()> {
430 unsafe { SetLastError(ERROR_SUCCESS) };
431 let map_result = unsafe {
432 MapWindowPoints(
433 Some(self.raw_handle),
434 other_window.map(|x| x.raw_handle),
435 points,
436 )
437 };
438 if map_result == 0 {
439 let last_error = unsafe { GetLastError() };
440 if last_error != ERROR_SUCCESS {
441 return Err(io::Error::last_os_error());
442 }
443 }
444 Ok(())
445 }
446
447 pub fn get_region(self) -> io::Result<Option<Region>> {
448 let region = Region::from_rect(Default::default())?;
449 let result = unsafe { GetWindowRgn(self.raw_handle, region.raw_handle) };
450 if result == RGN_ERROR {
451 Ok(None)
452 } else {
453 Ok(Some(region))
454 }
455 }
456
457 pub fn set_region(self, region: Region) -> io::Result<()> {
461 unsafe {
462 SetWindowRgn(self.raw_handle, Some(region.into()), true)
463 .if_null_to_error_else_drop(|| io::ErrorKind::Other.into())
464 }
465 }
466
467 pub fn redraw(self) -> io::Result<()> {
468 unsafe {
469 InvalidateRect(Some(self.raw_handle), None, true).if_null_get_last_error_else_drop()
470 }
471 }
472
473 pub fn get_class_name(self) -> io::Result<String> {
475 const BUFFER_SIZE: usize = WindowClass::MAX_WINDOW_CLASS_NAME_CHARS + 1;
476 let mut buffer: Vec<u16> = vec![0; BUFFER_SIZE];
477 let chars_copied: usize = unsafe { GetClassNameW(self.raw_handle, buffer.as_mut()) }
478 .if_null_get_last_error()?
479 .try_into()
480 .unwrap_or_else(|_| unreachable!());
481 buffer.truncate(chars_copied);
482 Ok(buffer.to_string_lossy())
483 }
484
485 pub fn send_command(self, action: WindowCommand) -> io::Result<()> {
487 let result = unsafe {
488 SendMessageW(
489 self.raw_handle,
490 WM_SYSCOMMAND,
491 Some(WPARAM(action.to_usize())),
492 None,
493 )
494 };
495 result
496 .if_non_null_to_error(|| custom_err_with_code("Cannot perform window action", result.0))
497 }
498
499 pub fn flash(self) {
503 self.flash_custom(Default::default(), Default::default(), Default::default());
504 }
505
506 pub fn flash_custom(
508 self,
509 element: FlashElement,
510 duration: FlashDuration,
511 frequency: FlashInterval,
512 ) {
513 let (count, flags) = match duration {
514 FlashDuration::Count(count) => (count, Default::default()),
515 FlashDuration::CountUntilForeground(count) => (count, FLASHW_TIMERNOFG),
516 FlashDuration::ContinuousUntilForeground => (0, FLASHW_TIMERNOFG),
517 FlashDuration::Continuous => (0, FLASHW_TIMER),
518 };
519 let flags = flags | element.to_flashwinfo_flags();
520 let raw_config = FLASHWINFO {
521 cbSize: mem::size_of::<FLASHWINFO>()
522 .try_into()
523 .unwrap_or_else(|_| unreachable!()),
524 hwnd: self.into(),
525 dwFlags: flags,
526 uCount: count,
527 dwTimeout: match frequency {
528 FlashInterval::DefaultCursorBlinkInterval => 0,
529 FlashInterval::Milliseconds(ms) => ms,
530 },
531 };
532 unsafe {
533 let _ = FlashWindowEx(&raw const raw_config);
534 };
535 }
536
537 pub fn flash_stop(self) {
539 let raw_config = FLASHWINFO {
540 cbSize: mem::size_of::<FLASHWINFO>()
541 .try_into()
542 .unwrap_or_else(|_| unreachable!()),
543 hwnd: self.into(),
544 dwFlags: FLASHW_STOP,
545 ..Default::default()
546 };
547 unsafe {
548 let _ = FlashWindowEx(&raw const raw_config);
549 };
550 }
551
552 fn internal_set_layered_opacity_alpha(self, alpha: u8) -> io::Result<()> {
553 unsafe {
554 SetLayeredWindowAttributes(self.raw_handle, Default::default(), alpha, LWA_ALPHA)?;
555 }
556 Ok(())
557 }
558
559 pub fn set_timer(self, timer_id: usize, interval_ms: u32) -> io::Result<()> {
560 unsafe {
561 SetTimer(Some(self.raw_handle), timer_id, interval_ms, None)
562 .if_null_get_last_error_else_drop()
563 }
564 }
565
566 pub fn kill_timer(self, timer_id: usize) -> io::Result<()> {
567 unsafe { KillTimer(Some(self.raw_handle), timer_id)? }
568 Ok(())
569 }
570
571 pub fn send_user_message(self, message: CustomUserMessage) -> io::Result<()> {
572 RawMessage::from(message).post_to_queue(Some(self))
573 }
574
575 #[cfg(feature = "process")]
577 pub fn get_creator_thread_id(self) -> ThreadId {
578 self.get_creator_thread_process_ids().0
579 }
580
581 #[cfg(feature = "process")]
583 pub fn get_creator_process_id(self) -> ProcessId {
584 self.get_creator_thread_process_ids().1
585 }
586
587 #[cfg(feature = "process")]
588 fn get_creator_thread_process_ids(self) -> (ThreadId, ProcessId) {
589 use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
590 let mut process_id: u32 = 0;
591 let thread_id =
592 unsafe { GetWindowThreadProcessId(self.raw_handle, Some(&raw mut process_id)) };
593 (ThreadId(thread_id), ProcessId(process_id))
594 }
595
596 #[cfg(feature = "process")]
598 pub fn get_nonchild_windows(thread_id: ThreadId) -> Vec<Self> {
599 use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
600 let mut result: Vec<WindowHandle> = Vec::new();
601 let callback = |handle: HWND, _app_value: LPARAM| -> BOOL {
602 let window_handle = WindowHandle::from_maybe_null(handle).unwrap_or_else(|| {
603 unreachable!("Window handle passed to callback should never be null")
604 });
605 result.push(window_handle);
606 true.into()
607 };
608 let acceptor = |raw_callback| {
609 let _ =
610 unsafe { EnumThreadWindows(thread_id.0, Some(raw_callback), LPARAM::default()) };
611 };
612 with_sync_closure_to_callback2(callback, acceptor);
613 result
614 }
615
616 pub fn set_monitor_power(self, level: MonitorPower) -> io::Result<()> {
621 let result = unsafe {
622 SendMessageW(
623 self.raw_handle,
624 WM_SYSCOMMAND,
625 Some(WPARAM(
626 SC_MONITORPOWER
627 .try_into()
628 .unwrap_or_else(|_| unreachable!()),
629 )),
630 Some(LPARAM(level.into())),
631 )
632 };
633 result.if_non_null_to_error(|| {
634 custom_err_with_code("Cannot set monitor power using window", result.0)
635 })
636 }
637
638 pub(crate) unsafe fn get_user_data_ptr<T>(self) -> Option<NonNull<T>> {
639 let ptr_value = unsafe { GetWindowLongPtrW(self.raw_handle, GWLP_USERDATA) };
640 NonNull::new(ptr::with_exposed_provenance_mut(ptr_value.cast_unsigned()))
641 }
642
643 pub(crate) unsafe fn set_user_data_ptr<T>(self, ptr: *const T) -> io::Result<()> {
644 unsafe { SetLastError(NO_ERROR) };
645 let ret_val = unsafe {
646 SetWindowLongPtrW(
647 self.raw_handle,
648 GWLP_USERDATA,
649 ptr.expose_provenance().cast_signed(),
650 )
651 };
652 if ret_val == 0 {
653 let err_val = unsafe { GetLastError() };
654 if err_val != NO_ERROR {
655 return Err(custom_err_with_code(
656 "Cannot set window procedure",
657 err_val.0,
658 ));
659 }
660 }
661 Ok(())
662 }
663}
664
665impl From<WindowHandle> for HWND {
666 fn from(value: WindowHandle) -> Self {
668 value.raw_handle
669 }
670}
671
672impl TryFrom<HWND> for WindowHandle {
673 type Error = TryFromHWNDError;
674
675 fn try_from(value: HWND) -> Result<Self, Self::Error> {
677 WindowHandle::from_maybe_null(value).ok_or(TryFromHWNDError(()))
678 }
679}
680
681#[derive(Copy, Clone, PartialEq, Eq, Debug)]
682pub struct TryFromHWNDError(pub(crate) ());
683
684impl Display for TryFromHWNDError {
685 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
686 write!(f, "HWND value must not be null")
687 }
688}
689
690impl Error for TryFromHWNDError {}
691
692#[derive(Debug)]
693enum WindowClassVariant {
694 Builtin(PCWSTR),
695 Custom(Rc<WindowClass>),
696}
697
698impl WindowClassVariant {
699 fn raw_class_identifier(&self) -> PCWSTR {
700 match self {
701 WindowClassVariant::Builtin(pcwstr) => *pcwstr,
702 WindowClassVariant::Custom(window_class) => window_class.raw_class_identifier(),
703 }
704 }
705}
706
707#[derive(Debug)]
709pub struct WindowClass {
710 atom: u16,
711 #[expect(dead_code)]
712 appearance: WindowClassAppearance,
713}
714
715impl WindowClass {
716 const MAX_WINDOW_CLASS_NAME_CHARS: usize = 256;
717
718 fn raw_class_identifier(&self) -> PCWSTR {
719 PCWSTR(self.atom as *const u16)
720 }
721}
722
723impl WindowClass {
724 pub fn register_new(
732 class_name_prefix: &str,
733 appearance: WindowClassAppearance,
734 ) -> io::Result<Self> {
735 use base64::Engine;
736 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
737
738 let base64_uuid = URL_SAFE_NO_PAD.encode(uuid::Uuid::new_v4().as_bytes());
739 let class_name = class_name_prefix.to_string() + "_" + &base64_uuid;
740
741 let icon_handle = appearance
742 .icon
743 .as_deref()
744 .map_or_else(Default::default, Icon::as_handle);
745 let class_def = WNDCLASSEXW {
747 cbSize: mem::size_of::<WNDCLASSEXW>()
748 .try_into()
749 .unwrap_or_else(|_| unreachable!()),
750 lpfnWndProc: Some(generic_window_proc),
751 hIcon: icon_handle,
752 hCursor: appearance
753 .cursor
754 .as_deref()
755 .map_or_else(Default::default, Cursor::as_handle),
756 hbrBackground: appearance
757 .background_brush
758 .as_deref()
759 .map_or_else(Default::default, Brush::as_handle),
760 lpszClassName: ZeroTerminatedWideString::from_os_str(class_name).as_raw_pcwstr(),
761 ..Default::default()
762 };
763 let atom = unsafe { RegisterClassExW(&raw const class_def).if_null_get_last_error()? };
764 Ok(WindowClass { atom, appearance })
765 }
766}
767
768impl Drop for WindowClass {
769 fn drop(&mut self) {
771 unsafe { UnregisterClassW(self.raw_class_identifier(), None) }
772 .unwrap_or_default_and_print_error();
773 }
774}
775
776#[derive(Clone, Debug)]
777pub struct WindowClassAppearance {
778 pub background_brush: Option<Rc<Brush>>,
779 pub icon: Option<Rc<Icon>>,
780 pub cursor: Option<Rc<Cursor>>,
781}
782
783impl WindowClassAppearance {
784 pub fn empty() -> Self {
785 Self {
786 background_brush: None,
787 icon: None,
788 cursor: None,
789 }
790 }
791}
792
793impl Default for WindowClassAppearance {
794 fn default() -> Self {
795 Self {
796 background_brush: Some(Default::default()),
797 icon: Some(Default::default()),
798 cursor: Some(Default::default()),
799 }
800 }
801}
802
803pub type DefaultWmlType = fn(&ListenerMessage) -> ListenerAnswer;
804
805pub trait WindowSubtype: 'static {}
806
807impl WindowSubtype for () {}
808
809pub enum Layered {}
810
811impl WindowSubtype for Layered {}
812
813pub enum Magnifier {}
814
815impl WindowSubtype for Magnifier {}
816
817pub struct Window<WST = ()> {
824 handle: WindowHandle,
825 #[expect(dead_code)]
826 class: WindowClassVariant,
827 #[expect(dead_code)]
828 opaque_listener: Option<RawBox<WmlOpaqueClosure<'static>>>,
829 #[expect(dead_code)]
830 parent: Option<Rc<dyn Any>>,
831 notification_icons: HashMap<NotificationIconId, NotificationIcon>,
832 phantom: PhantomData<WST>,
833}
834
835#[cfg(test)]
836static_assertions::assert_not_impl_any!(Window: Send);
837
838impl<WST: WindowSubtype> Window<WST> {
839 fn internal_new<WML, PST>(
840 class: WindowClassVariant,
841 listener: Option<WML>,
842 caption_text: &str,
843 appearance: WindowAppearance,
844 parent: Option<Rc<RefCell<Window<PST>>>>,
845 ) -> io::Result<Self>
846 where
847 WML: FnMut(&ListenerMessage) -> ListenerAnswer + 'static,
848 PST: WindowSubtype,
849 {
850 let h_wnd: HWND = unsafe {
851 CreateWindowExW(
852 appearance.extended_style.into(),
853 class.raw_class_identifier(),
854 ZeroTerminatedWideString::from_os_str(caption_text).as_raw_pcwstr(),
855 appearance.style.into(),
856 CW_USEDEFAULT,
857 0,
858 CW_USEDEFAULT,
859 0,
860 parent.as_deref().map(|x| x.borrow().raw_handle),
861 None,
862 None,
863 None,
864 )?
865 };
866 let handle = WindowHandle::from_non_null(h_wnd);
867
868 let opaque_listener = if let Some(listener) = listener {
869 let opaque_listener = unsafe { Self::set_listener_internal(handle, listener) }?;
870 Some(opaque_listener)
871 } else {
872 None
873 };
874 Ok(Window {
875 handle,
876 class,
877 opaque_listener,
878 parent: parent.map(|x| x as Rc<dyn Any>),
879 notification_icons: HashMap::new(),
880 phantom: PhantomData,
881 })
882 }
883
884 pub fn as_handle(&self) -> WindowHandle {
885 self.handle
886 }
887
888 pub fn set_listener<WML>(&mut self, listener: WML) -> io::Result<()>
890 where
891 WML: FnMut(&ListenerMessage) -> ListenerAnswer + 'static,
892 {
893 unsafe { Self::set_listener_internal(self.handle, listener) }?;
894 Ok(())
895 }
896
897 unsafe fn set_listener_internal<WML>(
903 window_handle: WindowHandle,
904 listener: WML,
905 ) -> io::Result<RawBox<WmlOpaqueClosure<'static>>>
906 where
907 WML: FnMut(&ListenerMessage) -> ListenerAnswer + 'static,
908 {
909 let mut opaque_listener = RawBox::new(Box::new(listener) as WmlOpaqueClosure);
910 unsafe {
911 window_handle.set_user_data_ptr::<WmlOpaqueClosure>(opaque_listener.as_mut_ptr())?;
912 }
913 Ok(opaque_listener)
914 }
915
916 pub fn set_menu(&mut self, menu: Option<&MenuBar>) -> io::Result<()> {
918 self.handle.set_menu(menu)
919 }
920
921 pub fn add_notification_icon(
927 &mut self,
928 options: NotificationIconOptions,
929 ) -> io::Result<&mut NotificationIcon> {
930 let id = options.icon_id;
931 assert!(
932 !self.notification_icons.contains_key(&id),
933 "Notification icon ID already exists"
934 );
935 self.notification_icons
936 .insert(id, NotificationIcon::new(self.handle, options)?);
937 Ok(self.get_notification_icon_mut(id))
938 }
939
940 pub fn get_notification_icon(&self, id: NotificationIconId) -> &NotificationIcon {
946 self.notification_icons
947 .get(&id)
948 .expect("Notification icon ID doesn't exist")
949 }
950
951 pub fn get_notification_icon_mut(&mut self, id: NotificationIconId) -> &mut NotificationIcon {
957 self.notification_icons
958 .get_mut(&id)
959 .expect("Notification icon ID doesn't exist")
960 }
961
962 pub fn remove_notification_icon(&mut self, id: NotificationIconId) {
968 let _ = self
969 .notification_icons
970 .remove(&id)
971 .expect("Notification icon ID doesn't exist");
972 }
973}
974
975impl Window<()> {
976 pub fn new<WML, PST>(
984 class: Rc<WindowClass>,
985 listener: Option<WML>,
986 caption_text: &str,
987 appearance: WindowAppearance,
988 parent: Option<Rc<RefCell<Window<PST>>>>,
989 ) -> io::Result<Self>
990 where
991 WML: FnMut(&ListenerMessage) -> ListenerAnswer + 'static,
992 PST: WindowSubtype,
993 {
994 let class = WindowClassVariant::Custom(class);
995 Self::internal_new(class, listener, caption_text, appearance, parent)
996 }
997}
998
999impl Window<Layered> {
1000 pub fn new_layered<WML, PST>(
1004 class: Rc<WindowClass>,
1005 listener: Option<WML>,
1006 caption_text: &str,
1007 mut appearance: WindowAppearance,
1008 parent: Option<Rc<RefCell<Window<PST>>>>,
1009 ) -> io::Result<Self>
1010 where
1011 WML: FnMut(&ListenerMessage) -> ListenerAnswer + 'static,
1012 PST: WindowSubtype,
1013 {
1014 appearance.extended_style =
1015 appearance.extended_style | WindowExtendedStyle::Other(WS_EX_LAYERED.0);
1016 let class = WindowClassVariant::Custom(class);
1017 Self::internal_new(class, listener, caption_text, appearance, parent)
1018 }
1019
1020 pub fn set_layered_opacity_alpha(&self, alpha: u8) -> io::Result<()> {
1022 self.handle.internal_set_layered_opacity_alpha(alpha)
1023 }
1024}
1025
1026impl Window<Magnifier> {
1027 pub fn new_magnifier(
1028 caption_text: &str,
1029 mut appearance: WindowAppearance,
1030 parent: Rc<RefCell<Window<Layered>>>,
1031 ) -> io::Result<Self> {
1032 init_magnifier()?;
1033 appearance.style =
1034 appearance.style | WindowStyle::Other(MS_SHOWMAGNIFIEDCURSOR.cast_unsigned());
1035 let class = WindowClassVariant::Builtin(WC_MAGNIFIER);
1036 Self::internal_new(
1037 class,
1038 None::<DefaultWmlType>,
1039 caption_text,
1040 appearance,
1041 Some(parent),
1042 )
1043 }
1044
1045 pub fn set_magnification_factor(&self, mag_factor: f32) -> io::Result<()> {
1046 const NUM_COLS: usize = 3;
1047 fn multi_index(matrix: &mut [f32], row: usize, col: usize) -> &mut f32 {
1048 &mut matrix[row * NUM_COLS + col]
1049 }
1050 let mut matrix: MAGTRANSFORM = Default::default();
1051 *multi_index(&mut matrix.v, 0, 0) = mag_factor;
1052 *multi_index(&mut matrix.v, 1, 1) = mag_factor;
1053 *multi_index(&mut matrix.v, 2, 2) = 1.0;
1054 unsafe {
1055 MagSetWindowTransform(self.raw_handle, &raw mut matrix)
1056 .if_null_get_last_error_else_drop()
1057 }
1058 }
1059
1060 pub fn set_magnification_source(&self, source: Rectangle) -> io::Result<()> {
1061 let _ = unsafe { MagSetWindowSource(self.raw_handle, source).if_null_get_last_error()? };
1062 Ok(())
1063 }
1064
1065 pub fn set_lens_use_bitmap_smoothing(&self, use_smoothing: bool) -> io::Result<()> {
1066 #[link(
1067 name = "magnification.dll",
1068 kind = "raw-dylib",
1069 modifiers = "+verbatim"
1070 )]
1071 unsafe extern "system" {
1072 fn MagSetLensUseBitmapSmoothing(h_wnd: HWND, use_smoothing: BOOL) -> BOOL;
1073 }
1074 unsafe {
1075 MagSetLensUseBitmapSmoothing(self.raw_handle, use_smoothing.into())
1076 .if_null_get_last_error_else_drop()
1077 }
1078 }
1079}
1080
1081impl<WST> Deref for Window<WST> {
1082 type Target = WindowHandle;
1083
1084 fn deref(&self) -> &Self::Target {
1085 &self.handle
1086 }
1087}
1088
1089impl<WST> Drop for Window<WST> {
1090 fn drop(&mut self) {
1091 if self.handle.is_window() {
1092 unsafe { DestroyWindow(self.handle.raw_handle) }.unwrap_or_default_and_print_error();
1093 }
1094 }
1095}
1096
1097#[derive(IntoPrimitive, TryFromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
1103#[non_exhaustive]
1104#[repr(u32)]
1105pub enum WindowStyle {
1106 Child = WS_CHILD.0,
1107 ClipChildren = WS_CLIPCHILDREN.0,
1108 Overlapped = WS_OVERLAPPED.0,
1109 OverlappedWindow = WS_OVERLAPPEDWINDOW.0,
1110 Popup = WS_POPUP.0,
1111 Visible = WS_VISIBLE.0,
1112 #[num_enum(catch_all)]
1113 Other(u32),
1114}
1115
1116#[expect(clippy::derivable_impls)]
1117impl Default for WindowStyle {
1118 fn default() -> Self {
1119 Self::Overlapped
1120 }
1121}
1122
1123impl BitOr for WindowStyle {
1124 type Output = WindowStyle;
1125
1126 fn bitor(self, rhs: Self) -> Self::Output {
1127 Self::Other(u32::from(self) | u32::from(rhs))
1128 }
1129}
1130
1131impl From<WindowStyle> for WINDOW_STYLE {
1132 fn from(value: WindowStyle) -> Self {
1133 WINDOW_STYLE(value.into())
1134 }
1135}
1136
1137#[derive(IntoPrimitive, TryFromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
1143#[non_exhaustive]
1144#[repr(u32)]
1145pub enum WindowExtendedStyle {
1146 Composited = WS_EX_COMPOSITED.0,
1147 Left = WS_EX_LEFT.0,
1148 NoActivate = WS_EX_NOACTIVATE.0,
1149 Topmost = WS_EX_TOPMOST.0,
1150 Transparent = WS_EX_TRANSPARENT.0,
1151 #[num_enum(catch_all)]
1152 Other(u32),
1153}
1154
1155#[expect(clippy::derivable_impls)]
1156impl Default for WindowExtendedStyle {
1157 fn default() -> Self {
1158 Self::Left
1159 }
1160}
1161
1162impl BitOr for WindowExtendedStyle {
1163 type Output = WindowExtendedStyle;
1164
1165 fn bitor(self, rhs: Self) -> Self::Output {
1166 Self::Other(u32::from(self) | u32::from(rhs))
1167 }
1168}
1169
1170impl From<WindowExtendedStyle> for WINDOW_EX_STYLE {
1171 fn from(value: WindowExtendedStyle) -> Self {
1172 WINDOW_EX_STYLE(value.into())
1173 }
1174}
1175
1176#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
1177pub struct WindowAppearance {
1178 pub style: WindowStyle,
1179 pub extended_style: WindowExtendedStyle,
1180}
1181
1182#[derive(IntoPrimitive, TryFromPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
1188#[repr(i32)]
1189pub enum WindowShowState {
1190 Hide = SW_HIDE.0,
1191 Maximize = SW_MAXIMIZE.0,
1192 Minimize = SW_MINIMIZE.0,
1193 Restore = SW_RESTORE.0,
1194 Show = SW_SHOW.0,
1195 ShowMinimized = SW_SHOWMINIMIZED.0,
1196 ShowMinNoActivate = SW_SHOWMINNOACTIVE.0,
1197 ShowNoActivate = SW_SHOWNA.0,
1198 ShowNormalNoActivate = SW_SHOWNOACTIVATE.0,
1199 ShowNormal = SW_SHOWNORMAL.0,
1200}
1201
1202impl From<WindowShowState> for SHOW_WINDOW_CMD {
1203 fn from(value: WindowShowState) -> Self {
1204 SHOW_WINDOW_CMD(value.into())
1205 }
1206}
1207
1208#[derive(Copy, Clone, Debug)]
1210pub struct WindowPlacement {
1211 raw_placement: WINDOWPLACEMENT,
1212}
1213
1214impl WindowPlacement {
1215 pub fn get_show_state(&self) -> Option<WindowShowState> {
1216 i32::try_from(self.raw_placement.showCmd)
1217 .ok()?
1218 .try_into()
1219 .ok()
1220 }
1221
1222 pub fn set_show_state(&mut self, state: WindowShowState) {
1223 self.raw_placement.showCmd = i32::from(state)
1224 .try_into()
1225 .unwrap_or_else(|_| unreachable!());
1226 }
1227
1228 pub fn get_minimized_position(&self) -> Point {
1229 self.raw_placement.ptMinPosition
1230 }
1231
1232 pub fn set_minimized_position(&mut self, coords: Point) {
1233 self.raw_placement.ptMinPosition = coords;
1234 self.raw_placement.flags |= WPF_SETMINPOSITION;
1235 }
1236
1237 pub fn get_maximized_position(&self) -> Point {
1238 self.raw_placement.ptMaxPosition
1239 }
1240
1241 pub fn set_maximized_position(&mut self, coords: Point) {
1242 self.raw_placement.ptMaxPosition = coords;
1243 }
1244
1245 pub fn get_normal_position(&self) -> Rectangle {
1246 self.raw_placement.rcNormalPosition
1247 }
1248
1249 pub fn set_normal_position(&mut self, rectangle: Rectangle) {
1250 self.raw_placement.rcNormalPosition = rectangle;
1251 }
1252}
1253
1254#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1255pub enum WindowZPosition {
1256 Bottom,
1257 NoTopMost,
1258 Top,
1259 TopMost,
1260}
1261
1262impl WindowZPosition {
1263 fn to_raw_hwnd(self) -> HWND {
1264 match self {
1265 WindowZPosition::Bottom => HWND_BOTTOM,
1266 WindowZPosition::NoTopMost => HWND_NOTOPMOST,
1267 WindowZPosition::Top => HWND_TOP,
1268 WindowZPosition::TopMost => HWND_TOPMOST,
1269 }
1270 }
1271}
1272
1273#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Debug)]
1275#[non_exhaustive]
1276#[repr(u32)]
1277pub enum WindowCommand {
1278 Close = SC_CLOSE,
1279 Maximize = SC_MAXIMIZE,
1280 Minimize = SC_MINIMIZE,
1281 Restore = SC_RESTORE,
1282}
1283
1284impl WindowCommand {
1285 fn to_usize(self) -> usize {
1286 usize::try_from(u32::from(self)).unwrap()
1287 }
1288}
1289
1290#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
1292#[repr(u32)]
1293pub enum FlashElement {
1294 Caption = FLASHW_CAPTION.0,
1295 Taskbar = FLASHW_TRAY.0,
1296 #[default]
1297 CaptionPlusTaskbar = FLASHW_ALL.0,
1298}
1299
1300impl FlashElement {
1301 fn to_flashwinfo_flags(self) -> FLASHWINFO_FLAGS {
1302 FLASHWINFO_FLAGS(u32::from(self))
1303 }
1304}
1305
1306#[derive(Copy, Clone, Eq, PartialEq, Debug)]
1308pub enum FlashDuration {
1309 Count(u32),
1310 CountUntilForeground(u32),
1311 ContinuousUntilForeground,
1312 Continuous,
1313}
1314
1315impl Default for FlashDuration {
1316 fn default() -> Self {
1317 FlashDuration::CountUntilForeground(5)
1318 }
1319}
1320
1321#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
1323pub enum FlashInterval {
1324 #[default]
1325 DefaultCursorBlinkInterval,
1326 Milliseconds(u32),
1327}
1328
1329#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
1333#[repr(isize)]
1334pub enum MonitorPower {
1335 #[default]
1336 On = -1,
1337 Low = 1,
1338 Off = 2,
1339}
1340
1341#[derive(Debug)]
1345pub struct NotificationIcon {
1346 id: NotificationIconId,
1347 window: WindowHandle,
1348 icon: Rc<Icon>,
1349}
1350
1351impl NotificationIcon {
1352 fn new(window: WindowHandle, options: NotificationIconOptions) -> io::Result<Self> {
1356 let call_data = Self::get_notification_call_data(
1359 window,
1360 options.icon_id,
1361 true,
1362 Some(options.icon.as_handle()),
1363 options.tooltip_text.as_deref(),
1364 Some(!options.visible),
1365 None,
1366 );
1367 unsafe {
1368 Shell_NotifyIconW(NIM_ADD, &raw const call_data)
1369 .if_null_to_error_else_drop(|| io::Error::other("Cannot add notification icon"))?;
1370 Shell_NotifyIconW(NIM_SETVERSION, &raw const call_data).if_null_to_error_else_drop(
1371 || io::Error::other("Cannot set notification version"),
1372 )?;
1373 };
1374 Ok(NotificationIcon {
1375 id: options.icon_id,
1376 window,
1377 icon: options.icon,
1378 })
1379 }
1380
1381 pub fn get_bounding_rectangle(&self) -> io::Result<Rectangle> {
1382 let identifier = self.get_raw_identifier();
1383 unsafe { Shell_NotifyIconGetRect(&raw const identifier).map_err(Into::into) }
1384 }
1385
1386 pub fn set_icon(&mut self, icon: Rc<Icon>) -> io::Result<()> {
1388 let call_data = Self::get_notification_call_data(
1389 self.window,
1390 self.id,
1391 false,
1392 Some(icon.as_handle()),
1393 None,
1394 None,
1395 None,
1396 );
1397 unsafe {
1398 Shell_NotifyIconW(NIM_MODIFY, &raw const call_data)
1399 .if_null_to_error_else_drop(|| io::Error::other("Cannot set notification icon"))?;
1400 };
1401 self.icon = icon;
1402 Ok(())
1403 }
1404
1405 pub fn set_icon_hidden_state(&mut self, hidden: bool) -> io::Result<()> {
1407 let call_data = Self::get_notification_call_data(
1408 self.window,
1409 self.id,
1410 false,
1411 None,
1412 None,
1413 Some(hidden),
1414 None,
1415 );
1416 unsafe {
1417 Shell_NotifyIconW(NIM_MODIFY, &raw const call_data).if_null_to_error_else_drop(
1418 || io::Error::other("Cannot set notification icon hidden state"),
1419 )?;
1420 };
1421 Ok(())
1422 }
1423
1424 pub fn set_tooltip_text(&mut self, text: &str) -> io::Result<()> {
1426 let call_data = Self::get_notification_call_data(
1427 self.window,
1428 self.id,
1429 false,
1430 None,
1431 Some(text),
1432 None,
1433 None,
1434 );
1435 unsafe {
1436 Shell_NotifyIconW(NIM_MODIFY, &raw const call_data).if_null_to_error_else_drop(
1437 || io::Error::other("Cannot set notification icon tooltip text"),
1438 )?;
1439 };
1440 Ok(())
1441 }
1442
1443 pub fn set_balloon_notification(
1445 &mut self,
1446 notification: Option<BalloonNotification>,
1447 ) -> io::Result<()> {
1448 let call_data = Self::get_notification_call_data(
1449 self.window,
1450 self.id,
1451 false,
1452 None,
1453 None,
1454 None,
1455 Some(notification),
1456 );
1457 unsafe {
1458 Shell_NotifyIconW(NIM_MODIFY, &raw const call_data).if_null_to_error_else_drop(
1459 || io::Error::other("Cannot set notification icon balloon text"),
1460 )?;
1461 };
1462 Ok(())
1463 }
1464
1465 fn get_raw_identifier(&self) -> NOTIFYICONIDENTIFIER {
1466 let (uid, guid_item) = match self.id {
1467 NotificationIconId::Simple(uid) => (uid, GUID::zeroed()),
1468 NotificationIconId::GUID(guid) => (0, guid),
1469 };
1470 NOTIFYICONIDENTIFIER {
1471 cbSize: std::mem::size_of::<NOTIFYICONIDENTIFIER>()
1472 .try_into()
1473 .unwrap_or_else(|_| unreachable!()),
1474 hWnd: self.window.into(),
1475 uID: uid.into(),
1476 guidItem: guid_item,
1477 }
1478 }
1479
1480 #[expect(clippy::option_option)]
1481 fn get_notification_call_data(
1482 window_handle: WindowHandle,
1483 icon_id: NotificationIconId,
1484 set_callback_message: bool,
1485 maybe_icon: Option<HICON>,
1486 maybe_tooltip_str: Option<&str>,
1487 icon_hidden_state: Option<bool>,
1488 maybe_balloon_text: Option<Option<BalloonNotification>>,
1489 ) -> NOTIFYICONDATAW {
1490 let mut icon_data = NOTIFYICONDATAW {
1491 cbSize: mem::size_of::<NOTIFYICONDATAW>()
1492 .try_into()
1493 .expect("NOTIFYICONDATAW size conversion failed"),
1494 hWnd: window_handle.into(),
1495 ..Default::default()
1496 };
1497 icon_data.Anonymous.uVersion = NOTIFYICON_VERSION_4;
1498 match icon_id {
1499 NotificationIconId::GUID(id) => {
1500 icon_data.guidItem = id;
1501 icon_data.uFlags |= NIF_GUID;
1502 }
1503 NotificationIconId::Simple(simple_id) => icon_data.uID = simple_id.into(),
1504 }
1505 if set_callback_message {
1506 icon_data.uCallbackMessage = super::messaging::RawMessage::ID_NOTIFICATION_ICON_MSG;
1507 icon_data.uFlags |= NIF_MESSAGE;
1508 }
1509 if let Some(icon) = maybe_icon {
1510 icon_data.hIcon = icon;
1511 icon_data.uFlags |= NIF_ICON;
1512 }
1513 if let Some(tooltip_str) = maybe_tooltip_str {
1514 let chars = to_wide_chars_iter(tooltip_str)
1515 .take(icon_data.szTip.len() - 1)
1516 .chain(std::iter::once(0))
1517 .enumerate();
1518 for (i, w_char) in chars {
1519 icon_data.szTip[i] = w_char;
1520 }
1521 icon_data.uFlags |= NIF_TIP;
1522 icon_data.uFlags |= NIF_SHOWTIP;
1524 }
1525 if let Some(hidden_state) = icon_hidden_state {
1526 if hidden_state {
1527 icon_data.dwState = NOTIFY_ICON_STATE(icon_data.dwState.0 | NIS_HIDDEN.0);
1528 icon_data.dwStateMask |= NIS_HIDDEN;
1529 }
1530 icon_data.uFlags |= NIF_STATE;
1531 }
1532 if let Some(set_balloon_notification) = maybe_balloon_text {
1533 if let Some(balloon) = set_balloon_notification {
1534 let body_chars = to_wide_chars_iter(balloon.body)
1535 .take(icon_data.szInfo.len() - 1)
1536 .chain(std::iter::once(0))
1537 .enumerate();
1538 for (i, w_char) in body_chars {
1539 icon_data.szInfo[i] = w_char;
1540 }
1541 let title_chars = to_wide_chars_iter(balloon.title)
1542 .take(icon_data.szInfoTitle.len() - 1)
1543 .chain(std::iter::once(0))
1544 .enumerate();
1545 for (i, w_char) in title_chars {
1546 icon_data.szInfoTitle[i] = w_char;
1547 }
1548 icon_data.dwInfoFlags =
1549 NOTIFY_ICON_INFOTIP_FLAGS(icon_data.dwInfoFlags.0 | u32::from(balloon.icon));
1550 }
1551 icon_data.uFlags |= NIF_INFO;
1552 }
1553 icon_data
1554 }
1555}
1556
1557impl Drop for NotificationIcon {
1558 fn drop(&mut self) {
1559 let call_data =
1560 Self::get_notification_call_data(self.window, self.id, false, None, None, None, None);
1561 unsafe { Shell_NotifyIconW(NIM_DELETE, &raw const call_data) }
1562 .if_null_get_last_error_else_drop()
1563 .unwrap_or_default_and_print_error();
1564 }
1565}
1566
1567#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
1569pub enum NotificationIconId {
1570 Simple(u16),
1572 GUID(GUID),
1576}
1577
1578impl Default for NotificationIconId {
1579 fn default() -> Self {
1580 NotificationIconId::Simple(0)
1581 }
1582}
1583
1584#[derive(Eq, PartialEq, Default, Debug)]
1586pub struct NotificationIconOptions {
1587 pub icon_id: NotificationIconId,
1588 pub icon: Rc<Icon>,
1589 pub tooltip_text: Option<String>,
1590 pub visible: bool,
1591}
1592
1593#[derive(Copy, Clone, Default, Debug)]
1597pub struct BalloonNotification<'a> {
1598 pub title: &'a str,
1599 pub body: &'a str,
1600 pub icon: BalloonNotificationStandardIcon,
1601}
1602
1603#[derive(IntoPrimitive, Copy, Clone, Default, Debug)]
1605#[repr(u32)]
1606pub enum BalloonNotificationStandardIcon {
1607 #[default]
1608 None = NIIF_NONE.0,
1609 Info = NIIF_INFO.0,
1610 Warning = NIIF_WARNING.0,
1611 Error = NIIF_ERROR.0,
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616 use more_asserts::*;
1617
1618 use super::*;
1619
1620 #[test]
1621 fn run_window_tests_without_parallelism() -> io::Result<()> {
1622 check_toplevel_windows()?;
1623 new_window_with_class()?;
1624 Ok(())
1625 }
1626
1627 fn check_toplevel_windows() -> io::Result<()> {
1628 let all_windows = WindowHandle::get_toplevel_windows()?;
1629 assert_gt!(all_windows.len(), 0);
1630 for window in all_windows {
1631 assert!(window.is_window());
1632 assert!(window.get_placement().is_ok());
1633 assert!(window.get_class_name().is_ok());
1634 std::hint::black_box(&window.get_caption_text());
1635 #[cfg(feature = "process")]
1636 std::hint::black_box(&window.get_creator_thread_process_ids());
1637 }
1638 Ok(())
1639 }
1640
1641 fn new_window_with_class() -> io::Result<()> {
1642 const CLASS_NAME_PREFIX: &str = "myclass1";
1643 const WINDOW_NAME: &str = "mywindow1";
1644 const CAPTION_TEXT: &str = "Testwindow";
1645
1646 let icon: Rc<Icon> = Default::default();
1647 let class: WindowClass = WindowClass::register_new(
1648 CLASS_NAME_PREFIX,
1649 WindowClassAppearance {
1650 icon: Some(Rc::clone(&icon)),
1651 ..Default::default()
1652 },
1653 )?;
1654 let mut window = Window::new::<DefaultWmlType, ()>(
1655 class.into(),
1656 None,
1657 WINDOW_NAME,
1658 WindowAppearance::default(),
1659 None,
1660 )?;
1661 let notification_icon_options = NotificationIconOptions {
1662 icon,
1663 tooltip_text: Some("A tooltip!".to_string()),
1664 visible: false,
1665 ..Default::default()
1666 };
1667 let notification_icon = window.add_notification_icon(notification_icon_options)?;
1668 let balloon_notification = BalloonNotification::default();
1669 notification_icon.set_balloon_notification(Some(balloon_notification))?;
1670
1671 let window_handle = window.as_handle();
1672 assert!(!window_handle.is_visible());
1673 assert!(!window_handle.is_cloaked()?);
1674 assert_eq!(window_handle.get_caption_text(), WINDOW_NAME);
1675 window_handle.set_caption_text(CAPTION_TEXT)?;
1676 assert_eq!(window_handle.get_caption_text(), CAPTION_TEXT);
1677 assert!(dbg!(window_handle.get_class_name()?).starts_with(CLASS_NAME_PREFIX));
1678 assert!(window_handle.get_client_area_coords()?.left >= 0);
1679
1680 Ok(())
1681 }
1682}