wallpaper_app/
lib.rs

1use std::default::Default;
2use core::ptr::null_mut;
3use std::sync::Mutex;
4
5use winapi::ctypes::c_int;
6use winapi::shared::minwindef::BOOL;
7
8use winapi::um::errhandlingapi::GetLastError;
9use winapi::um::libloaderapi::GetModuleHandleW;
10
11use winapi::um::winuser::{
12    WNDCLASSW,
13    WNDPROC,
14    MSG,
15    IDC_ARROW,
16    SMTO_NORMAL,
17
18    PM_REMOVE,
19    WS_POPUP,
20    WS_VISIBLE,
21    SM_CXSCREEN,
22    SM_CYSCREEN,
23    SWP_NOZORDER,
24    SWP_NOOWNERZORDER,
25};
26
27use winapi::um::winuser::{
28    RegisterClassW,
29    CreateWindowExW,
30    ShowWindow,
31    EnumWindows,
32    FindWindowW,
33    FindWindowExW,
34    SendMessageTimeoutW,
35
36    GetSystemMetrics,
37    SetWindowPos,
38    SetParent,
39
40    LoadCursorW,
41};
42
43use winapi::um::winuser::{
44    PeekMessageW,
45    TranslateMessage,
46    DispatchMessageW,
47    SystemParametersInfoW,
48};
49
50use winapi::shared::minwindef::{
51    HINSTANCE,
52    LPARAM,
53};
54
55use winapi::um::winuser::SW_SHOW;
56use winapi::shared::windef::HWND;
57
58/// Used in [`FindWindowExW()`]. Is the name of the window class that is the parent of the desktop window:
59///
60/// --- Window ... SHELLDLL_DefView
61///
62/// ------ Window ... WorkerW
63///
64/// --------- OurWindow ... OurWindowClass
65///
66/// See more: <https://learn.microsoft.com/en-us/archive/msdn-magazine/2004/march/c-q-a-list-view-mode-setforegroundwindow-and-class-protection>
67/// <https://www.codeproject.com/Articles/856020/Draw-Behind-Desktop-Icons-in-Windows-plus>
68pub const SHELLDLL_DEF_VIEW_STR : &str = "SHELLDLL_DefView";
69
70/// Used in [`FindWindowExW()`]. Any application that needs to listen to window messages call this Api to create a worker window.
71/// Is the name of the window class we are looking for to put our window into as a child:
72///
73/// --- Window ... SHELLDLL_DefView
74///
75/// ------ Window ... WorkerW
76///
77/// --------- OurWindow ... OurWindowClass
78///
79/// See more: <https://learn.microsoft.com/en-us/archive/msdn-magazine/2004/march/c-q-a-list-view-mode-setforegroundwindow-and-class-protection>
80/// <https://www.codeproject.com/Articles/856020/Draw-Behind-Desktop-Icons-in-Windows-plus>
81pub const WORKER_W_STR : &str = "WorkerW";
82
83pub mod drawing;
84
85/// Handle to desktop window app. Any application that needs to listen to window messages call this Api to create a worker window.
86static mut WORKER_W : Mutex::<HWND> = Mutex::new(null_mut());
87
88/// Create WNDCLASSW and handle to it with custom name and WNDPROC.
89///
90/// <i>window_procedure</i> - A callback function, which you define in your application, that processes messages sent to a window.
91///
92/// Read more:
93///
94/// WNDCLASSW - <https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw>
95///
96/// WNDPROC - <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc>
97///
98/// Example:
99/// ```
100/// fn main() {
101///     let class_name = wide_null("My app window Class");
102///     let window_name = wide_null("My app window");
103///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
104///     let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);
105///     create_window(window_handle);
106/// }
107/// ```
108///
109/// Procedure example:
110/// ```
111/// pub unsafe extern "system" fn window_procedure(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM,) -> LRESULT {
112///    match msg {
113///        WM_NCCREATE => {
114///            println!("NC Create");
115///            let createstruct: *mut CREATESTRUCTW = l_param as *mut _;
116///            if createstruct.is_null() {
117///                return 0;
118///            }
119///            let boxed_i32_ptr = (*createstruct).lpCreateParams;
120///            SetWindowLongPtrW(hwnd, GWLP_USERDATA, boxed_i32_ptr as LONG_PTR);
121///            return 1;
122///        }
123///        WM_CREATE => println!("WM Create"),
124///        WM_CLOSE => drop(DestroyWindow(hwnd)),
125///        WM_DESTROY => {
126///            let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut i32;
127///            drop(Box::from_raw(ptr));
128///            println!("Cleaned up the box.");
129///            PostQuitMessage(0);
130///        }
131///        WM_ERASEBKGND => return 1,
132///        WM_PAINT => your_paint_func(hwnd),
133///        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
134///    }
135///
136///    0
137///  }
138/// ```
139pub fn create_window_class(name: &Vec<u16>, window_procedure: WNDPROC) -> (WNDCLASSW, HINSTANCE) {
140    let h_instance = unsafe { GetModuleHandleW(core::ptr::null()) };
141
142    let mut wc = WNDCLASSW::default();
143    wc.lpfnWndProc = window_procedure;
144    wc.hInstance = h_instance;
145    wc.lpszClassName = name.as_ptr();
146    wc.hCursor = unsafe { LoadCursorW(null_mut(), IDC_ARROW) };
147    (wc, h_instance)
148}
149
150/// Create window handle for window class (WNDCLASSW) with <i>window_name</i>
151///
152/// <i>wc</i> and <i>h_instance</i> - can be results o the [`create_window_class()`] func
153///
154/// Example:
155/// ```
156/// fn main() {
157///     let class_name = wide_null("My app window Class");
158///     let window_name = wide_null("My app window");
159///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
160///     let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);
161///     create_window(window_handle);
162/// }
163/// ```
164pub fn create_window_handle(wc: &WNDCLASSW, wc_name: &Vec<u16>, window_name: &Vec<u16>, h_instance: HINSTANCE, ) -> HWND {
165    let atom = unsafe { RegisterClassW(wc) };
166    if atom == 0 {
167        let last_error = unsafe { GetLastError() };
168        panic!("Could not register the window class, error code: {}", last_error);
169    }
170
171    let lparam: *mut i32 = Box::leak(Box::new(5_i32));
172    let hwnd = unsafe {
173        CreateWindowExW(
174            0,
175            wc_name.as_ptr(),
176            window_name.as_ptr(),
177            WS_POPUP | WS_VISIBLE,
178            0,
179            0,
180            0,
181            0,
182            core::ptr::null_mut(),
183            core::ptr::null_mut(),
184            h_instance,
185            lparam.cast(),
186        )
187    };
188    if hwnd.is_null() {
189        panic!("Failed to create a window.");
190    }
191
192    hwnd
193}
194
195/// Create window using window <i>handle</i>.
196///
197/// <i>handle</i> - can be result of [`create_window_handle()`] func
198///
199/// Example:
200/// ```
201/// fn main() {
202///     let class_name = wide_null("My app window Class");
203///     let window_name = wide_null("My app window");
204///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
205///     let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);
206///     create_window(window_handle);
207/// }
208/// ```
209pub fn create_window(handle: HWND) {
210    let _previously_visible = unsafe { ShowWindow(handle, SW_SHOW) };
211}
212
213/// Find `Progman` and get handle. Progman requires for [`try_spawn_worker_w()`] func
214///
215/// Example:
216/// ```
217/// fn main() {
218///     let progman_h = get_progman_handle();
219///     if try_spawn_worker_w(progman_h).is_err() {
220///         panic!("`Progman` failed to spawn WorkerW!");
221///     };
222/// }
223/// ```
224pub fn get_progman_handle() -> HWND {
225    let h_progman = unsafe { FindWindowW(wide_null("Progman").as_ptr(), null_mut()) };
226    h_progman
227}
228
229/// Message to `Progman` to spawn a `WorkerW`
230///
231/// Send 0x052C to Progman. This message directs Progman to spawn a
232/// WorkerW behind the desktop icons. If it is already there, nothing
233/// happens.
234///
235/// Example:
236/// ```
237/// fn main() {
238///     let progman_h = get_progman_handle();
239///     if try_spawn_worker_w(progman_h).is_err() {
240///         panic!("`Progman` failed to spawn WorkerW!");
241///     };
242/// }
243/// ```
244pub fn try_spawn_worker_w(progman_handle: HWND) -> Result<(), &'static str> {
245    // Requare all for support all windows versions!
246    let send_message_results = unsafe { [
247        SendMessageTimeoutW(progman_handle, 0x052C, 0, 0, SMTO_NORMAL, 1000, null_mut()),
248        SendMessageTimeoutW(progman_handle, 0x052C, 0x0d, 0, SMTO_NORMAL, 1000, null_mut()),
249        SendMessageTimeoutW(progman_handle, 0x052C, 0x0d, 1, SMTO_NORMAL, 1000, null_mut())
250    ] };
251
252    if send_message_results.iter().all(|r| *r == 0) {
253        return Err("`Progman` failed to spawn WorkerW!");
254    }
255
256    Ok(())
257}
258
259/// Find the newly created `WorkerW`
260/// Example:
261/// ```
262/// fn main() {
263///     let class_name = wide_null("My app window Class");
264///     let window_name = wide_null("My app window");
265///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
266///     let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);let worker_w_handle = find_worker_w();
267///     pull_window_to_desktop(window_handle, worker_w_handle);
268///     let worker_w_handle = find_worker_w();
269///     pull_window_to_desktop(window_handle, worker_w_handle);
270/// }
271/// ```
272pub fn find_worker_w() -> HWND {
273    unsafe {
274        EnumWindows(Some(enum_windows_proc), 0);
275        return WORKER_W.lock().unwrap().clone();
276    };
277}
278
279/// Sets worker_w_handle as parent to handle and set window size to [`winapi::um::winuser::SM_CXSCREEN`] x [`winapi::um::winuser::SM_CYSCREEN`]
280///
281/// Used flags:
282///
283/// <b>SWP_NOOWNERZORDER</b> - Does not change the owner window's position in the Z order.
284///
285/// <b>SWP_NOZORDER</b> - Retains the current Z order (ignores the hWndInsertAfter parameter).
286///
287/// Example:
288/// ```
289/// fn main() {
290///     let class_name = wide_null("My app window Class");
291///     let window_name = wide_null("My app window");
292///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
293///     let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);let worker_w_handle = find_worker_w();
294///     pull_window_to_desktop(window_handle, worker_w_handle);
295/// }
296/// ```
297pub fn pull_window_to_desktop(handle: HWND, worker_w_handle: HWND) {
298    unsafe { SetParent(handle, worker_w_handle) };
299    unsafe {
300        SetWindowPos(
301            handle,
302            null_mut(),
303            0,
304            0,
305            GetSystemMetrics(SM_CXSCREEN) as c_int,
306            GetSystemMetrics(SM_CYSCREEN) as c_int,
307            SWP_NOOWNERZORDER | SWP_NOZORDER
308        )
309    };
310
311    unsafe { SystemParametersInfoW(20, 0, null_mut(), 0x1) };
312}
313
314/// It receives top-level window handles and find windows with class [`SHELLDLL_DEF_VIEW_STR`] + child with [`WORKER_W_STR`] class
315///
316/// Read more: <https://learn.microsoft.com/ru-ru/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)>
317pub unsafe extern "system" fn enum_windows_proc(hwnd: HWND, _l_param: LPARAM) -> BOOL {
318    let shelldll_def_view_name = wide_null(SHELLDLL_DEF_VIEW_STR);
319    let cur_hwnd = unsafe { FindWindowExW(hwnd, null_mut(), shelldll_def_view_name.as_ptr(), null_mut()) };
320
321    if !cur_hwnd.is_null()
322    {
323        println!("{} window found!", SHELLDLL_DEF_VIEW_STR);
324        let worker_w_name = wide_null(WORKER_W_STR);
325        // Gets the WorkerW Window after the current one.
326        let mut worker = WORKER_W.lock().unwrap();
327        unsafe { *worker = FindWindowExW(null_mut(), hwnd, worker_w_name.as_ptr(), null_mut()) };
328        if !worker.is_null() {
329            println!("{} window found!", WORKER_W_STR);
330        }
331    }
332
333    return 1;
334}
335
336/// A simple function to handle window messages.
337/// You can use it, or define your own. It use PeekMessageW() (<https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew>)
338///
339/// Returns TRUE if the message was received and processed
340///
341/// Example:
342/// ```
343/// let msg = MSG::default();
344/// loop {
345///     if handle_window_messages(msg) {
346///         println!("Message received and processed!");
347///     }
348///     else {
349///         std::thread::sleep(std::time::Duration::from_micros(100));
350///     }
351/// }
352/// ```
353pub fn handle_window_messages(mut msg: MSG) -> bool {
354    let message_return = unsafe { PeekMessageW(&mut msg, null_mut(), 0, 0, PM_REMOVE) };
355    if message_return == 0 {
356        return false;
357    } else if message_return == -1 {
358        let last_error = unsafe { GetLastError() };
359        panic!("Error with `GetMessageW`, error code: {}", last_error);
360    } else {
361        unsafe {
362            TranslateMessage(&msg);
363            DispatchMessageW(&msg);
364        }
365    }
366
367    true
368}
369
370/// Combines low-lewel methods for simplify create window at desktop!
371///
372/// <i>name</i> - name of new window
373///
374/// <i>window_procedure</i> - A callback function, which you define in your application, that processes messages sent to a window.
375///
376/// Read more about WNDPROC - <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc>
377///
378/// Usage example:
379///  ```
380/// fn main() {
381///     unsafe { SetProcessDPIAware(); }
382///     let window_handle = create_desktop_window_fast("My app window", Some(window_procedure));
383///     loop_graphics(window_handle);
384/// }
385///  ```
386///
387/// Procedure example:
388/// ```
389/// pub unsafe extern "system" fn window_procedure(hwnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM,) -> LRESULT {
390///    match msg {
391///        WM_NCCREATE => {
392///            println!("NC Create");
393///            let createstruct: *mut CREATESTRUCTW = l_param as *mut _;
394///            if createstruct.is_null() {
395///                return 0;
396///            }
397///            let boxed_i32_ptr = (*createstruct).lpCreateParams;
398///            SetWindowLongPtrW(hwnd, GWLP_USERDATA, boxed_i32_ptr as LONG_PTR);
399///            return 1;
400///        }
401///        WM_CREATE => println!("WM Create"),
402///        WM_CLOSE => drop(DestroyWindow(hwnd)),
403///        WM_DESTROY => {
404///            let ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut i32;
405///            drop(Box::from_raw(ptr));
406///            println!("Cleaned up the box.");
407///            PostQuitMessage(0);
408///        }
409///        WM_ERASEBKGND => return 1,
410///        WM_PAINT => your_paint_func(hwnd),
411///        _ => return DefWindowProcW(hwnd, msg, w_param, l_param),
412///    }
413///
414///    0
415///  }
416/// ```
417pub fn create_desktop_window_fast(name: &str, window_procedure: WNDPROC) -> HWND {
418    let class_name = wide_null(format!("{} Class", name).as_str());
419    let window_name = wide_null(name);
420    let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
421    let window_handle = create_window_handle(&window_class, &class_name, &window_name, h_instance);
422    create_window(window_handle);
423
424    let progman_h = get_progman_handle();
425    if try_spawn_worker_w(progman_h).is_err() {
426        panic!("`Progman` failed to spawn WorkerW!");
427    };
428
429    let worker_w_handle = find_worker_w();
430    pull_window_to_desktop(window_handle, worker_w_handle);
431
432    window_handle
433}
434
435/// Convert string to windows friedly format.
436///
437/// !!! With this strings use W-functions !!!
438///
439/// For example: [`winapi::um::libloaderapi::GetModuleHandleW()`] or [`winapi::um::winuser::RegisterClassW`]
440///
441/// Usage example:
442/// ```
443/// fn main() {
444///     let class_name = wide_null("My app window Class");
445///     let (window_class, h_instance) = create_window_class(&class_name, window_procedure);
446/// }
447/// ```
448pub fn wide_null(s: &str) -> Vec<u16> {
449    s.encode_utf16().chain(Some(0)).collect()
450}