use crate::constants::{
JAVA_HOME_BIN_WINDOWS, JVR_OS_WINDOWS_ENVIRONMENT_SYSTEM_SUB_KEY,
JVR_OS_WINDOWS_ENVIRONMENT_USER_SUB_KEY, PATH_ENVIRONMENT_VARIABLE_WINDOWS,
};
use std::io;
use windows::core::PCSTR;
use windows::{Win32::Foundation::*, Win32::UI::WindowsAndMessaging::*};
use windows_sys::Win32::System::Registry;
use winreg::enums::{RegType, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE};
use winreg::types::FromRegValue;
use winreg::{RegKey, RegValue};
use crate::env::EnvironmentAccessor;
pub struct WindowsEnvironmentAccessor {}
impl WindowsEnvironmentAccessor {
pub fn new() -> Self {
Self {}
}
fn open_env_subkey(
&self,
h_key: winreg::HKEY,
sub_key: &str,
access: Registry::REG_SAM_FLAGS,
) -> io::Result<RegKey> {
let root = RegKey::predef(h_key);
root.open_subkey_with_flags(sub_key, access).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to open registry subkey '{}': {}", sub_key, e),
)
})
}
fn create_or_open_env_subkey(&self, h_key: winreg::HKEY, sub_key: &str) -> io::Result<RegKey> {
let root = RegKey::predef(h_key);
let (key, _) = root
.create_subkey_with_flags(sub_key, KEY_WRITE)
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to create/open registry subkey '{}': {}", sub_key, e),
)
})?;
Ok(key)
}
fn get_environment_variable(
&self,
h_key: winreg::HKEY,
sub_key: &str,
name: &str,
) -> io::Result<String> {
let env_key = self.open_env_subkey(h_key, sub_key, KEY_READ)?;
env_key.get_value(name).map_err(|e| {
if e.kind() == io::ErrorKind::NotFound {
io::Error::new(
io::ErrorKind::NotFound,
format!("Environment variable '{}' not found in '{}'", name, sub_key),
)
} else {
io::Error::new(io::ErrorKind::Other, e.to_string())
}
})
}
fn set_environment_variable(
&self,
h_key: winreg::HKEY,
sub_key: &str,
name: &str,
value: &str,
) -> io::Result<()> {
let env_key = self.create_or_open_env_subkey(h_key, sub_key)?;
env_key
.set_value(name, &value)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
self.broadcast_environment_change()
}
fn ensure_path_contains(&self, entry: &str, user: bool) -> io::Result<()> {
let (h_key, sub_key) = if user {
(HKEY_CURRENT_USER, JVR_OS_WINDOWS_ENVIRONMENT_USER_SUB_KEY)
} else {
(
HKEY_LOCAL_MACHINE,
JVR_OS_WINDOWS_ENVIRONMENT_SYSTEM_SUB_KEY,
)
};
let root = RegKey::predef(h_key);
let env_key = root.open_subkey_with_flags(sub_key, KEY_READ | KEY_WRITE)?;
let path_value: String = match env_key.get_raw_value(PATH_ENVIRONMENT_VARIABLE_WINDOWS) {
Ok(raw) if raw.vtype == RegType::REG_EXPAND_SZ || raw.vtype == RegType::REG_SZ => {
String::from_reg_value(&raw)?
}
Err(ref e) if e.kind() == io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(e),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PATH is not a string type",
))
}
};
let paths: Vec<&str> = path_value.split(';').collect();
let found = paths.iter().any(|p| p.trim().eq_ignore_ascii_case(entry));
if !found {
let new_path = if path_value.is_empty() {
entry.to_string()
} else if path_value.ends_with(';') {
format!("{}{}", path_value, entry)
} else {
format!("{};{}", path_value, entry)
};
let reg_value = self.string_to_reg_expand_sz(&new_path);
env_key.set_raw_value(PATH_ENVIRONMENT_VARIABLE_WINDOWS, ®_value)?;
self.broadcast_environment_change()?;
}
Ok(())
}
fn string_to_reg_expand_sz(&self, v: &str) -> RegValue {
let mut wide: Vec<u16> = v.encode_utf16().collect();
wide.push(0);
let bytes: Vec<u8> = unsafe {
std::slice::from_raw_parts(
wide.as_ptr() as *const u8,
wide.len() * std::mem::size_of::<u16>(),
)
.to_vec()
};
RegValue {
vtype: RegType::REG_EXPAND_SZ,
bytes,
}
}
}
impl Default for WindowsEnvironmentAccessor {
fn default() -> Self {
Self::new()
}
}
impl EnvironmentAccessor for WindowsEnvironmentAccessor {
fn set_user_environment_variable(&self, name: &str, value: &str) -> io::Result<()> {
self.set_environment_variable(
HKEY_CURRENT_USER,
JVR_OS_WINDOWS_ENVIRONMENT_USER_SUB_KEY,
name,
value,
)
}
fn get_user_environment_variable(&self, name: &str) -> io::Result<String> {
self.get_environment_variable(
HKEY_CURRENT_USER,
JVR_OS_WINDOWS_ENVIRONMENT_USER_SUB_KEY,
name,
)
}
fn set_system_environment_variable(&self, name: &str, value: &str) -> io::Result<()> {
self.set_environment_variable(
HKEY_LOCAL_MACHINE,
JVR_OS_WINDOWS_ENVIRONMENT_SYSTEM_SUB_KEY,
name,
value,
)
}
fn get_system_environment_variable(&self, name: &str) -> io::Result<String> {
self.get_environment_variable(
HKEY_LOCAL_MACHINE,
JVR_OS_WINDOWS_ENVIRONMENT_SYSTEM_SUB_KEY,
name,
)
}
fn broadcast_environment_change(&self) -> io::Result<()> {
const ENVIRONMENT_CSTR: PCSTR = PCSTR(b"Environment\0".as_ptr());
unsafe {
let _ = SendMessageTimeoutA(
HWND_BROADCAST,
WM_SETTINGCHANGE,
WPARAM(0),
LPARAM(ENVIRONMENT_CSTR.0 as isize),
SMTO_ABORTIFHUNG,
100, None,
);
}
Ok(())
}
fn ensure_java_home_bin_in_user_path(&self) -> io::Result<()> {
self.ensure_path_contains(JAVA_HOME_BIN_WINDOWS, true)
}
fn ensure_java_home_bin_in_system_path(&self) -> io::Result<()> {
self.ensure_path_contains(JAVA_HOME_BIN_WINDOWS, false)
}
}