use std::os::windows::process::CommandExt;
use std::process::Command;
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError};
use windows_sys::Win32::System::Threading::{GetExitCodeProcess, WaitForSingleObject, INFINITE};
use windows_sys::Win32::UI::Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW};
use windows_sys::Win32::UI::WindowsAndMessaging::{GetPropW, SW_HIDE};
use winspaces_core::daemon::find_daemon_window;
use winspaces_win32::registry::{delete_hkcu_value, hkcu_value_exists, write_hkcu_string};
use winspaces_win32::text::encode_wide;
const RUN_KEY: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
const APP_NAME: &str = "WinSpaces";
pub const ELEVATED_TASK_NAME: &str = "WinSpaces Daemon (Elevated)";
pub const PROP_ELEVATED: &str = "WinSpacesElevated";
const CREATE_NO_WINDOW: u32 = 0x08000000;
pub fn is_run_key_enabled() -> bool {
hkcu_value_exists(RUN_KEY, APP_NAME)
}
pub fn set_run_key_enabled(enable: bool) -> bool {
if enable {
let Ok(exe) = std::env::current_exe() else {
return false;
};
write_hkcu_string(RUN_KEY, APP_NAME, &format!("\"{}\"", exe.display()))
} else {
delete_hkcu_value(RUN_KEY, APP_NAME)
}
}
pub fn is_elevated_task_installed() -> bool {
let output = Command::new("schtasks.exe")
.args(["/query", "/tn", ELEVATED_TASK_NAME])
.creation_flags(CREATE_NO_WINDOW)
.output();
match output {
Ok(out) => out.status.success(),
Err(_) => false,
}
}
pub fn is_daemon_elevated() -> bool {
let hwnd = find_daemon_window();
if hwnd.is_null() {
return false;
}
let prop = encode_wide(PROP_ELEVATED);
unsafe { !GetPropW(hwnd, prop.as_ptr()).is_null() }
}
pub fn is_elevated_mode_active() -> bool {
is_elevated_task_installed() || is_daemon_elevated()
}
pub fn is_autostart_enabled() -> bool {
is_elevated_task_installed() || is_run_key_enabled()
}
#[derive(Debug, PartialEq, Eq)]
pub enum ElevationResult {
Success,
Cancelled,
Failed(String),
}
pub fn run_elevated_command(flag: &str) -> ElevationResult {
let exe = match std::env::current_exe() {
Ok(p) => p,
Err(e) => return ElevationResult::Failed(format!("Could not get current executable: {e}")),
};
let exe_wide = encode_wide(&exe.to_string_lossy());
let verb_wide = encode_wide("runas");
let params_wide = encode_wide(flag);
let mut info = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS,
hwnd: std::ptr::null_mut(),
lpVerb: verb_wide.as_ptr(),
lpFile: exe_wide.as_ptr(),
lpParameters: params_wide.as_ptr(),
lpDirectory: std::ptr::null(),
nShow: SW_HIDE as _,
hInstApp: std::ptr::null_mut(),
lpIDList: std::ptr::null_mut(),
lpClass: std::ptr::null(),
hkeyClass: std::ptr::null_mut(),
dwHotKey: 0,
Anonymous: unsafe { std::mem::zeroed() },
hProcess: std::ptr::null_mut(),
};
unsafe {
if ShellExecuteExW(&mut info) == 0 {
let err = GetLastError();
const ERROR_CANCELLED: u32 = 1223;
if err == ERROR_CANCELLED {
return ElevationResult::Cancelled;
}
return ElevationResult::Failed(format!("ShellExecuteEx failed with error {err}"));
}
if !info.hProcess.is_null() {
WaitForSingleObject(info.hProcess, INFINITE);
let mut exit_code = 0u32;
GetExitCodeProcess(info.hProcess, &mut exit_code);
CloseHandle(info.hProcess);
if exit_code == 0 {
ElevationResult::Success
} else {
ElevationResult::Failed(format!("Process exited with error code {exit_code}"))
}
} else {
ElevationResult::Success
}
}
}