virtual_desktop_manager\tray_plugins/
desktop_events_dynamic.rs

1//! Tray plugin that registers to Virtual Desktop events using the dynamic
2//! library `VirtualDesktopAccessor.dll`.
3
4#![cfg(feature = "winvd_dynamic")]
5
6use windows::Win32::Foundation::HWND;
7
8use crate::{
9    dynamic_gui::DynamicUiHooks,
10    nwg_ext::FastTimerControl,
11    tray::{SystemTray, SystemTrayRef, TrayPlugin, TrayRoot},
12    vd,
13};
14use std::{any::TypeId, cell::Cell, cmp::Ordering, rc::Rc, time::Duration};
15
16/// Any value between WM_USER (0x0400 = 1024) and 0x7FFF (32767) can be used
17/// according to
18/// <https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-user>. But don't
19/// use any already used by nwg (see top of its `window_helper.rs` file).
20///
21/// Current value was suggested in AutoHotkey example script inside `winvd`
22/// repository.
23const MESSAGE_OFFSET: u32 = 0x1400;
24
25#[derive(nwd::NwgPartial, Default)]
26pub struct DynamicVirtualDesktopEventManager {
27    tray_ref: SystemTrayRef,
28    #[nwg_control(interval: Duration::from_millis(1000))]
29    #[nwg_events(OnNotice: [Self::on_poll_timer])]
30    poll_timer: FastTimerControl,
31    registered_at: Cell<Option<HWND>>,
32    prev_window_count: Cell<u32>,
33}
34impl DynamicVirtualDesktopEventManager {
35    fn on_poll_timer(&self) {
36        let Some(tray) = self.tray_ref.get() else {
37            return;
38        };
39        let Some(Ok(_)) = vd::dynamic::get_loaded_symbols() else {
40            return;
41        };
42        let new_count = match vd::get_desktop_count() {
43            Ok(count) => count,
44            Err(e) => {
45                tracing::warn!("Failed to get desktop count from the dynamic library: {e:?}");
46                return;
47            }
48        };
49
50        match new_count.cmp(&self.prev_window_count.get()) {
51            Ordering::Equal => return,
52            Ordering::Less => {
53                tray.notify_desktop_event(vd::DesktopEvent::DesktopDestroyed {
54                    destroyed: vd::get_desktop(self.prev_window_count.get() - 1),
55                    fallback: match vd::get_current_desktop() {
56                        Ok(desk) => desk,
57                        Err(e) => {
58                            tracing::warn!(
59                                "Failed to get current desktop from the dynamic library: {e:?}"
60                            );
61                            return;
62                        }
63                    },
64                });
65            }
66            Ordering::Greater => {
67                tray.notify_desktop_event(vd::DesktopEvent::DesktopCreated(vd::get_desktop(
68                    new_count - 1,
69                )));
70            }
71        }
72
73        self.prev_window_count.set(new_count);
74    }
75}
76impl DynamicUiHooks<SystemTray> for DynamicVirtualDesktopEventManager {
77    fn before_partial_build(
78        &mut self,
79        tray: &Rc<SystemTray>,
80        _should_build: &mut bool,
81    ) -> Option<(nwg::ControlHandle, TypeId)> {
82        self.tray_ref.set(tray);
83        Some((tray.root().window.handle, TypeId::of::<TrayRoot>()))
84    }
85    fn after_partial_build(&mut self, tray_ui: &Rc<SystemTray>) {
86        let Some(Ok(symbols)) = vd::dynamic::get_loaded_symbols() else {
87            self.poll_timer.cancel_last();
88            return;
89        };
90        let handle = tray_ui.root().window.handle;
91        let handle = HWND(
92            handle
93                .hwnd()
94                .expect("Root window should have a valid handle")
95                .cast(),
96        );
97
98        let res = unsafe { symbols.RegisterPostMessageHook(handle, MESSAGE_OFFSET) };
99        if let Err(e) = res {
100            tracing::error!("Failed to register post message hook for virtual desktop events from the dynamic library: {e:?}");
101            tray_ui.show_notification(
102                "Virtual Desktop Manager Error",
103                &format!("Failed to start listening for virtual desktop events: {e:?}"),
104            );
105        } else {
106            self.registered_at.set(Some(handle));
107        }
108    }
109    fn before_rebuild(&mut self, _dynamic_ui: &Rc<SystemTray>) {
110        let mut old = std::mem::take(self);
111        let Some(Ok(symbols)) = vd::dynamic::get_loaded_symbols() else {
112            return;
113        };
114
115        let Some(hwnd) = old.registered_at.get_mut().take() else {
116            return;
117        };
118
119        if let Err(e) = unsafe { symbols.UnregisterPostMessageHook(hwnd) } {
120            tracing::warn!("Failed to unregister post message hook for virtual desktop events from the dynamic library: {e:?}");
121        }
122    }
123    fn process_raw_event(
124        &self,
125        dynamic_ui: &Rc<SystemTray>,
126        hwnd: isize,
127        msg: u32,
128        w: usize,
129        l: isize,
130        _window: nwg::ControlHandle,
131    ) -> Option<isize> {
132        if Some(HWND(hwnd as *mut _)) != self.registered_at.get() {
133            return None;
134        }
135        if msg != MESSAGE_OFFSET {
136            return None;
137        }
138        dynamic_ui.notify_desktop_event(vd::DesktopEvent::DesktopChanged {
139            old: vd::get_desktop(w as u32),
140            new: vd::get_desktop(l as u32),
141        });
142        None
143    }
144}
145impl TrayPlugin for DynamicVirtualDesktopEventManager {}