use std::ffi::OsString;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use windows_sys::Win32::Foundation::{
CloseHandle, SetLastError, ERROR_ALREADY_EXISTS, HANDLE, MAX_PATH,
};
use windows_sys::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_REPLACE_EXISTING};
use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW;
use windows_sys::Win32::System::Threading::{CreateMutexW, ReleaseMutex};
use windows_sys::Win32::UI::Shell::{SHChangeNotify, SHCNE_ASSOCCHANGED, SHCNF_IDLIST};
use crate::Error;
pub struct Wide(Vec<u16>);
impl Wide {
pub fn new(text: impl AsRef<std::ffi::OsStr>) -> Self {
let mut buffer: Vec<u16> = text.as_ref().encode_wide().collect();
buffer.push(0);
Wide(buffer)
}
pub fn as_ptr(&self) -> *const u16 {
self.0.as_ptr()
}
}
pub fn current_exe() -> Result<PathBuf, Error> {
static LAUNCH_PATH: OnceLock<PathBuf> = OnceLock::new();
if let Some(path) = LAUNCH_PATH.get() {
return Ok(path.clone());
}
let path = query_current_exe()?;
Ok(LAUNCH_PATH.get_or_init(|| path).clone())
}
fn query_current_exe() -> Result<PathBuf, Error> {
let mut buffer = vec![0u16; MAX_PATH as usize];
loop {
let written = unsafe {
GetModuleFileNameW(
std::ptr::null_mut(),
buffer.as_mut_ptr(),
buffer.len() as u32,
)
};
if written == 0 {
return Err(Error::Windows("GetModuleFileNameW", last_error()));
}
if (written as usize) < buffer.len() {
buffer.truncate(written as usize);
return Ok(PathBuf::from(OsString::from_wide(&buffer)));
}
buffer.resize(buffer.len() * 2, 0);
}
}
pub fn replace(from: &Path, to: &Path) -> Result<(), Error> {
rename(from, to, MOVEFILE_REPLACE_EXISTING)
}
pub fn rename_new(from: &Path, to: &Path) -> Result<(), Error> {
rename(from, to, 0)
}
fn rename(from: &Path, to: &Path, flags: u32) -> Result<(), Error> {
let from_w = Wide::new(from);
let to_w = Wide::new(to);
let ok = unsafe { MoveFileExW(from_w.as_ptr(), to_w.as_ptr(), flags) };
if ok == 0 {
return Err(Error::Windows("MoveFileExW", last_error()));
}
Ok(())
}
pub fn notify_shell() {
unsafe {
SHChangeNotify(
SHCNE_ASSOCCHANGED as i32,
SHCNF_IDLIST,
std::ptr::null(),
std::ptr::null(),
);
}
}
pub struct SwapLock(HANDLE);
impl SwapLock {
pub fn acquire(exe: &Path) -> Result<Self, Error> {
let name = format!("Local\\alt-icons-{:016x}", path_hash(exe));
let name_w = Wide::new(name);
unsafe { SetLastError(0) };
let handle = unsafe { CreateMutexW(std::ptr::null(), 1, name_w.as_ptr()) };
if handle.is_null() {
return Err(Error::Windows("CreateMutexW", last_error()));
}
if last_error() == ERROR_ALREADY_EXISTS {
unsafe { CloseHandle(handle) };
return Err(Error::SwapInProgress);
}
Ok(SwapLock(handle))
}
}
impl Drop for SwapLock {
fn drop(&mut self) {
unsafe {
ReleaseMutex(self.0);
CloseHandle(self.0);
}
}
}
pub fn last_error() -> u32 {
unsafe { windows_sys::Win32::Foundation::GetLastError() }
}
fn path_hash(path: &Path) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for unit in path.as_os_str().encode_wide() {
let folded = if (b'A' as u16..=b'Z' as u16).contains(&unit) {
unit + 32
} else {
unit
};
hash ^= folded as u64;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}