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}