1#![allow(dead_code)] mod number_select;
5
6use std::{
7 any::Any,
8 borrow::Cow,
9 cell::{Cell, RefCell},
10 cmp::Ordering,
11 collections::BTreeMap,
12 mem,
13 ops::ControlFlow,
14 ptr::null_mut,
15 sync::{
16 atomic::AtomicBool,
17 mpsc::{self, RecvTimeoutError},
18 Arc, OnceLock,
19 },
20 time::{Duration, Instant},
21};
22
23use nwg::ControlHandle;
24use windows::Win32::Foundation::{HWND, RECT};
25
26pub use number_select::{NumberSelect2, NumberSelectBuilder};
27
28pub fn to_utf16(s: &str) -> Vec<u16> {
30 use std::ffi::OsStr;
31 use std::os::windows::ffi::OsStrExt;
32
33 OsStr::new(s)
34 .encode_wide()
35 .chain(core::iter::once(0u16))
36 .collect()
37}
38pub fn from_utf16(s: &[u16]) -> String {
42 use std::os::windows::ffi::OsStringExt;
43
44 let null_index = s.iter().position(|&i| i == 0).unwrap_or(s.len());
45 let os_string = std::ffi::OsString::from_wide(&s[0..null_index]);
46
47 os_string
48 .into_string()
49 .unwrap_or("Decoding error".to_string())
50}
51
52pub struct PanicCatcher {
55 caught: Option<Box<dyn Any + Send + 'static>>,
56}
57impl PanicCatcher {
58 pub const fn new() -> Self {
59 Self { caught: None }
60 }
61 fn drop_without_unwind(value: Box<dyn Any + Send + 'static>) {
62 struct SafeDrop(Option<Box<dyn Any + Send + 'static>>);
63 impl Drop for SafeDrop {
64 fn drop(&mut self) {
65 while let Some(value) = self.0.take() {
66 if let Err(payload) =
67 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(value)))
68 {
69 self.0 = Some(payload);
70 }
71 }
72 }
73 }
74 drop(SafeDrop(Some(value)));
75 }
76 pub fn has_caught_panic(&self) -> bool {
77 self.caught.is_some()
78 }
79 pub fn catch<R, F: FnOnce() -> R>(&mut self, f: F) -> Option<R> {
81 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
82 Ok(res) => Some(res),
83 Err(e) => {
84 if let Some(old) = std::mem::replace(&mut self.caught, Some(e)) {
85 Self::drop_without_unwind(old);
86 }
87 None
88 }
89 }
90 }
91 pub fn resume_panic(&mut self) {
93 if let Some(e) = self.caught.take() {
94 std::panic::resume_unwind(e);
95 }
96 }
97}
98impl Default for PanicCatcher {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104pub fn enum_child_windows<F: FnMut(HWND) -> ControlFlow<()>>(parent: Option<HWND>, f: F) {
117 use windows::Win32::{
118 Foundation::{BOOL, HWND, LPARAM},
119 UI::WindowsAndMessaging::EnumChildWindows,
120 };
121
122 struct State<F> {
123 f: F,
124 catcher: PanicCatcher,
125 }
126
127 unsafe extern "system" fn enumerate_windows<F: FnMut(HWND) -> ControlFlow<()>>(
128 window: HWND,
129 state: LPARAM,
130 ) -> BOOL {
131 let state = state.0 as *mut State<F>;
132 let state: &mut State<F> = unsafe { &mut *state };
133 let result = state
134 .catcher
135 .catch(|| (state.f)(window))
136 .unwrap_or(ControlFlow::Break(()));
137 BOOL::from(result.is_continue())
138 }
139
140 let mut state = State {
141 f,
142 catcher: PanicCatcher::new(),
143 };
144
145 unsafe {
146 let _ = EnumChildWindows(
147 parent.unwrap_or(HWND(null_mut())),
148 Some(enumerate_windows::<F>),
149 LPARAM(&mut state as *mut State<F> as isize),
150 );
151 }
152
153 state.catcher.resume_panic();
154}
155
156pub fn menu_item_index_in_parent(item_handle: ControlHandle) -> Option<u32> {
161 if item_handle.blank() {
162 return None;
163 }
164 let (parent, id) = item_handle.hmenu_item()?;
165
166 use windows::Win32::UI::WindowsAndMessaging::{GetMenuItemCount, GetMenuItemID, HMENU};
167
168 let parent = HMENU(parent.cast());
169 let children_count = unsafe { GetMenuItemCount(parent) };
170
171 for i in 0..children_count {
172 let item_id = unsafe { GetMenuItemID(parent, i) };
173 if item_id == (-1_i32 as u32) {
174 continue;
175 } else if item_id == id {
176 return Some(i as u32);
177 }
178 }
179
180 None
181}
182
183pub fn menu_index_in_parent(menu_handle: ControlHandle) -> Option<u32> {
189 if menu_handle.blank() {
190 return None;
191 }
192 let (parent, menu) = menu_handle.hmenu()?;
193
194 use windows::Win32::UI::WindowsAndMessaging::{GetMenuItemCount, GetSubMenu, HMENU};
197
198 let parent = HMENU(parent.cast());
199 let children_count = unsafe { GetMenuItemCount(parent) };
200 let mut sub_menu;
201
202 for i in 0..children_count {
203 sub_menu = unsafe { GetSubMenu(parent, i) };
204 if sub_menu.0 == null_mut() {
205 continue;
206 } else if sub_menu.0 == (menu.cast()) {
207 return Some(i as u32);
208 }
209 }
210
211 None
212}
213
214pub fn menu_set_text(handle: ControlHandle, text: &str) {
216 if handle.blank() {
217 panic!("Unbound handle");
218 }
219 enum MenuItemInfo {
220 Position(u32),
221 Id(u32),
222 }
223 let (parent, item_info) = match handle {
224 ControlHandle::Menu(parent, _) => {
225 if let Some(index) = menu_index_in_parent(handle) {
230 (parent, MenuItemInfo::Position(index))
231 } else {
232 return;
233 }
234 }
235 ControlHandle::MenuItem(parent, id) => (parent, MenuItemInfo::Id(id)),
236 _ => return,
237 };
238
239 use windows::{
240 core::PWSTR,
241 Win32::UI::WindowsAndMessaging::{SetMenuItemInfoW, HMENU, MENUITEMINFOW, MIIM_STRING},
242 };
243
244 let use_position = matches!(item_info, MenuItemInfo::Position(_));
248 let value = match item_info {
249 MenuItemInfo::Position(p) => p,
250 MenuItemInfo::Id(id) => id,
251 };
252
253 let text = to_utf16(text);
254
255 let mut info = MENUITEMINFOW::default();
256 info.cbSize = core::mem::size_of_val(&info) as u32;
257 info.fMask = MIIM_STRING;
258 info.dwTypeData = PWSTR(text.as_ptr().cast_mut());
259
260 let _ = unsafe { SetMenuItemInfoW(HMENU(parent as _), value, use_position, &info) };
261}
262
263pub fn menu_remove(menu: &nwg::Menu) {
266 if menu.handle.blank() {
267 return;
268 }
269 let Some((parent, _)) = menu.handle.hmenu() else {
270 return;
271 };
272
273 let Some(index) = menu_index_in_parent(menu.handle) else {
274 return;
275 };
276
277 use windows::Win32::UI::WindowsAndMessaging::{RemoveMenu, HMENU, MF_BYPOSITION};
278
279 let _ = unsafe { RemoveMenu(HMENU(parent.cast()), index, MF_BYPOSITION) };
280}
281
282pub fn find_context_menu_window() -> Option<HWND> {
298 use windows::{
299 core::PCWSTR,
300 Win32::{Foundation::HWND, UI::WindowsAndMessaging::FindWindowW},
301 };
302
303 static CONTEXT_MENU_CLASS_NAME: OnceLock<Vec<u16>> = OnceLock::new();
304 let class_name = CONTEXT_MENU_CLASS_NAME.get_or_init(|| {
305 let mut t = to_utf16("#32768");
306 t.shrink_to_fit();
307 t
308 });
309
310 let window = unsafe { FindWindowW(PCWSTR::from_raw(class_name.as_ptr()), None) }.ok()?;
311 if window == HWND::default() {
312 None
313 } else {
314 Some(window)
315 }
316}
317
318pub fn window_is_valid(handle: nwg::ControlHandle) -> bool {
324 if handle.blank() {
325 return false;
326 }
327 let Some(hwnd) = handle.hwnd() else {
328 return false;
329 };
330 unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(HWND(hwnd.cast())) }.as_bool()
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
334pub enum WindowPlacement {
335 Normal,
336 Maximized,
337 Minimized,
338}
339
340pub fn window_placement(window: &nwg::Window) -> windows::core::Result<WindowPlacement> {
347 use windows::Win32::UI::WindowsAndMessaging::{
348 GetWindowPlacement, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, SW_SHOWNORMAL, WINDOWPLACEMENT,
349 };
350
351 let handle = window.handle.hwnd().expect("Not a window handle");
352
353 let mut info = WINDOWPLACEMENT {
354 length: core::mem::size_of::<WINDOWPLACEMENT>() as u32,
355 ..WINDOWPLACEMENT::default()
356 };
357
358 unsafe { GetWindowPlacement(HWND(handle.cast()), &mut info) }.inspect_err(|e| {
359 tracing::error!(error = e.to_string(), "GetWindowPlacement failed");
360 })?;
361
362 Ok(if info.showCmd == SW_SHOWMAXIMIZED.0 as u32 {
363 WindowPlacement::Maximized
364 } else if info.showCmd == SW_SHOWMINIMIZED.0 as u32 {
365 WindowPlacement::Minimized
366 } else if info.showCmd == SW_SHOWNORMAL.0 as u32 {
367 WindowPlacement::Normal
368 } else {
369 tracing::error!(
370 showCmd = info.showCmd,
371 "Invalid return value from GetWindowPlacement"
372 );
373 WindowPlacement::Normal
374 })
375}
376
377pub fn tray_set_version_4(tray: &nwg::TrayNotification) {
387 use windows::Win32::UI::Shell::{
388 Shell_NotifyIconW, NIM_SETVERSION, NOTIFYICONDATAW, NOTIFYICON_VERSION_4,
389 };
390
391 const NOT_BOUND: &str = "TrayNotification is not yet bound to a winapi object";
392 const BAD_HANDLE: &str = "INTERNAL ERROR: TrayNotification handle is not HWND!";
393
394 if tray.handle.blank() {
395 panic!("{}", NOT_BOUND);
396 }
397
398 let parent = tray.handle.tray().expect(BAD_HANDLE);
399 let mut data = NOTIFYICONDATAW {
400 hWnd: HWND(parent.cast()),
401 ..Default::default()
402 };
403 data.Anonymous.uVersion = NOTIFYICON_VERSION_4;
404 data.cbSize = mem::size_of_val(&data) as u32;
405
406 let success = unsafe { Shell_NotifyIconW(NIM_SETVERSION, &data) };
407 if !success.as_bool() {
408 tracing::error!("Failed to set tray version to 4");
409 }
410}
411
412#[inline]
413pub fn tray_get_rect(tray: &nwg::TrayNotification) -> windows::core::Result<RECT> {
414 use windows::Win32::UI::Shell::{Shell_NotifyIconGetRect, NOTIFYICONIDENTIFIER};
415
416 const NOT_BOUND: &str = "TrayNotification is not yet bound to a winapi object";
417 const BAD_HANDLE: &str = "INTERNAL ERROR: TrayNotification handle is not HWND!";
418
419 if tray.handle.blank() {
420 panic!("{}", NOT_BOUND);
421 }
422 let parent = tray.handle.tray().expect(BAD_HANDLE);
423
424 let nid = NOTIFYICONIDENTIFIER {
425 hWnd: HWND(parent.cast()),
426 cbSize: std::mem::size_of::<NOTIFYICONIDENTIFIER>() as _,
427 ..NOTIFYICONIDENTIFIER::default()
428 };
429
430 unsafe { Shell_NotifyIconGetRect(&nid) }
431}
432
433pub fn list_view_sort_rows<F>(list_view: &nwg::ListView, f: F)
448where
449 F: FnMut(usize, usize) -> Ordering,
450{
451 use windows::Win32::{
452 Foundation::{LPARAM, LRESULT, WPARAM},
453 UI::{
454 Controls::LVM_SORTITEMSEX,
455 WindowsAndMessaging::{IsWindow, SendMessageW},
456 },
457 };
458
459 const NOT_BOUND: &str = "ListView is not yet bound to a winapi object";
460 const BAD_HANDLE: &str = "INTERNAL ERROR: ListView handle is not HWND!";
461
462 fn check_hwnd(handle: &ControlHandle, not_bound: &str, bad_handle: &str) -> HWND {
464 if handle.blank() {
465 panic!("{}", not_bound);
466 }
467 match handle.hwnd() {
468 Some(hwnd) => {
469 if unsafe { IsWindow(HWND(hwnd.cast())) }.as_bool() {
470 HWND(hwnd.cast())
471 } else {
472 panic!("The window handle is no longer valid. This usually means the control was freed by the OS");
473 }
474 }
475 None => {
476 panic!("{}", bad_handle);
477 }
478 }
479 }
480 let handle = check_hwnd(&list_view.handle, NOT_BOUND, BAD_HANDLE);
481
482 struct State<F> {
483 f: F,
484 catcher: PanicCatcher,
485 }
486
487 unsafe extern "system" fn compare_func<F: FnMut(usize, usize) -> Ordering>(
488 index1: LPARAM,
489 index2: LPARAM,
490 state: LPARAM,
491 ) -> LRESULT {
492 let state = state.0 as *mut State<F>;
493 let state: &mut State<F> = unsafe { &mut *state };
494 if state.catcher.has_caught_panic() {
495 return LRESULT(0);
496 }
497 let result = state
498 .catcher
499 .catch(|| (state.f)(index1.0 as usize, index2.0 as usize))
500 .unwrap_or(Ordering::Equal);
501 LRESULT(match result {
502 Ordering::Less => -1,
503 Ordering::Equal => 0,
504 Ordering::Greater => 1,
505 })
506 }
507
508 let mut state = State {
509 f,
510 catcher: PanicCatcher::new(),
511 };
512
513 unsafe {
514 SendMessageW(
515 handle,
516 LVM_SORTITEMSEX,
517 WPARAM(&mut state as *mut State<F> as usize),
518 LPARAM(compare_func::<F> as usize as isize),
519 )
520 };
521
522 state.catcher.resume_panic();
523}
524
525pub fn list_view_enable_groups(list_view: &nwg::ListView, enable: bool) {
533 if !window_is_valid(list_view.handle) {
534 tracing::error!("Tried to toggle groups for invalid list view");
535 return;
536 }
537 let Some(handle) = list_view.handle.hwnd() else {
538 tracing::error!("Tried to toggle groups for invalid list view");
539 return;
540 };
541 let result = unsafe {
542 windows::Win32::UI::WindowsAndMessaging::SendMessageW(
543 HWND(handle.cast()),
544 windows::Win32::UI::Controls::LVM_ENABLEGROUPVIEW,
545 windows::Win32::Foundation::WPARAM(enable as usize),
546 windows::Win32::Foundation::LPARAM(0),
547 )
548 };
549 match result.0 {
550 0 => tracing::trace!(
551 "Groups in list view was already {}",
552 if enable { "enabled" } else { "disabled" }
553 ),
554 1 => tracing::trace!(
555 "Groups in list view was successfully {}",
556 if enable { "enabled" } else { "disabled" }
557 ),
558 -1 => tracing::error!("Failed to enable/disable groups in list view"),
559 _ => {
560 tracing::error!(result =? result.0, "Unexpected return value when toggling groups in list view")
561 }
562 }
563}
564
565#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
566pub enum ListViewGroupAlignment {
567 #[default]
568 Left,
569 Center,
570 Right,
571}
572
573#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
574pub struct ListViewGroupInfo<'a> {
575 pub group_id: i32,
576 pub create_new: bool,
579 pub header: Option<Cow<'a, str>>,
580 pub subtitle: Option<Cow<'a, str>>,
582 pub footer: Option<Cow<'a, str>>,
583 pub task: Option<Cow<'a, str>>,
586 pub description_top: Option<Cow<'a, str>>,
589 pub description_bottom: Option<Cow<'a, str>>,
592 pub header_alignment: Option<ListViewGroupAlignment>,
593 pub footer_alignment: Option<ListViewGroupAlignment>,
595 pub collapsed: Option<bool>,
597 pub hidden: Option<bool>,
599 pub no_header: Option<bool>,
601 pub collapsible: Option<bool>,
603 pub focused: Option<bool>,
605 pub selected: Option<bool>,
607 pub subseted: Option<bool>,
609 pub subset_link_focused: Option<bool>,
611}
612
613pub fn list_view_set_group_info(list_view: &nwg::ListView, info: ListViewGroupInfo) {
627 use windows::{
628 core::PWSTR,
629 Win32::{
630 Foundation::{LPARAM, WPARAM},
631 UI::{
632 Controls::{
633 LVGA_FOOTER_CENTER, LVGA_FOOTER_LEFT, LVGA_FOOTER_RIGHT, LVGA_HEADER_CENTER,
634 LVGA_HEADER_LEFT, LVGA_HEADER_RIGHT, LVGF_ALIGN, LVGF_DESCRIPTIONBOTTOM,
635 LVGF_DESCRIPTIONTOP, LVGF_FOOTER, LVGF_GROUPID, LVGF_HEADER, LVGF_NONE,
636 LVGF_STATE, LVGF_SUBTITLE, LVGF_TASK, LVGROUP, LVGS_COLLAPSED,
637 LVGS_COLLAPSIBLE, LVGS_FOCUSED, LVGS_HIDDEN, LVGS_NOHEADER, LVGS_SELECTED,
638 LVGS_SUBSETED, LVGS_SUBSETLINKFOCUSED, LVM_INSERTGROUP, LVM_SETGROUPINFO,
639 },
640 WindowsAndMessaging::SendMessageW,
641 },
642 },
643 };
644
645 if !window_is_valid(list_view.handle) {
646 tracing::error!("Tried to create/update group for invalid list view");
647 return;
648 }
649 let Some(handle) = list_view.handle.hwnd() else {
650 tracing::error!("Tried to create/update group for invalid list view");
651 return;
652 };
653 let mut item = LVGROUP {
654 cbSize: core::mem::size_of::<LVGROUP>() as u32,
655 mask: if info.create_new {
656 LVGF_GROUPID
657 } else {
658 LVGF_NONE
659 },
660 iGroupId: info.group_id,
661 ..Default::default()
662 };
663
664 macro_rules! set_str {
669 (Options {
670 mask: $mask:expr,
671 input: $input:expr,
672 str_field: $str:ident,
673 }) => {
674 let mut __temp_buffer;
675 if let Some(text_utf8) = $input {
676 item.mask |= $mask;
677 __temp_buffer = to_utf16(text_utf8);
678 item.$str = PWSTR::from_raw(__temp_buffer.as_mut_ptr());
679 }
680 };
681 }
682
683 set_str!(Options {
684 mask: LVGF_HEADER,
685 input: &info.header,
686 str_field: pszHeader,
687 });
688 set_str!(Options {
689 mask: LVGF_FOOTER,
690 input: &info.footer,
691 str_field: pszFooter,
692 });
693 set_str!(Options {
694 mask: LVGF_SUBTITLE,
695 input: &info.subtitle,
696 str_field: pszSubtitle,
697 });
698 set_str!(Options {
699 mask: LVGF_TASK,
700 input: &info.task,
701 str_field: pszTask,
702 });
703 set_str!(Options {
704 mask: LVGF_DESCRIPTIONTOP,
705 input: &info.description_top,
706 str_field: pszDescriptionTop,
707 });
708 set_str!(Options {
709 mask: LVGF_DESCRIPTIONBOTTOM,
710 input: &info.description_bottom,
711 str_field: pszDescriptionBottom,
712 });
713
714 if info.header_alignment.is_some() || info.footer_alignment.is_some() {
715 item.mask |= LVGF_ALIGN;
716 }
717 if let Some(header_align) = info.header_alignment {
718 item.uAlign = match header_align {
719 ListViewGroupAlignment::Left => LVGA_HEADER_LEFT,
720 ListViewGroupAlignment::Center => LVGA_HEADER_CENTER,
721 ListViewGroupAlignment::Right => LVGA_HEADER_RIGHT,
722 };
723 } else if info.footer_alignment.is_some() {
724 item.uAlign = LVGA_HEADER_LEFT;
725 }
726 if let Some(footer_align) = info.footer_alignment {
727 item.uAlign |= match footer_align {
728 ListViewGroupAlignment::Left => LVGA_FOOTER_LEFT,
729 ListViewGroupAlignment::Center => LVGA_FOOTER_CENTER,
730 ListViewGroupAlignment::Right => LVGA_FOOTER_RIGHT,
731 };
732 }
733
734 let state_flags = [
735 (info.collapsed, LVGS_COLLAPSED),
736 (info.hidden, LVGS_HIDDEN),
737 (info.no_header, LVGS_NOHEADER),
738 (info.collapsible, LVGS_COLLAPSIBLE),
739 (info.focused, LVGS_FOCUSED),
740 (info.selected, LVGS_SELECTED),
741 (info.subseted, LVGS_SUBSETED),
742 (info.subset_link_focused, LVGS_SUBSETLINKFOCUSED),
743 ];
744 if state_flags.iter().any(|(v, _)| v.is_some()) {
745 item.mask |= LVGF_STATE;
746 for (value, flag) in state_flags {
747 if let Some(value) = value {
748 item.stateMask |= flag;
749 if value {
750 item.state |= flag;
751 }
752 }
753 }
754 }
755
756 let res = unsafe {
757 if info.create_new {
758 SendMessageW(
759 HWND(handle.cast()),
760 LVM_INSERTGROUP,
761 WPARAM((-1_isize) as usize),
763 LPARAM(&mut item as *mut LVGROUP as _),
764 )
765 } else {
766 SendMessageW(
767 HWND(handle.cast()),
768 LVM_SETGROUPINFO,
769 WPARAM(info.group_id as _),
770 LPARAM(&mut item as *mut LVGROUP as _),
771 )
772 }
773 };
774 if res.0 == -1 {
775 tracing::error!(
776 create_new = info.create_new,
777 "Failed to create or update group for list view"
778 );
779 }
780 }
782
783pub fn list_view_item_set_group_id(
793 list_view: &nwg::ListView,
794 row_index: usize,
795 group_id: Option<i32>,
796) {
797 use windows::Win32::{
798 Foundation::{LPARAM, WPARAM},
799 UI::{
800 Controls::{I_GROUPIDNONE, LVIF_GROUPID, LVITEMW, LVM_SETITEMW},
801 WindowsAndMessaging::SendMessageW,
802 },
803 };
804
805 if !list_view.has_item(row_index, 0) {
806 tracing::error!(
807 row_index,
808 "Tried to set group id for row that didn't exist."
809 );
810 return;
811 }
812 if !window_is_valid(list_view.handle) {
813 tracing::error!("Tried to set group id for item inside invalid list view");
814 return;
815 }
816 let Some(handle) = list_view.handle.hwnd() else {
817 tracing::error!("Tried to set group id for item inside invalid list view");
818 return;
819 };
820 let mut item = LVITEMW {
821 mask: LVIF_GROUPID,
822 iGroupId: group_id.unwrap_or(I_GROUPIDNONE.0),
823 iItem: row_index as _,
824 iSubItem: 0,
825 ..Default::default()
826 };
827
828 let res = unsafe {
829 SendMessageW(
830 HWND(handle.cast()),
831 LVM_SETITEMW,
832 WPARAM(0),
833 LPARAM(&mut item as *mut LVITEMW as _),
834 )
835 };
836 if res.0 == 0 {
837 tracing::error!("Failed to set group id for list view item");
838 }
839}
840
841pub fn list_view_item_get_group_id(list_view: &nwg::ListView, row_index: usize) -> i32 {
851 use windows::Win32::{
852 Foundation::{LPARAM, WPARAM},
853 UI::{
854 Controls::{I_GROUPIDNONE, LVIF_GROUPID, LVITEMW, LVM_GETITEMW},
855 WindowsAndMessaging::SendMessageW,
856 },
857 };
858
859 if !window_is_valid(list_view.handle) {
860 tracing::error!("Tried to set group id for item inside invalid list view");
861 return I_GROUPIDNONE.0;
862 }
863 let Some(handle) = list_view.handle.hwnd() else {
864 tracing::error!("Tried to set group id for item inside invalid list view");
865 return I_GROUPIDNONE.0;
866 };
867 let mut item = LVITEMW {
868 mask: LVIF_GROUPID,
869 iItem: row_index as _,
870 iSubItem: 0,
871 ..Default::default()
872 };
873
874 let res = unsafe {
875 SendMessageW(
876 HWND(handle.cast()),
877 LVM_GETITEMW,
878 WPARAM(0),
879 LPARAM(&mut item as *mut LVITEMW as _),
880 )
881 };
882 if res.0 == 0 {
883 I_GROUPIDNONE.0
885 } else {
886 item.iGroupId
887 }
888}
889
890pub fn windows_msg_for_explorer_restart() -> u32 {
901 static TASKBAR_RESTART_MSG: OnceLock<u32> = OnceLock::new();
902 *TASKBAR_RESTART_MSG.get_or_init(|| {
903 let msg = unsafe {
904 windows::Win32::UI::WindowsAndMessaging::RegisterWindowMessageA(windows::core::s!(
905 "TaskbarCreated"
906 ))
907 };
908 if msg == 0 {
909 tracing::error!(
910 error = ?windows::core::Error::from_win32(),
911 "Called \"RegisterWindowMessageA\" with \"TaskbarCreated\" and failed!"
912 );
913 } else {
914 tracing::debug!(
915 msg = ?msg,
916 "Called \"RegisterWindowMessageA\" with \"TaskbarCreated\" and succeeded"
917 );
918 }
919 msg
920 })
921}
922
923#[derive(Default, PartialEq, Eq)]
931pub struct TrayWindow {
932 pub handle: ControlHandle,
933}
934impl TrayWindow {
935 pub fn builder() -> TrayWindowBuilder {
936 TrayWindowBuilder {}
937 }
938}
939impl Drop for TrayWindow {
940 fn drop(&mut self) {
941 self.handle.destroy();
942 }
943}
944impl<'a> From<&'a TrayWindow> for nwg::ControlHandle {
945 fn from(value: &'a TrayWindow) -> Self {
946 value.handle
947 }
948}
949impl nwg::PartialUi for TrayWindow {
953 fn build_partial<W: Into<ControlHandle>>(
954 data: &mut Self,
955 parent: Option<W>,
956 ) -> Result<(), nwg::NwgError> {
957 let mut b = Self::builder();
958 if let Some(p) = parent {
959 b = b.parent(p.into());
960 }
961 b.build(data)
962 }
963
964 fn handles(&self) -> Vec<&ControlHandle> {
965 vec![&self.handle]
966 }
967}
968
969#[non_exhaustive]
970pub struct TrayWindowBuilder {}
971
972impl TrayWindowBuilder {
973 pub fn parent<C: Into<ControlHandle>>(self, _p: C) -> Self {
974 self
976 }
977 pub fn build(self, out: &mut TrayWindow) -> Result<(), nwg::NwgError> {
978 use windows::Win32::UI::WindowsAndMessaging::*;
979
980 *out = Default::default();
981 out.handle = nwg::ControlBase::build_hwnd()
982 .class_name(nwg::Window::class_name(&Default::default()))
983 .ex_flags(
984 WS_EX_NOACTIVATE.0 | WS_EX_TRANSPARENT.0 | WS_EX_LAYERED.0 |
987 WS_EX_TOOLWINDOW.0,
995 )
996 .flags(WS_OVERLAPPED.0)
997 .size((CW_USEDEFAULT, 0))
998 .position((CW_USEDEFAULT, 0))
999 .text("")
1000 .build()?;
1001
1002 Ok(())
1003 }
1004}
1005
1006pub trait LazyUiHooks {
1007 fn set_parent(&mut self, _parent: Option<ControlHandle>) {}
1008 fn eager_build(&mut self) -> bool {
1011 false
1012 }
1013}
1014#[derive(Default)]
1020pub struct LazyUi<T> {
1021 pub ui: RefCell<T>,
1022 pub latest_parent: Cell<Option<ControlHandle>>,
1023 pub is_built: Cell<bool>,
1024}
1025impl<T> LazyUi<T> {
1026 pub fn clear(&self)
1028 where
1029 T: Default,
1030 {
1031 *self.ui.borrow_mut() = T::default();
1032 self.is_built.set(false);
1033 }
1034 pub fn build_with_latest_parent(&self) -> Result<(), nwg::NwgError>
1038 where
1039 T: LazyUiHooks + nwg::PartialUi,
1040 {
1041 self.build(self.latest_parent.get())
1042 }
1043 pub fn build(&self, parent: Option<ControlHandle>) -> Result<(), nwg::NwgError>
1046 where
1047 T: LazyUiHooks + nwg::PartialUi,
1048 {
1049 let mut this = self.ui.borrow_mut();
1050 self.latest_parent.set(parent);
1051 this.set_parent(parent);
1052 <T as nwg::PartialUi>::build_partial(&mut *this, parent)?;
1053 self.is_built.set(true);
1054 Ok(())
1055 }
1056}
1057impl<T> nwg::PartialUi for LazyUi<T>
1058where
1059 T: LazyUiHooks + nwg::PartialUi,
1060{
1061 fn build_partial<W: Into<ControlHandle>>(
1062 data: &mut Self,
1063 parent: Option<W>,
1064 ) -> Result<(), nwg::NwgError> {
1065 let parent = parent.map(Into::into);
1066 data.latest_parent.set(parent);
1067 data.ui.get_mut().set_parent(parent);
1068 if data.ui.get_mut().eager_build() {
1069 nwg::PartialUi::build_partial(data.ui.get_mut(), parent)?;
1070 }
1071 Ok(())
1072 }
1073
1074 fn process_event(&self, evt: nwg::Event, evt_data: &nwg::EventData, handle: ControlHandle) {
1075 match self.ui.try_borrow() {
1076 Ok(ui) => ui.process_event(evt, evt_data, handle),
1077 Err(e) => tracing::error!(
1080 "Failed to process event {evt:?} on a `LazyUi<{}>` type: {e}",
1081 std::any::type_name::<T>()
1082 ),
1083 }
1084 }
1085
1086 fn handles(&self) -> Vec<&'_ ControlHandle> {
1087 vec![]
1088 }
1089}
1090impl<T> core::ops::Deref for LazyUi<T> {
1091 type Target = RefCell<T>;
1092 fn deref(&self) -> &Self::Target {
1093 &self.ui
1094 }
1095}
1096impl<T> core::ops::DerefMut for LazyUi<T> {
1097 fn deref_mut(&mut self) -> &mut Self::Target {
1098 &mut self.ui
1099 }
1100}
1101
1102#[derive(Default, PartialEq, Eq)]
1107pub struct ParentCapture {
1108 pub captured_parent: Option<ControlHandle>,
1109}
1110impl ParentCapture {
1111 pub fn builder() -> ParentCaptureBuilder {
1112 ParentCaptureBuilder(Self::default())
1113 }
1114 pub fn handle(&self) -> ControlHandle {
1115 self.captured_parent.unwrap_or(ControlHandle::NoHandle)
1116 }
1117}
1118pub struct ParentCaptureBuilder(ParentCapture);
1119impl ParentCaptureBuilder {
1120 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> Self {
1121 self.0.captured_parent = Some(p.into());
1122 self
1123 }
1124 pub fn build(self, out: &mut ParentCapture) -> Result<(), nwg::NwgError> {
1125 *out = self.0;
1126 Ok(())
1127 }
1128}
1129impl From<&ParentCapture> for ControlHandle {
1130 fn from(control: &ParentCapture) -> Self {
1131 control.handle()
1132 }
1133}
1134impl From<&mut ParentCapture> for ControlHandle {
1135 fn from(control: &mut ParentCapture) -> Self {
1136 control.handle()
1137 }
1138}
1139impl PartialEq<ControlHandle> for ParentCapture {
1140 fn eq(&self, other: &ControlHandle) -> bool {
1141 self.handle() == *other
1142 }
1143}
1144impl PartialEq<ParentCapture> for ControlHandle {
1145 fn eq(&self, other: &ParentCapture) -> bool {
1146 *self == other.handle()
1147 }
1148}
1149
1150pub struct TimerThread {
1152 join_handle: std::thread::JoinHandle<()>,
1153 send_time_request: mpsc::Sender<(Instant, Box<dyn FnOnce() + Send + 'static>)>,
1154}
1155impl TimerThread {
1156 pub fn new() -> Self {
1157 let (tx, rx) = mpsc::channel();
1158 let join_handle = std::thread::Builder::new()
1159 .name("TimerThread".to_string())
1160 .spawn(move || Self::background_work(rx))
1161 .expect("Failed to spawn timer thread");
1162 Self {
1163 join_handle,
1164 send_time_request: tx,
1165 }
1166 }
1167 fn safe_call(f: impl FnOnce()) {
1169 struct SafeDrop(Option<Box<dyn std::any::Any + Send>>);
1171 impl Drop for SafeDrop {
1172 fn drop(&mut self) {
1173 while let Some(value) = self.0.take() {
1174 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1175 drop(value);
1176 }));
1177 match result {
1178 Ok(()) => {}
1179 Err(e) => {
1180 self.0 = Some(e);
1181 }
1182 }
1183 }
1184 }
1185 }
1186 if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
1187 drop(SafeDrop(Some(e)));
1188 }
1189 }
1190 fn background_work(rx: mpsc::Receiver<(Instant, Box<dyn FnOnce() + Send + 'static>)>) {
1191 let mut times = BTreeMap::<Instant, Box<dyn FnOnce() + Send + 'static>>::new();
1192 loop {
1193 let (new_time, f) = if let Some(first_time) = times.first_entry() {
1194 let sleep_to = first_time.key();
1195 let Some(timeout) = sleep_to.checked_duration_since(Instant::now()) else {
1196 let f = first_time.remove();
1197 Self::safe_call(f);
1198 continue;
1199 };
1200 match rx.recv_timeout(timeout) {
1201 Ok(msg) => msg,
1202 Err(RecvTimeoutError::Disconnected) => {
1203 for (sleep_to, f) in times.into_iter() {
1205 if let Some(timeout) = sleep_to.checked_duration_since(Instant::now()) {
1206 std::thread::sleep(timeout);
1207 }
1208 Self::safe_call(f);
1209 }
1210 break;
1211 }
1212 Err(RecvTimeoutError::Timeout) => {
1213 let f = first_time.remove();
1214 Self::safe_call(f);
1215 continue;
1216 }
1217 }
1218 } else {
1219 match rx.recv() {
1220 Ok(msg) => msg,
1221 Err(mpsc::RecvError) => break,
1222 }
1223 };
1224 times.insert(new_time, f);
1225 }
1226 }
1227 pub fn notify_at(&self, time: Instant, f: impl FnOnce() + Send + 'static) {
1228 self.send_time_request
1229 .send((time, Box::new(f)))
1230 .expect("Background timer thread has exited");
1231 }
1232 fn notify_waker(
1236 &self,
1237 time: Instant,
1238 waker: Arc<std::sync::Mutex<Result<std::task::Waker, bool>>>,
1239 ) {
1240 self.notify_at(time, move || {
1241 let result = std::mem::replace(&mut *waker.lock().unwrap(), Err(true));
1242 if let Ok(waker) = result {
1243 waker.wake();
1244 }
1245 })
1246 }
1247 pub fn delay_future(&self, delay: Duration) -> impl std::future::Future<Output = ()> {
1248 self.notify_future(Instant::now() + delay)
1249 }
1250 pub fn notify_future(&self, time: Instant) -> impl std::future::Future<Output = ()> {
1252 struct WaitFut(Arc<std::sync::Mutex<Result<std::task::Waker, bool>>>);
1253 impl std::future::Future for WaitFut {
1254 type Output = ();
1255
1256 fn poll(
1257 self: std::pin::Pin<&mut Self>,
1258 cx: &mut std::task::Context<'_>,
1259 ) -> std::task::Poll<Self::Output> {
1260 let this = &mut self.get_mut().0;
1261 let mut guard = this.lock().unwrap();
1262 if let &Err(true) = &*guard {
1263 std::task::Poll::Ready(())
1264 } else {
1265 *guard = Ok(cx.waker().clone());
1266 std::task::Poll::Pending
1267 }
1268 }
1269 }
1270 let shared = Arc::new(std::sync::Mutex::new(Err(false)));
1271 self.notify_waker(time, shared.clone());
1272 WaitFut(shared)
1273 }
1274 pub fn get_global() -> &'static Self {
1275 static GLOBAL: OnceLock<TimerThread> = OnceLock::new();
1276 GLOBAL.get_or_init(Self::new)
1277 }
1278}
1279impl Default for TimerThread {
1280 fn default() -> Self {
1281 Self::new()
1282 }
1283}
1284
1285#[derive(nwd::NwgPartial)]
1321pub struct FastTimer {
1322 #[nwg_control]
1323 #[nwg_events( OnNotice: [Self::on_notice] )]
1324 pub notice: nwg::Notice,
1325 callback: RefCell<Box<dyn Fn() + 'static>>,
1326 cancel_latest: RefCell<Arc<AtomicBool>>,
1327 interval_config: Cell<Option<(Duration, Instant)>>,
1330}
1331impl FastTimer {
1332 pub fn set_callback(&self, callback: impl Fn() + 'static) {
1333 *self.callback.borrow_mut() = Box::new(callback);
1334 }
1335 pub fn cancel_last(&self) {
1337 let cancel_latest = self.cancel_latest.borrow();
1338 cancel_latest.store(true, std::sync::atomic::Ordering::Release);
1339 self.interval_config.set(None);
1340 }
1341 pub fn notify_after(&self, duration: Duration) {
1342 self.notify_at(
1343 Instant::now()
1344 .checked_add(duration)
1345 .expect("Time is out of bounds"),
1346 );
1347 }
1348 pub fn notify_at(&self, time_to_notify_at: Instant) {
1349 let sender = self.notice.sender();
1350 let canceled = {
1351 let mut cancel_latest = self.cancel_latest.borrow_mut();
1352 cancel_latest.store(true, std::sync::atomic::Ordering::Release);
1353 let canceled = Arc::new(AtomicBool::new(false));
1354 *cancel_latest = canceled.clone();
1355 canceled
1356 };
1357 TimerThread::get_global().notify_at(time_to_notify_at, move || {
1358 if !canceled.load(std::sync::atomic::Ordering::Acquire) {
1359 sender.notice();
1360 }
1361 })
1362 }
1363 pub fn start_interval(&self, between_ticks: Duration) {
1364 let target_time = Instant::now() + between_ticks;
1365 self.interval_config.set(Some((between_ticks, target_time)));
1366 self.notify_at(target_time);
1367 }
1368 fn on_notice(&self) {
1369 if let Some((between_ticks, target_time)) = self.interval_config.get() {
1370 let mut new_target = target_time + between_ticks;
1371 let now = Instant::now();
1372 if new_target < now {
1373 new_target = now + between_ticks;
1376 }
1377 self.notify_at(new_target);
1378 }
1379 self.callback.borrow()();
1380 }
1381}
1382impl Default for FastTimer {
1383 fn default() -> Self {
1384 Self {
1385 notice: Default::default(),
1386 callback: RefCell::new(Box::new(|| {})),
1387 cancel_latest: RefCell::new(Arc::new(AtomicBool::new(false))),
1388 interval_config: Cell::new(None),
1389 }
1390 }
1391}
1392impl Drop for FastTimer {
1393 fn drop(&mut self) {
1394 self.cancel_last();
1395 }
1396}
1397
1398#[derive(Default)]
1453pub struct FastTimerControl {
1454 pub notice: nwg::Notice,
1455 is_last_active: RefCell<Arc<AtomicBool>>,
1456}
1457impl FastTimerControl {
1458 pub fn builder() -> FastTimerControlBuilder {
1459 FastTimerControlBuilder {
1460 parent: None,
1461 interval: None,
1462 }
1463 }
1464
1465 pub fn is_waiting(&self) -> bool {
1469 self.is_last_active
1470 .borrow()
1471 .load(std::sync::atomic::Ordering::Acquire)
1472 }
1473 pub fn cancel_last(&self) {
1475 let last_active = self.is_last_active.borrow();
1476 last_active.store(false, std::sync::atomic::Ordering::Release);
1477 }
1478 fn new_enable_signal(&self) -> Arc<AtomicBool> {
1479 let mut last_active = self.is_last_active.borrow_mut();
1480 last_active.store(false, std::sync::atomic::Ordering::Release);
1481 let new_active = Arc::new(AtomicBool::new(true));
1482 *last_active = new_active.clone();
1483 new_active
1484 }
1485
1486 pub fn notify_after(&self, duration: Duration) {
1487 self.notify_at(
1488 Instant::now()
1489 .checked_add(duration)
1490 .expect("Time is out of bounds"),
1491 );
1492 }
1493 pub fn notify_at(&self, time_to_notify_at: Instant) {
1494 let sender = self.notice.sender();
1495 let is_active = self.new_enable_signal();
1496 TimerThread::get_global().notify_at(time_to_notify_at, move || {
1497 if is_active.load(std::sync::atomic::Ordering::Acquire) {
1498 is_active.store(false, std::sync::atomic::Ordering::Release);
1499 sender.notice();
1500 }
1501 })
1502 }
1503 pub fn start_interval(&self, between_ticks: Duration) {
1504 struct CallbackState {
1505 target_time: Instant,
1506 between_ticks: Duration,
1507 sender: nwg::NoticeSender,
1508 is_active: Arc<AtomicBool>,
1509 timer_thread: &'static TimerThread,
1510 }
1511 impl CallbackState {
1512 fn into_callback(mut self) -> impl FnOnce() + Send + 'static {
1513 move || {
1514 if self.is_active.load(std::sync::atomic::Ordering::Acquire) {
1515 self.sender.notice();
1516
1517 self.target_time += self.between_ticks;
1518 let now = Instant::now();
1519 if self.target_time < now {
1520 self.target_time = now + self.between_ticks;
1523 }
1524
1525 let timer_thread = self.timer_thread;
1526 let target_time = self.target_time;
1527 timer_thread.notify_at(target_time, self.into_callback());
1528 }
1529 }
1530 }
1531 }
1532 let target_time = Instant::now() + between_ticks;
1533 let timer_thread = TimerThread::get_global();
1534 let state = CallbackState {
1535 target_time,
1536 between_ticks,
1537 sender: self.notice.sender(),
1538 is_active: self.new_enable_signal(),
1539 timer_thread,
1540 };
1541 timer_thread.notify_at(target_time, state.into_callback());
1542 }
1543}
1544impl PartialEq for FastTimerControl {
1545 fn eq(&self, other: &Self) -> bool {
1546 self.notice == other.notice
1547 }
1548}
1549impl Eq for FastTimerControl {}
1550impl Drop for FastTimerControl {
1551 fn drop(&mut self) {
1552 self.cancel_last();
1553 }
1554}
1555
1556pub struct FastTimerControlBuilder {
1557 parent: Option<ControlHandle>,
1558 interval: Option<Duration>,
1559}
1560impl FastTimerControlBuilder {
1561 pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> Self {
1562 self.parent = Some(p.into());
1563 self
1564 }
1565 pub fn interval(mut self, interval: Duration) -> Self {
1566 self.interval = Some(interval);
1567 self
1568 }
1569 pub fn build(self, out: &mut FastTimerControl) -> Result<(), nwg::NwgError> {
1570 out.cancel_last();
1571
1572 let mut notice_builder = nwg::Notice::builder();
1573 if let Some(parent) = self.parent {
1574 notice_builder = notice_builder.parent(parent);
1575 }
1576 notice_builder.build(&mut out.notice)?;
1577
1578 if let Some(interval) = self.interval {
1579 out.start_interval(interval);
1580 }
1581
1582 Ok(())
1583 }
1584}
1585impl From<&FastTimerControl> for ControlHandle {
1586 fn from(control: &FastTimerControl) -> Self {
1587 control.notice.handle
1588 }
1589}
1590impl From<&mut FastTimerControl> for ControlHandle {
1591 fn from(control: &mut FastTimerControl) -> Self {
1592 control.notice.handle
1593 }
1594}
1595impl PartialEq<ControlHandle> for FastTimerControl {
1596 fn eq(&self, other: &ControlHandle) -> bool {
1597 self.notice.handle == *other
1598 }
1599}
1600impl PartialEq<FastTimerControl> for ControlHandle {
1601 fn eq(&self, other: &FastTimerControl) -> bool {
1602 *self == other.notice.handle
1603 }
1604}