Skip to main content

cleansys_gui/
platform.rs

1//! Small platform-integration helpers for the GUI: desktop notifications and
2//! (Windows-only) relaunching the process elevated.
3
4/// Send a desktop notification announcing that a cleaning run finished.
5/// Best-effort: failures are logged but never surfaced to the UI (not every
6/// desktop environment/session has a notification daemon available, and
7/// that's fine — the in-app activity log always has the same information).
8pub fn notify_completion(summary: &str) {
9    let result = notify_rust::Notification::new()
10        .summary("CleanSys")
11        .body(summary)
12        .appname("CleanSys")
13        .show();
14
15    if let Err(e) = result {
16        log::debug!("desktop notification failed (non-fatal): {e}");
17    }
18}
19
20/// Relaunch the current executable with an elevation request.
21///
22/// On Windows this uses `ShellExecuteW` with the `"runas"` verb, which
23/// triggers the standard UAC consent prompt. On other platforms there is no
24/// equivalent single-click relaunch (Unix elevation is handled by the
25/// sudo-password dialog instead), so this is a no-op.
26pub fn relaunch_as_admin() {
27    #[cfg(target_os = "windows")]
28    {
29        if let Err(e) = windows_impl::relaunch_elevated() {
30            log::warn!("Failed to relaunch as Administrator: {e}");
31        }
32    }
33    #[cfg(not(target_os = "windows"))]
34    {
35        log::debug!("relaunch_as_admin() is a no-op on this platform");
36    }
37}
38
39#[cfg(target_os = "windows")]
40mod windows_impl {
41    use anyhow::{Context, Result};
42    use std::os::windows::ffi::OsStrExt;
43    use windows::core::PCWSTR;
44    use windows::Win32::UI::Shell::ShellExecuteW;
45    use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
46
47    /// Null-terminated UTF-16 encoding of an OS string, for use with the
48    /// `*W` (wide-character) Win32 APIs.
49    fn to_wide(s: &std::ffi::OsStr) -> Vec<u16> {
50        s.encode_wide().chain(std::iter::once(0)).collect()
51    }
52
53    pub fn relaunch_elevated() -> Result<()> {
54        let exe = std::env::current_exe().context("failed to determine current executable")?;
55        let exe_wide = to_wide(exe.as_os_str());
56        let verb_wide = to_wide(std::ffi::OsStr::new("runas"));
57
58        // SAFETY: all string pointers reference `Vec<u16>` buffers that are
59        // kept alive for the duration of this call, and are properly
60        // null-terminated as `ShellExecuteW` requires.
61        let result = unsafe {
62            ShellExecuteW(
63                None,
64                PCWSTR(verb_wide.as_ptr()),
65                PCWSTR(exe_wide.as_ptr()),
66                PCWSTR::null(),
67                PCWSTR::null(),
68                SW_SHOWNORMAL,
69            )
70        };
71
72        // ShellExecuteW returns a value > 32 on success (per Win32 docs);
73        // anything else (including the user declining the UAC prompt)
74        // indicates failure.
75        if (result.0 as isize) <= 32 {
76            anyhow::bail!("ShellExecuteW returned error code {}", result.0 as isize);
77        }
78
79        // Exit this (unelevated) instance now that the elevated copy is starting.
80        std::process::exit(0);
81    }
82}