use std::env;
use std::path::PathBuf;
use crate::config::{self, StartupMirror};
use crate::i18n::{t, t_args};
use windows::core::w;
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows::Win32::System::Registry::{
RegCloseKey, RegDeleteValueW, RegOpenKeyExW, RegQueryValueExW, RegSetValueExW, HKEY,
HKEY_CURRENT_USER, KEY_QUERY_VALUE, KEY_SET_VALUE, REG_SAM_FLAGS, REG_SZ,
};
const RUN_KEY: windows::core::PCWSTR = w!("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
const VALUE_NAME: windows::core::PCWSTR = w!("desk0k");
fn exe_path() -> Result<PathBuf, String> {
let p = env::current_exe()
.map(|p| p.canonicalize().unwrap_or(p))
.map_err(|e| format!("{e}"))?;
Ok(strip_unc_prefix(p))
}
fn strip_unc_prefix(p: PathBuf) -> PathBuf {
if let Some(s) = p.to_str() {
if let Some(rest) = s.strip_prefix("\\\\?\\") {
return PathBuf::from(rest);
}
}
p
}
fn command_line(exe: &PathBuf, profile: &str) -> Vec<u16> {
let mut s: Vec<u16> = format!("\"{}\" restore \"{profile}\"", exe.display())
.encode_utf16()
.collect();
s.push(0);
s
}
fn open_run_key(access: REG_SAM_FLAGS) -> Result<HKEY, String> {
unsafe {
let mut key = HKEY::default();
let status = RegOpenKeyExW(HKEY_CURRENT_USER, RUN_KEY, 0, access, &mut key);
if status.is_err() {
return Err(t_args(
"startup.error_open_key",
&[("error", &format!("{status:?}"))],
));
}
Ok(key)
}
}
pub fn enable(profile: &str) -> Result<String, String> {
let exe = exe_path()?;
let cmd = command_line(&exe, profile);
unsafe {
let key = open_run_key(KEY_SET_VALUE)?;
let status = RegSetValueExW(
key,
VALUE_NAME,
0,
REG_SZ,
Some(std::slice::from_raw_parts(
cmd.as_ptr() as *const u8,
cmd.len() * 2,
)),
);
let _ = RegCloseKey(key);
if status.is_err() {
return Err(t_args(
"startup.error_write",
&[("error", &format!("{status:?}"))],
));
}
}
let command = format!("\"{}\" restore \"{profile}\"", exe.display());
config::set_startup(Some(StartupMirror {
enabled: true,
profile: Some(profile.to_string()),
command: Some(command),
}));
Ok(t_args(
"startup.enable_success",
&[("profile", profile), ("path", &exe.display().to_string())],
))
}
pub fn disable() -> Result<String, String> {
unsafe {
let key = open_run_key(KEY_SET_VALUE)?;
let status = RegDeleteValueW(key, VALUE_NAME);
let _ = RegCloseKey(key);
if status.is_err() {
if status == ERROR_FILE_NOT_FOUND {
config::set_startup(Some(StartupMirror {
enabled: false,
profile: None,
command: None,
}));
return Ok(t("startup.already_disabled"));
}
return Err(t_args(
"startup.error_remove",
&[("error", &format!("{status:?}"))],
));
}
}
config::set_startup(Some(StartupMirror {
enabled: false,
profile: None,
command: None,
}));
Ok(t("startup.disabled"))
}
pub fn status() -> Result<String, String> {
if let Some(mirror) = config::get_startup() {
if !mirror.enabled {
return Ok(t("startup.status_disabled"));
}
let command = mirror.command.unwrap_or_default();
return Ok(t_args("startup.status_enabled", &[("command", &command)]));
}
unsafe {
let key = open_run_key(KEY_QUERY_VALUE)?;
let mut size: u32 = 0;
let query = RegQueryValueExW(key, VALUE_NAME, None, None, None, Some(&mut size));
if query.is_err() {
let _ = RegCloseKey(key);
return Ok(t("startup.status_disabled"));
}
let mut buf = vec![0u8; size as usize];
let query = RegQueryValueExW(
key,
VALUE_NAME,
None,
None,
Some(buf.as_mut_ptr()),
Some(&mut size),
);
let _ = RegCloseKey(key);
if query.is_err() {
return Ok(t("startup.status_disabled"));
}
let wide = std::slice::from_raw_parts(buf.as_ptr() as *const u16, (size as usize) / 2);
let text = String::from_utf16_lossy(wide)
.trim_end_matches('\0')
.to_string();
let profile = extract_profile(&text);
config::set_startup(Some(StartupMirror {
enabled: true,
profile,
command: Some(text.clone()),
}));
Ok(t_args("startup.status_enabled", &[("command", &text)]))
}
}
fn extract_profile(command: &str) -> Option<String> {
let marker = " restore \"";
let start = command.find(marker)? + marker.len();
let rest = &command[start..];
let end = rest.find('\"')?;
Some(rest[..end].to_string())
}