1use 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#[derive(Eq, PartialEq, Debug)]
181pub struct WindowHandle {
182 raw_handle: HWND,
183 marker: PhantomData<*mut ()>,
184}
185
186impl WindowHandle {
187 pub fn get_console_window() -> Option<Self> {
189 let handle = unsafe { GetConsoleWindow() };
190 Self::from_maybe_null(handle)
191 }
192
193 pub fn get_foreground_window() -> Option<Self> {
195 let handle = unsafe { GetForegroundWindow() };
196 Self::from_maybe_null(handle)
197 }
198
199 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 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 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 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 buffer.truncate(copied_chars as usize);
266 buffer.to_string_lossy()
267 }
268
269 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 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 pub fn set_as_active(&self) -> io::Result<()> {
296 unsafe {
297 SetActiveWindow(self.raw_handle)?;
298 }
299 Ok(())
300 }
301
302 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 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 pub fn set_placement(&self, placement: &WindowPlacement) -> io::Result<()> {
329 unsafe { SetWindowPlacement(self.raw_handle, &placement.raw_placement)? };
330 Ok(())
331 }
332
333 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 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 #[inline(always)]
361 pub fn flash(&self) {
362 self.flash_custom(Default::default(), Default::default(), Default::default())
363 }
364
365 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 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 #[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 #[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 #[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 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 fn from(value: WindowHandle) -> Self {
490 value.raw_handle
491 }
492}
493
494impl From<&WindowHandle> for HWND {
495 fn from(value: &WindowHandle) -> Self {
497 value.raw_handle
498 }
499}
500
501impl TryFrom<HWND> for WindowHandle {
502 type Error = TryFromHWNDError;
503
504 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#[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 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 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 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#[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 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 pub fn set_listener(&self, listener: &'listener WML) -> io::Result<()> {
673 unsafe { self.handle.set_user_data_ptr(listener) }
674 }
675
676 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 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#[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
762pub type Point = POINT;
764pub type Rectangle = RECT;
766
767#[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#[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#[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#[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#[derive(Copy, Clone, Eq, PartialEq, Default, Debug)]
861pub enum FlashInterval {
862 #[default]
863 DefaultCursorBlinkInterval,
864 Milliseconds(u32),
865}
866
867#[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
879pub struct NotificationIcon<'a, WML> {
883 id: NotificationIconId,
884 window: &'a Window<'a, 'a, WML>,
885}
886
887impl<'a, WML> NotificationIcon<'a, WML> {
888 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 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 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 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 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#[derive(Copy, Clone, Eq, PartialEq, Debug)]
1068pub enum NotificationIconId {
1069 Simple(u16),
1071 GUID(GUID),
1075}
1076
1077impl Default for NotificationIconId {
1078 fn default() -> Self {
1079 NotificationIconId::Simple(0)
1080 }
1081}
1082
1083#[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#[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#[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#[derive(IntoPrimitive, Copy, Clone, Eq, PartialEq, Default, Debug)]
1115#[repr(i32)]
1116pub enum ProgressState {
1117 #[default]
1119 NoProgress = TBPF_NOPROGRESS.0,
1120 Indeterminate = TBPF_INDETERMINATE.0,
1122 Normal = TBPF_NORMAL.0,
1124 Error = TBPF_ERROR.0,
1128 Paused = TBPF_PAUSED.0,
1132}
1133
1134impl From<ProgressState> for TBPFLAG {
1135 fn from(value: ProgressState) -> Self {
1136 TBPFLAG(value.into())
1137 }
1138}
1139
1140pub 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 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 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
1209pub fn allocate_console() -> io::Result<()> {
1211 unsafe {
1212 AllocConsole()?;
1213 }
1214 Ok(())
1215}
1216
1217pub fn detach_console() -> io::Result<()> {
1221 unsafe {
1222 FreeConsole()?;
1223 }
1224 Ok(())
1225}
1226
1227pub fn lock_workstation() -> io::Result<()> {
1229 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}