#![cfg(windows)]
use std::path::Path;
use anyhow::Context;
use tracing::warn;
use windows::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_INVALID_DATA, LPARAM, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::{
HWND_BROADCAST, SMTO_ABORTIFHUNG, SendMessageTimeoutW, WM_SETTINGCHANGE,
};
use windows::core::{HRESULT, w};
use windows_registry::{CURRENT_USER, HSTRING};
use uv_static::EnvVars;
pub fn prepend_path(path: &Path) -> anyhow::Result<bool> {
let windows_path = get_windows_path_var()?;
let windows_path =
windows_path.and_then(|windows_path| prepend_to_path(&windows_path, HSTRING::from(path)));
let Some(windows_path) = windows_path else {
return Ok(false);
};
apply_windows_path_var(&windows_path)?;
Ok(true)
}
fn apply_windows_path_var(path: &HSTRING) -> anyhow::Result<()> {
let environment = CURRENT_USER.create("Environment")?;
if path.is_empty() {
environment.remove_value(EnvVars::PATH)?;
} else {
environment.set_expand_hstring(EnvVars::PATH, path)?;
}
broadcast_environment_changes();
Ok(())
}
#[allow(unsafe_code)]
fn broadcast_environment_changes() {
unsafe {
SendMessageTimeoutW(
HWND_BROADCAST,
WM_SETTINGCHANGE,
WPARAM(0),
LPARAM(w!("Environment").as_ptr() as isize), SMTO_ABORTIFHUNG,
5000,
None,
);
}
}
fn get_windows_path_var() -> anyhow::Result<Option<HSTRING>> {
let environment = CURRENT_USER
.create("Environment")
.context("Failed to open `Environment` key")?;
let reg_value = environment.get_hstring(EnvVars::PATH);
match reg_value {
Ok(reg_value) => Ok(Some(reg_value)),
Err(err) if err.code() == HRESULT::from(ERROR_INVALID_DATA) => {
warn!("`HKEY_CURRENT_USER\\Environment\\PATH` is a non-string");
Ok(None)
}
Err(err) if err.code() == HRESULT::from(ERROR_FILE_NOT_FOUND) => Ok(Some(HSTRING::new())),
Err(err) => Err(err.into()),
}
}
fn prepend_to_path(existing_path: &HSTRING, path: HSTRING) -> Option<HSTRING> {
if existing_path.is_empty() {
Some(path)
} else if existing_path.windows(path.len()).any(|p| *p == *path) {
None
} else {
let mut new_path = path.to_os_string();
new_path.push(";");
new_path.push(existing_path.to_os_string());
Some(HSTRING::from(new_path))
}
}