1use crate::config::{WindowConfig, WindowKind};
14use tao::window::{Icon, Window};
15
16pub 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
28pub 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 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 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}