Skip to main content

rdesktop_core/
window_extras.rs

1//! Platform-specific window attributes for deep desktop scenarios.
2//!
3//! Provides [`apply_window_attributes`], called right after a tao window is
4//! built. It realizes the `Wallpaper`/`Overlay` window kinds and click-through
5//! requested in [`crate::config::WindowConfig`].
6//!
7//! - **Click-through**: the window ignores pointer input, which falls through
8//!   to whatever is behind it. Required for wallpaper; optional for overlays.
9//! - **Desktop layer**: the window is reparented beneath the desktop icons
10//!   (Windows: `WorkerW`; macOS: `kCGDesktopWindowLevel`) so it behaves like
11//!   a Wallpaper-Engine-style background.
12
13use crate::config::{WindowConfig, WindowKind};
14use tao::window::{Icon, Window};
15
16/// Convert a framework icon into the platform window icon type.
17pub fn window_icon(config: &WindowConfig) -> Option<Icon> {
18    let icon = config.icon.as_ref()?;
19    match Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
20        Ok(icon) => Some(icon),
21        Err(error) => {
22            tracing::warn!(%error, "invalid rdesktop window icon; continuing without icon");
23            None
24        }
25    }
26}
27
28/// Apply `config.kind` / `config.click_through` to a freshly built window.
29pub fn apply_window_attributes(window: &Window, config: &WindowConfig) {
30    let click_through = config.click_through || config.kind == WindowKind::Wallpaper;
31    let is_wallpaper = config.kind == WindowKind::Wallpaper;
32    let fit_to_work_area = config.kind == WindowKind::Normal;
33
34    #[cfg(target_os = "windows")]
35    windows::apply(window, click_through, is_wallpaper, fit_to_work_area);
36    #[cfg(target_os = "macos")]
37    macos::apply(window, click_through, is_wallpaper);
38    #[cfg(not(any(target_os = "windows", target_os = "macos")))]
39    {
40        let _ = (window, click_through, is_wallpaper, fit_to_work_area);
41        tracing::debug!("click-through / wallpaper layer not implemented on this platform");
42    }
43}
44
45#[cfg(target_os = "windows")]
46mod windows {
47    use std::{mem::size_of, ptr};
48
49    use tao::platform::windows::WindowExtWindows;
50    use tao::window::Window;
51    use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, RECT};
52    use windows_sys::Win32::Graphics::Gdi::{
53        GetMonitorInfoW, MonitorFromWindow, MONITORINFO, MONITOR_DEFAULTTONEAREST,
54    };
55    use windows_sys::Win32::UI::WindowsAndMessaging::{
56        EnumWindows, FindWindowExW, FindWindowW, GetWindowLongPtrW, GetWindowRect,
57        SendMessageTimeoutW, SetParent, SetWindowLongPtrW, SetWindowPos, GWL_EXSTYLE, HWND_BOTTOM,
58        SMTO_ABORTIFHUNG, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSIZE, SWP_NOZORDER,
59        WS_EX_LAYERED, WS_EX_TRANSPARENT,
60    };
61
62    pub(crate) fn apply(
63        window: &Window,
64        click_through: bool,
65        is_wallpaper: bool,
66        fit_to_work_area: bool,
67    ) {
68        let hwnd = window.hwnd() as HWND;
69        if hwnd == 0 {
70            return;
71        }
72        unsafe {
73            if fit_to_work_area {
74                center_and_fit_to_work_area(hwnd);
75            }
76            if click_through {
77                let ex = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
78                let new_ex = ex | (WS_EX_LAYERED as isize) | (WS_EX_TRANSPARENT as isize);
79                SetWindowLongPtrW(hwnd, GWL_EXSTYLE, new_ex);
80            }
81            if is_wallpaper {
82                if let Some(worker) = find_desktop_workerw() {
83                    SetParent(hwnd, worker);
84                }
85                SetWindowPos(
86                    hwnd,
87                    HWND_BOTTOM,
88                    0,
89                    0,
90                    0,
91                    0,
92                    SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
93                );
94            }
95        }
96    }
97
98    fn fitted_window_rect(window: RECT, work: RECT) -> RECT {
99        let work_width = (work.right - work.left).max(1);
100        let work_height = (work.bottom - work.top).max(1);
101        let width = (window.right - window.left).clamp(1, work_width);
102        let height = (window.bottom - window.top).clamp(1, work_height);
103        let left = work.left + (work_width - width) / 2;
104        let top = work.top + (work_height - height) / 2;
105        RECT {
106            left,
107            top,
108            right: left + width,
109            bottom: top + height,
110        }
111    }
112
113    unsafe fn center_and_fit_to_work_area(hwnd: HWND) {
114        let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
115        if monitor == 0 {
116            return;
117        }
118        let mut monitor_info = MONITORINFO {
119            cbSize: size_of::<MONITORINFO>() as u32,
120            rcMonitor: RECT {
121                left: 0,
122                top: 0,
123                right: 0,
124                bottom: 0,
125            },
126            rcWork: RECT {
127                left: 0,
128                top: 0,
129                right: 0,
130                bottom: 0,
131            },
132            dwFlags: 0,
133        };
134        let mut window_rect = RECT {
135            left: 0,
136            top: 0,
137            right: 0,
138            bottom: 0,
139        };
140        if GetMonitorInfoW(monitor, &mut monitor_info) == 0
141            || GetWindowRect(hwnd, &mut window_rect) == 0
142        {
143            return;
144        }
145        let fitted = fitted_window_rect(window_rect, monitor_info.rcWork);
146        SetWindowPos(
147            hwnd,
148            0,
149            fitted.left,
150            fitted.top,
151            fitted.right - fitted.left,
152            fitted.bottom - fitted.top,
153            SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER,
154        );
155    }
156
157    /// Locate the desktop `WorkerW` window (the host behind the desktop icons)
158    /// by asking Explorer to (re)create it and then finding the sibling
159    /// `WorkerW` that does not own `SHELLDLL_DefView`.
160    unsafe fn find_desktop_workerw() -> Option<HWND> {
161        let progman = FindWindowW(windows_sys::w!("Progman"), ptr::null::<u16>());
162        if progman == 0 {
163            return None;
164        }
165        // Prompt Explorer to create a dedicated wallpaper WorkerW host.
166        SendMessageTimeoutW(
167            progman,
168            0x52C,
169            0,
170            0,
171            SMTO_ABORTIFHUNG,
172            1000,
173            ptr::null_mut::<usize>(),
174        );
175        let mut worker: HWND = 0;
176        EnumWindows(Some(enum_proc), &mut worker as *mut _ as LPARAM);
177        if worker == 0 {
178            None
179        } else {
180            Some(worker)
181        }
182    }
183
184    extern "system" fn enum_proc(hwnd: HWND, lparam: LPARAM) -> BOOL {
185        unsafe {
186            let defview = FindWindowExW(
187                hwnd,
188                0,
189                windows_sys::w!("SHELLDLL_DefView"),
190                ptr::null::<u16>(),
191            );
192            if defview != 0 {
193                let pworker = &mut *(lparam as *mut HWND);
194                *pworker = FindWindowExW(0, hwnd, windows_sys::w!("WorkerW"), ptr::null::<u16>());
195            }
196            windows_sys::Win32::Foundation::TRUE
197        }
198    }
199
200    #[cfg(test)]
201    mod tests {
202        use super::*;
203
204        #[test]
205        fn centers_window_and_clamps_oversized_height_to_work_area() {
206            let fitted = fitted_window_rect(
207                RECT {
208                    left: 120,
209                    top: 120,
210                    right: 2280,
211                    bottom: 1560,
212                },
213                RECT {
214                    left: 0,
215                    top: 0,
216                    right: 2560,
217                    bottom: 1400,
218                },
219            );
220
221            assert_eq!(
222                (fitted.left, fitted.top, fitted.right, fitted.bottom),
223                (200, 0, 2360, 1400)
224            );
225        }
226
227        #[test]
228        fn centers_smaller_window_without_resizing_it() {
229            let fitted = fitted_window_rect(
230                RECT {
231                    left: 0,
232                    top: 0,
233                    right: 1455,
234                    bottom: 957,
235                },
236                RECT {
237                    left: 0,
238                    top: 0,
239                    right: 2560,
240                    bottom: 1400,
241                },
242            );
243
244            assert_eq!(
245                (fitted.left, fitted.top, fitted.right, fitted.bottom),
246                (552, 221, 2007, 1178)
247            );
248        }
249    }
250}
251
252#[cfg(target_os = "macos")]
253mod macos {
254    use core_graphics::window::{CGWindowLevelForKey, CGWindowLevelKey};
255    use objc::{msg_send, runtime::Object};
256    use tao::platform::macos::WindowExtMacOS;
257    use tao::window::Window;
258
259    pub(crate) fn apply(window: &Window, click_through: bool, is_wallpaper: bool) {
260        let ns_win = window.ns_window() as *mut Object;
261        if ns_win.is_null() {
262            return;
263        }
264        unsafe {
265            let _: () = msg_send![ns_win, setIgnoresMouseEvents: click_through || is_wallpaper];
266            if is_wallpaper {
267                let level: i64 =
268                    CGWindowLevelForKey(CGWindowLevelKey::kCGDesktopWindowLevelKey) as i64;
269                let _: () = msg_send![ns_win, setLevel: level];
270                let _: () = msg_send![ns_win, orderBack: ns_win];
271            }
272        }
273    }
274}