use std::path::PathBuf;
use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_SUCCESS, MAX_PATH};
use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW;
use windows_sys::Win32::System::Registry::{
HKEY, HKEY_CURRENT_USER, KEY_QUERY_VALUE, KEY_SET_VALUE, REG_SZ, RegCloseKey, RegCreateKeyExW,
RegDeleteValueW, RegQueryValueExW, RegSetValueExW,
};
const RUN_SUBKEY: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
const VALUE_NAME: &str = "ai-usagebar-tray";
pub fn is_enabled() -> bool {
match read_run_value() {
Some(stored) => stored == run_command(),
None => false,
}
}
pub fn set_enabled(enabled: bool) -> Result<(), String> {
if enabled {
write_run_value(&run_command())
} else {
delete_run_value()
}
}
fn run_command() -> String {
let path = exe_path().unwrap_or_else(|| PathBuf::from("ai-usagebar-tray.exe"));
format!("\"{}\"", path.to_string_lossy())
}
fn exe_path() -> Option<PathBuf> {
let mut buf = vec![0u16; MAX_PATH as usize];
let n = unsafe { GetModuleFileNameW(std::ptr::null_mut(), buf.as_mut_ptr(), buf.len() as u32) };
if n == 0 || n as usize >= buf.len() {
return None;
}
buf.truncate(n as usize);
Some(PathBuf::from(String::from_utf16_lossy(&buf)))
}
fn utf16_z(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
fn open_run_key() -> Result<HKEY, String> {
let subkey = utf16_z(RUN_SUBKEY);
let mut handle: HKEY = std::ptr::null_mut();
let status = unsafe {
RegCreateKeyExW(
HKEY_CURRENT_USER,
subkey.as_ptr(),
0,
std::ptr::null_mut(),
0,
KEY_QUERY_VALUE | KEY_SET_VALUE,
std::ptr::null(),
&mut handle,
std::ptr::null_mut(),
)
};
if status != ERROR_SUCCESS {
return Err(format!("could not open the Run key ({status})"));
}
Ok(handle)
}
fn read_run_value() -> Option<String> {
let handle = open_run_key().ok()?;
let name = utf16_z(VALUE_NAME);
let mut kind = 0u32;
let mut size = 0u32;
let status = unsafe {
RegQueryValueExW(
handle,
name.as_ptr(),
std::ptr::null(),
&mut kind,
std::ptr::null_mut(),
&mut size,
)
};
if status != ERROR_SUCCESS || kind != REG_SZ || size < 2 {
unsafe { RegCloseKey(handle) };
return None;
}
let mut bytes = vec![0u8; size as usize];
let status = unsafe {
RegQueryValueExW(
handle,
name.as_ptr(),
std::ptr::null(),
&mut kind,
bytes.as_mut_ptr(),
&mut size,
)
};
unsafe { RegCloseKey(handle) };
if status != ERROR_SUCCESS {
return None;
}
let u16s = bytes
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes(*c))
.filter(|u| *u != 0)
.collect::<Vec<_>>();
Some(String::from_utf16_lossy(&u16s))
}
fn write_run_value(command: &str) -> Result<(), String> {
let handle = open_run_key()?;
let name = utf16_z(VALUE_NAME);
let data: Vec<u8> = command
.encode_utf16()
.chain(std::iter::once(0))
.flat_map(u16::to_le_bytes)
.collect();
let status = unsafe {
RegSetValueExW(
handle,
name.as_ptr(),
0,
REG_SZ,
data.as_ptr(),
data.len() as u32,
)
};
unsafe { RegCloseKey(handle) };
if status != ERROR_SUCCESS {
return Err(format!("could not write the Run value ({status})"));
}
Ok(())
}
fn delete_run_value() -> Result<(), String> {
let handle = open_run_key()?;
let name = utf16_z(VALUE_NAME);
let status = unsafe { RegDeleteValueW(handle, name.as_ptr()) };
unsafe { RegCloseKey(handle) };
if status != ERROR_SUCCESS && status != ERROR_FILE_NOT_FOUND {
return Err(format!("could not remove the Run value ({status})"));
}
Ok(())
}