1use std::fmt;
5
6use windows::{core::GUID, Win32::Foundation::HWND};
7
8#[cfg(not(any(feature = "winvd_dynamic", feature = "winvd_static")))]
9compile_error!("One of the features 'winvd_dynamic' and 'winvd_static' must be enabled; otherwise the program can't interact with virtual desktops at all.");
10
11pub mod dynamic {
12 #![cfg(feature = "winvd_dynamic")]
13
14 use std::{fmt, sync::OnceLock};
15
16 use libloading::{library_filename, Library, Symbol};
17 use windows::{core::GUID, Win32::Foundation::HWND};
18
19 static LIBRARY: OnceLock<Result<Library, libloading::Error>> = OnceLock::new();
20
21 pub unsafe fn loaded_library() -> Result<&'static Library, &'static libloading::Error> {
27 let res = LIBRARY.get_or_init(|| {
28 let name = library_filename("VirtualDesktopAccessor");
29 unsafe { Library::new(name) }
30 });
31 match &res {
32 Ok(lib) => Ok(lib),
33 Err(err) => Err(err),
34 }
35 }
36
37 static SYMBOLS: OnceLock<Result<VdSymbols<'static>, &'static libloading::Error>> =
38 OnceLock::new();
39
40 pub unsafe fn loaded_symbols() -> Result<&'static VdSymbols<'static>, &'static libloading::Error>
49 {
50 let res = SYMBOLS.get_or_init(|| unsafe { Ok(VdSymbols::new(loaded_library()?)) });
51
52 match &res {
53 Ok(lib) => Ok(lib),
54 Err(err) => Err(err),
55 }
56 }
57 pub fn get_loaded_symbols(
58 ) -> Option<Result<&'static VdSymbols<'static>, &'static libloading::Error>> {
59 let res = SYMBOLS.get()?;
60 Some(match &res {
61 Ok(lib) => Ok(lib),
62 Err(err) => Err(err),
63 })
64 }
65
66 trait CheckError {
67 fn is_error(&self) -> bool;
68 }
69 impl CheckError for i32 {
70 fn is_error(&self) -> bool {
71 *self == -1
72 }
73 }
74 impl CheckError for GUID {
75 fn is_error(&self) -> bool {
76 *self == GUID::default()
77 }
78 }
79
80 macro_rules! define_symbols {
81 (
82 $(
83 $(#[no_error $(@ $no_error:tt)?])?
84 $(#[optional $(@ $optional:tt)?])?
85 $(unsafe $(@ $unsafe:tt)?)? fn $name:ident($($arg:ident: $t:ty),* $(,)?) -> $ret:ty {}
86 )*
87 ) => {
88 #[derive(Debug, Clone)]
89 pub enum DynamicError {
90 $(
91 #[allow(dead_code)]
92 $name { $($arg: $t,)* },
93 )*
94 Missing(&'static str),
95 }
96 impl fmt::Display for DynamicError {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 match self {
99 $(DynamicError::$name { $($arg,)* } => write!(
100 f, concat!(
101 "Failed to call \"{fn_name}\" in dynamic library with arguments [",
102 $(stringify!($arg), ": {:?}, ",)*
103 "]"
104 ),
105 $($arg,)*
106 fn_name = stringify!($name),
107 ),)*
108 DynamicError::Missing(method) => write!(f, "The method \"{method}\" was not found in the dynamic library"),
109 }
110 }
111 }
112
113 #[allow(non_snake_case, dead_code)]
114 pub struct VdSymbols<'lib> {
115 $(
116 pub $name: Result<Symbol<'lib, $(unsafe $($unsafe)?)? extern "C" fn($($t),*) -> $ret>, libloading::Error>,
117 )*
118 }
119 impl<'lib> VdSymbols<'lib> {
120 pub unsafe fn new(lib: &'lib Library) -> Self {
126 Self {
127 $(
128 $name: unsafe {
131 lib.get(concat!(stringify!($name), "\0").as_bytes())
132 },
133 )*
134 }
135 }
136 pub fn ensure_required_methods_exists(&self) -> Result<(), DynamicError> {
137 $(
138 #[cfg(all($(any( $($optional)? ))?))]
139 if let Err(_) = &self.$name {
140 return Err(DynamicError::Missing(stringify!($name)));
141 }
142 )*
143 Ok(())
144 }
145
146 $(
147 #[allow(non_snake_case)]
154 pub $(unsafe $($unsafe)?)? fn $name(&self, $($arg: $t),*) -> Result<$ret, DynamicError> {
155 tracing::trace!("Dynamic library call to {}", stringify!($name));
156 let sym = match &self.$name {
157 Ok(sym) => sym,
158 Err(_) => return Err(DynamicError::Missing(stringify!($name))),
159 };
160 let res = sym($($arg),*);
161 #[cfg(all($(any( $($no_error)? ))?))]
162 {
163 if CheckError::is_error(&res) {
164 return Err(DynamicError::$name { $($arg,)* })
165 }
166 }
167 Ok(res)
168 }
169 )*
170 }
171 };
172 }
173 define_symbols!(
176 fn GetCurrentDesktopNumber() -> i32 {}
177 fn GetDesktopCount() -> i32 {}
178 fn GetDesktopIdByNumber(number: i32) -> GUID {} fn GetDesktopNumberById(desktop_id: GUID) -> i32 {} fn GetWindowDesktopId(hwnd: HWND) -> GUID {}
181 fn GetWindowDesktopNumber(hwnd: HWND) -> i32 {}
182 fn IsWindowOnCurrentVirtualDesktop(hwnd: HWND) -> i32 {}
183 fn MoveWindowToDesktopNumber(hwnd: HWND, desktop_number: i32) -> i32 {}
184 fn GoToDesktopNumber(desktop_number: i32) -> i32 {}
185 #[optional] unsafe fn SetDesktopName(desktop_number: i32, in_name_ptr: *const i8) -> i32 {}
187 #[optional] unsafe fn GetDesktopName(
189 desktop_number: i32,
190 out_utf8_ptr: *mut u8,
191 out_utf8_len: usize,
192 ) -> i32 {
193 }
194 unsafe fn RegisterPostMessageHook(listener_hwnd: HWND, message_offset: u32) -> i32 {}
195 unsafe fn UnregisterPostMessageHook(listener_hwnd: HWND) -> i32 {}
196 fn IsPinnedWindow(hwnd: HWND) -> i32 {}
197 fn PinWindow(hwnd: HWND) -> i32 {}
198 fn UnPinWindow(hwnd: HWND) -> i32 {}
199 fn IsPinnedApp(hwnd: HWND) -> i32 {}
200 fn PinApp(hwnd: HWND) -> i32 {}
201 fn UnPinApp(hwnd: HWND) -> i32 {}
202 fn IsWindowOnDesktopNumber(hwnd: HWND, desktop_number: i32) -> i32 {}
203 #[optional] fn CreateDesktop() -> i32 {}
205 #[optional] fn RemoveDesktop(remove_desktop_number: i32, fallback_desktop_number: i32) -> i32 {}
207 );
208
209 impl From<DynamicError> for super::Error {
210 fn from(err: DynamicError) -> Self {
211 Self::DynamicCall(err)
212 }
213 }
214}
215
216#[derive(Copy, Clone, Debug, Eq, PartialEq)]
218pub enum Desktop {
219 #[cfg(feature = "winvd_static")]
220 Static(winvd::Desktop),
221 Index(u32),
222 Guid(GUID),
223}
224impl Desktop {
225 pub fn get_index(&self) -> Result<u32, Error> {
226 match self {
227 #[cfg(feature = "winvd_static")]
228 Self::Static(d) => Ok(d.get_index()?),
229 Self::Index(i) => Ok(*i),
230 Self::Guid(guid) => {
231 #[cfg(feature = "winvd_dynamic")]
232 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
233 return Ok(symbols.GetDesktopNumberById(*guid)? as u32);
234 }
235 #[cfg(feature = "winvd_static")]
236 {
237 return Ok(winvd::get_desktop(*guid).get_index()?);
238 }
239 #[allow(unreachable_code)]
240 {
241 Err(no_dynamic_library_error())
242 }
243 }
244 }
245 }
246 pub fn get_name(&self) -> Result<String, Error> {
247 match self {
248 #[cfg(feature = "winvd_static")]
249 Self::Static(d) => Ok(d.get_name()?),
250 _ => {
251 #[cfg(feature = "winvd_dynamic")]
252 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
253 let mut buf = vec![0u8; 256];
254 let desktop_number = self.get_index()? as i32;
255 let out_utf8_len = buf.len();
256 let out_utf8_ptr = buf.as_mut_ptr();
257 let res = unsafe {
259 symbols.GetDesktopName(desktop_number, out_utf8_ptr, out_utf8_len)?
260 };
261 if res == 0 {
262 return Err(Error::DynamicCall(dynamic::DynamicError::GetDesktopName {
264 desktop_number,
265 out_utf8_ptr,
266 out_utf8_len,
267 }));
268 }
269 if let Some(first_nul) = buf.iter().position(|&byte| byte == b'\0') {
271 buf.truncate(first_nul + 1);
272 }
273 let mut name = std::ffi::CString::from_vec_with_nul(buf)
274 .map_err(|_| Error::DesktopNameWithoutNul)?
275 .into_string()
276 .map_err(|e| {
277 Error::NonUtf8DesktopName(
278 String::from_utf8_lossy(e.into_cstring().as_bytes()).into_owned(),
279 )
280 })?;
281 name.shrink_to_fit();
282 return Ok(name);
283 }
284 #[cfg(feature = "winvd_static")]
285 {
286 return Ok(winvd::Desktop::from(*self).get_name()?);
287 }
288 #[allow(unreachable_code)]
289 {
290 Err(no_dynamic_library_error())
291 }
292 }
293 }
294 }
295}
296#[cfg(feature = "winvd_static")]
297impl From<winvd::Desktop> for Desktop {
298 fn from(d: winvd::Desktop) -> Self {
299 Self::Static(d)
300 }
301}
302#[cfg(feature = "winvd_static")]
303impl From<Desktop> for winvd::Desktop {
304 fn from(d: Desktop) -> Self {
305 match d {
306 Desktop::Static(d) => d,
307 Desktop::Index(i) => winvd::get_desktop(i),
308 Desktop::Guid(g) => winvd::get_desktop(g),
309 }
310 }
311}
312impl From<u32> for Desktop {
313 fn from(i: u32) -> Self {
314 Self::Index(i)
315 }
316}
317impl From<i32> for Desktop {
318 fn from(i: i32) -> Self {
319 Self::Index(i as u32)
320 }
321}
322impl From<GUID> for Desktop {
323 fn from(g: GUID) -> Self {
324 Self::Guid(g)
325 }
326}
327
328pub fn get_desktop<T>(desktop: T) -> Desktop
337where
338 T: Into<Desktop>,
339{
340 desktop.into()
341}
342
343#[derive(Debug, Clone, Eq, PartialEq)]
345pub enum DesktopEvent {
346 DesktopCreated(Desktop),
347 DesktopDestroyed {
348 destroyed: Desktop,
349 fallback: Desktop,
350 },
351 DesktopChanged {
352 new: Desktop,
353 old: Desktop,
354 },
355 DesktopNameChanged(Desktop, String),
356 DesktopWallpaperChanged(Desktop, String),
357 DesktopMoved {
358 desktop: Desktop,
359 old_index: i64,
360 new_index: i64,
361 },
362 WindowChanged(HWND),
363}
364#[cfg(feature = "winvd_static")]
365impl From<winvd::DesktopEvent> for DesktopEvent {
366 fn from(event: winvd::DesktopEvent) -> Self {
367 match event {
368 winvd::DesktopEvent::DesktopCreated(d) => Self::DesktopCreated(d.into()),
369 winvd::DesktopEvent::DesktopDestroyed {
370 destroyed,
371 fallback,
372 } => Self::DesktopDestroyed {
373 destroyed: destroyed.into(),
374 fallback: fallback.into(),
375 },
376 winvd::DesktopEvent::DesktopChanged { new, old } => Self::DesktopChanged {
377 new: new.into(),
378 old: old.into(),
379 },
380 winvd::DesktopEvent::DesktopNameChanged(d, name) => {
381 Self::DesktopNameChanged(d.into(), name)
382 }
383 winvd::DesktopEvent::DesktopWallpaperChanged(d, path) => {
384 Self::DesktopWallpaperChanged(d.into(), path)
385 }
386 winvd::DesktopEvent::DesktopMoved {
387 desktop,
388 old_index,
389 new_index,
390 } => Self::DesktopMoved {
391 desktop: desktop.into(),
392 old_index,
393 new_index,
394 },
395 winvd::DesktopEvent::WindowChanged(hwnd) => Self::WindowChanged(hwnd),
396 }
397 }
398}
399
400#[derive(Debug, Clone)]
401pub enum Error {
402 #[cfg(feature = "winvd_dynamic")]
403 DynamicCall(dynamic::DynamicError),
404 #[cfg(feature = "winvd_dynamic")]
405 FailedToLoadDynamicLibrary(&'static libloading::Error),
406 NotLoadedDynamicLibrary,
408 #[cfg(feature = "winvd_static")]
409 StaticCall(winvd::Error),
410 NonUtf8DesktopName(String),
411 DesktopNameWithoutNul,
412}
413#[cfg(feature = "winvd_static")]
414impl From<winvd::Error> for Error {
415 fn from(value: winvd::Error) -> Self {
416 Self::StaticCall(value)
417 }
418}
419impl fmt::Display for Error {
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 match self {
422 #[cfg(feature = "winvd_dynamic")]
423 Self::DynamicCall(err) => fmt::Display::fmt(err, f),
424 #[cfg(feature = "winvd_dynamic")]
425 Self::FailedToLoadDynamicLibrary(err) => {
426 write!(f, "Failed to load dynamic library VirtualDesktopAccessor.dll: {err}")
427 }
428 Self::NotLoadedDynamicLibrary => write!(
429 f,
430 "Tried to call a virtual desktop function before loading the dynamic library VirtualDesktopAccessor.dll"
431 ),
432 #[cfg(feature = "winvd_static")]
433 Self::StaticCall(err) => write!(
434 f,
435 "Failed to call virtual desktop function in static library: {err:?}"
436 ),
437 Self::NonUtf8DesktopName(name) => write!(f, "Non-UTF8 desktop name: {name}"),
438 Self::DesktopNameWithoutNul => write!(f, "Invalid virtual desktop name"),
439 }
440 }
441}
442impl std::error::Error for Error {}
443
444pub type Result<T, E = Error> = std::result::Result<T, E>;
445
446pub unsafe fn load_dynamic_library() -> Result<(), Error> {
466 #[cfg(feature = "winvd_dynamic")]
467 {
468 let res = unsafe { dynamic::loaded_symbols() };
469 let res = match res {
470 Err(e) => {
471 tracing::warn!("Failed to load VirtualDesktopAccessor.dll: {e}");
472 Err(Error::FailedToLoadDynamicLibrary(e))
473 }
474 Ok(symbols) => {
475 if let Err(e) = symbols.ensure_required_methods_exists() {
476 tracing::error!("Failed to load VirtualDesktopAccessor.dll: {e}");
477 Err(Error::DynamicCall(e))
478 } else {
479 tracing::info!("Successfully loaded VirtualDesktopAccessor.dll");
480 Ok(())
481 }
482 }
483 };
484 if cfg!(feature = "winvd_static") {
485 Ok(())
487 } else {
488 res
489 }
490 }
491 #[cfg(not(feature = "winvd_dynamic"))]
492 {
493 Ok(())
494 }
495}
496
497pub fn has_loaded_dynamic_library_successfully() -> bool {
498 #[cfg(feature = "winvd_dynamic")]
499 {
500 matches!(dynamic::get_loaded_symbols(), Some(Ok(symbols)) if symbols.ensure_required_methods_exists().is_ok())
501 }
502 #[cfg(not(feature = "winvd_dynamic"))]
503 {
504 false
505 }
506}
507
508fn no_dynamic_library_error() -> Error {
514 #[cfg(feature = "winvd_dynamic")]
515 {
516 match dynamic::get_loaded_symbols() {
517 Some(Err(e)) => return Error::FailedToLoadDynamicLibrary(e),
518 Some(Ok(_)) => panic!("Should have called the loaded function instead of reporting that no library was loaded"),
519 None => (),
520 }
521 }
522 Error::NotLoadedDynamicLibrary
523}
524
525pub fn get_desktop_count() -> Result<u32> {
528 #[cfg(feature = "winvd_dynamic")]
529 {
530 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
531 return Ok(symbols.GetDesktopCount()? as u32);
532 }
533 }
534 #[cfg(feature = "winvd_static")]
535 {
536 return Ok(winvd::get_desktop_count()?);
537 }
538 #[allow(unreachable_code)]
539 Err(no_dynamic_library_error())
540}
541
542pub fn get_current_desktop() -> Result<Desktop> {
545 #[cfg(feature = "winvd_dynamic")]
546 {
547 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
548 return Ok(Desktop::Index(symbols.GetCurrentDesktopNumber()? as u32));
549 }
550 }
551 #[cfg(feature = "winvd_static")]
552 {
553 return Ok(winvd::get_current_desktop()?.into());
554 }
555 #[allow(unreachable_code)]
556 Err(no_dynamic_library_error())
557}
558
559pub fn move_window_to_desktop(desktop: Desktop, hwnd: &HWND) -> Result<()> {
562 #[cfg(feature = "winvd_dynamic")]
563 {
564 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
565 symbols.MoveWindowToDesktopNumber(*hwnd, desktop.get_index()? as i32)?;
566 return Ok(());
567 }
568 }
569 #[cfg(feature = "winvd_static")]
570 {
571 winvd::move_window_to_desktop(winvd::Desktop::from(desktop), hwnd)?;
572 return Ok(());
573 }
574 #[allow(unreachable_code)]
575 Err(no_dynamic_library_error())
576}
577
578pub fn pin_window(hwnd: HWND) -> Result<()> {
581 #[cfg(feature = "winvd_dynamic")]
582 {
583 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
584 symbols.PinWindow(hwnd)?;
585 return Ok(());
586 }
587 }
588 #[cfg(feature = "winvd_static")]
589 {
590 winvd::pin_window(hwnd)?;
591 return Ok(());
592 }
593 #[allow(unreachable_code)]
594 Err(no_dynamic_library_error())
595}
596
597pub fn unpin_window(hwnd: HWND) -> Result<()> {
600 #[cfg(feature = "winvd_dynamic")]
601 {
602 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
603 symbols.UnPinWindow(hwnd)?;
604 return Ok(());
605 }
606 }
607 #[cfg(feature = "winvd_static")]
608 {
609 winvd::unpin_window(hwnd)?;
610 return Ok(());
611 }
612 #[allow(unreachable_code)]
613 Err(no_dynamic_library_error())
614}
615
616pub fn switch_desktop(desktop: Desktop) -> Result<()> {
619 #[cfg(feature = "winvd_dynamic")]
620 {
621 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
622 symbols.GoToDesktopNumber(desktop.get_index()? as i32)?;
623 return Ok(());
624 }
625 }
626 #[cfg(feature = "winvd_static")]
627 {
628 winvd::switch_desktop(winvd::Desktop::from(desktop))?;
629 return Ok(());
630 }
631 #[allow(unreachable_code)]
632 Err(no_dynamic_library_error())
633}
634
635pub fn switch_desktop_with_animation(desktop: Desktop) -> Result<()> {
638 #[cfg(feature = "winvd_static")]
639 {
640 winvd::switch_desktop_with_animation(winvd::Desktop::from(desktop))?;
641 return Ok(());
642 }
643 #[allow(unreachable_code)]
644 Err(Error::DynamicCall(
645 dynamic::DynamicError::GoToDesktopNumber {
646 desktop_number: desktop.get_index().unwrap_or(1) as i32,
647 },
648 ))
649}
650
651pub fn remove_desktop(desktop: Desktop, fallback_desktop: Desktop) -> Result<()> {
654 #[cfg(feature = "winvd_dynamic")]
655 {
656 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
657 symbols.RemoveDesktop(
658 desktop.get_index()? as i32,
659 fallback_desktop.get_index()? as i32,
660 )?;
661 return Ok(());
662 }
663 }
664 #[cfg(feature = "winvd_static")]
665 {
666 winvd::remove_desktop(
667 winvd::Desktop::from(desktop),
668 winvd::Desktop::from(fallback_desktop),
669 )?;
670 return Ok(());
671 }
672 #[allow(unreachable_code)]
673 Err(no_dynamic_library_error())
674}
675
676pub fn create_desktop() -> Result<Desktop> {
679 #[cfg(feature = "winvd_dynamic")]
680 {
681 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
682 return Ok(Desktop::Index(symbols.CreateDesktop()? as u32));
683 }
684 }
685 #[cfg(feature = "winvd_static")]
686 {
687 return Ok(Desktop::Static(winvd::create_desktop()?));
688 }
689 #[allow(unreachable_code)]
690 Err(no_dynamic_library_error())
691}
692
693pub fn get_desktops() -> Result<Vec<Desktop>> {
696 #[cfg(feature = "winvd_dynamic")]
697 {
698 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
699 return Ok((0..symbols.GetDesktopCount()?)
700 .map(|i| Desktop::Index(i as u32))
701 .collect());
702 }
703 }
704 #[cfg(feature = "winvd_static")]
705 {
706 return Ok(winvd::get_desktops()?
707 .into_iter()
708 .map(Desktop::from)
709 .collect());
710 }
711 #[allow(unreachable_code)]
712 Err(no_dynamic_library_error())
713}
714
715pub fn get_window_desktop(hwnd: HWND) -> Result<Desktop> {
716 #[cfg(feature = "winvd_dynamic")]
717 {
718 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
719 return Ok(Desktop::Guid(symbols.GetWindowDesktopId(hwnd)?));
720 }
721 }
722 #[cfg(feature = "winvd_static")]
723 {
724 return Ok(winvd::get_desktop_by_window(hwnd)?.into());
725 }
726 #[allow(unreachable_code)]
727 Err(no_dynamic_library_error())
728}
729
730pub fn is_pinned_window(hwnd: HWND) -> Result<bool> {
731 #[cfg(feature = "winvd_dynamic")]
732 {
733 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
734 return Ok(symbols.IsPinnedWindow(hwnd)? != 0);
735 }
736 }
737 #[cfg(feature = "winvd_static")]
738 {
739 return Ok(winvd::is_pinned_window(hwnd)?);
740 }
741 #[allow(unreachable_code)]
742 Err(no_dynamic_library_error())
743}
744
745pub fn is_pinned_app(hwnd: HWND) -> Result<bool> {
746 #[cfg(feature = "winvd_dynamic")]
747 {
748 if let Some(Ok(symbols)) = dynamic::get_loaded_symbols() {
749 return Ok(symbols.IsPinnedApp(hwnd)? != 0);
750 }
751 }
752 #[cfg(feature = "winvd_static")]
753 {
754 return Ok(winvd::is_pinned_app(hwnd)?);
755 }
756 #[allow(unreachable_code)]
757 Err(no_dynamic_library_error())
758}
759
760pub fn start_flashing_window(hwnd: HWND) {
762 use windows::Win32::UI::WindowsAndMessaging::{
763 FlashWindowEx, FLASHWINFO, FLASHW_TIMERNOFG, FLASHW_TRAY,
764 };
765
766 let info = FLASHWINFO {
767 cbSize: std::mem::size_of::<FLASHWINFO>() as u32,
768 hwnd,
770 dwFlags: FLASHW_TIMERNOFG | FLASHW_TRAY,
771 uCount: 0,
773 dwTimeout: 0,
775 };
776 let _ = unsafe { FlashWindowEx(&info) };
781}
782
783pub fn stop_flashing_windows_blocking(
791 windows: Vec<(HWND, Option<Desktop>)>,
792) -> Result<(), Box<dyn std::error::Error>> {
793 tracing::debug!(?windows, "stop_flashing_windows_blocking");
794 if windows.is_empty() {
795 return Ok(());
796 }
797 let error = std::cell::OnceCell::new();
798 crate::block_on::block_on(crate::block_on::simple_join(windows.into_iter().map(
799 |(hwnd, target)| {
800 let error = &error;
801 async move {
802 if let Err(e) = stop_flashing_window(hwnd, target).await {
803 let _ = error.set(e);
804 }
805 }
806 },
807 )));
808 if let Some(e) = error.into_inner() {
809 Err(e)
810 } else {
811 Ok(())
812 }
813}
814
815pub async fn stop_flashing_window(
831 hwnd: HWND,
832 target_desktop: Option<Desktop>,
833) -> Result<(), Box<dyn std::error::Error>> {
834 use crate::nwg_ext::TimerThread;
835 use std::time::{Duration, Instant};
836 use windows::Win32::UI::WindowsAndMessaging::{
837 FlashWindowEx, GetWindowInfo, ShowWindow, FLASHWINFO, FLASHW_STOP, SW_HIDE, SW_SHOWNA,
838 WINDOWINFO, WS_VISIBLE,
839 };
840
841 if let Some(target_desktop) = target_desktop {
844 move_window_to_desktop(target_desktop, &hwnd)?;
845 };
846
847 let info = FLASHWINFO {
852 cbSize: std::mem::size_of::<FLASHWINFO>() as u32,
853 hwnd,
855 dwFlags: FLASHW_STOP,
856 uCount: 0,
858 dwTimeout: 0,
860 };
861 let _ = unsafe { FlashWindowEx(&info) };
866
867 let was_visible;
869 {
870 struct ShowGuard {
872 hwnd: Option<HWND>,
873 hidden_at: Instant,
874 }
875 impl Drop for ShowGuard {
876 fn drop(&mut self) {
877 let Some(hwnd) = self.hwnd else {
878 return;
879 };
880 let wake_at = self.hidden_at + Duration::from_millis(1000);
881 let wait = wake_at.saturating_duration_since(Instant::now());
882 if !wait.is_zero() {
883 std::thread::sleep(wait);
884 }
885 let _ = unsafe { ShowWindow(hwnd, SW_SHOWNA) };
886 }
887 }
888 let mut show_guard = ShowGuard {
889 hwnd: Some(hwnd),
890 hidden_at: Instant::now(),
891 };
892
893 TimerThread::get_global()
895 .delay_future(Duration::from_millis(1000))
896 .await;
897
898 was_visible = unsafe { ShowWindow(hwnd, SW_HIDE) }.as_bool();
900 if was_visible {
901 let retry_times = [
907 100, 400, 500, 1_000, 3_000, 5_000, 5_000, 5_000, 10_000, 30_000, ]
922 .map(Duration::from_millis);
923 for time in retry_times {
924 TimerThread::get_global().delay_future(time).await;
925
926 let mut info = WINDOWINFO {
927 cbSize: std::mem::size_of::<WINDOWINFO>() as u32,
928 ..Default::default()
929 };
930 if unsafe { GetWindowInfo(hwnd, &mut info) }.is_ok()
931 && (info.dwStyle.0 & WS_VISIBLE.0 == 0)
932 {
933 break;
935 }
936 }
937
938 let _ = unsafe { ShowWindow(hwnd, SW_SHOWNA) };
940
941 show_guard.hwnd = None;
943 }
944 }
945
946 {
948 if !was_visible {
949 return Ok(());
951 }
952 let Some(target_desktop) = target_desktop else {
953 return Ok(());
955 };
956
957 let retry_times = [
970 0, 25, 25, 50, 400, ]
977 .map(Duration::from_millis);
978
979 for time in retry_times {
980 if !time.is_zero() {
981 TimerThread::get_global().delay_future(time).await;
983 }
984
985 let Ok(current) = get_window_desktop(hwnd) else {
986 continue;
988 };
989 if current == target_desktop {
990 break;
992 }
993 let _ = move_window_to_desktop(target_desktop, &hwnd);
996 }
997 }
998
999 Ok(())
1000}