use anyhow::{Context, Result};
pub fn configure(enable: bool, port: u16) -> Result<()> {
#[cfg(windows)]
{
configure_windows(enable, port)
}
#[cfg(not(windows))]
{
configure_auto_launch(enable, port)
}
}
pub fn is_enabled() -> bool {
#[cfg(windows)]
{
use winreg::RegKey;
use winreg::enums::{HKEY_CURRENT_USER, KEY_READ};
RegKey::predef(HKEY_CURRENT_USER)
.open_subkey_with_flags(RUN_KEY, KEY_READ)
.and_then(|key| key.get_value::<String, _>(VALUE_NAME))
.is_ok()
}
#[cfg(target_os = "linux")]
{
std::env::var("HOME")
.map(|home| is_enabled_in(std::path::Path::new(&home)))
.unwrap_or(false)
}
#[cfg(all(not(windows), not(target_os = "linux")))]
{
let Ok(exe) = std::env::current_exe() else {
return false;
};
let path = exe.to_string_lossy();
auto_launch::AutoLaunchBuilder::new()
.set_app_name("llmtrim")
.set_app_path(path.as_ref())
.build()
.and_then(|auto| auto.is_enabled())
.unwrap_or(false)
}
}
#[cfg(target_os = "linux")]
fn is_enabled_in(base: &std::path::Path) -> bool {
xdg_autostart_dir(base).join("llmtrim.desktop").exists()
}
#[cfg(windows)]
const RUN_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
#[cfg(windows)]
const STARTUP_APPROVED_KEY: &str =
r"Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run";
#[cfg(windows)]
const VALUE_NAME: &str = "llmtrim";
#[cfg(windows)]
fn configure_windows(enable: bool, port: u16) -> Result<()> {
use winreg::RegKey;
use winreg::enums::{HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
if enable {
let exe = std::env::current_exe().context("could not find the llmtrim executable")?;
let (key, _) = RegKey::predef(HKEY_CURRENT_USER)
.create_subkey_with_flags(RUN_KEY, KEY_READ | KEY_WRITE)
.with_context(|| format!("failed to open HKCU\\{RUN_KEY}"))?;
let cmd = format!("\"{}\" serve --port {} --supervised", exe.display(), port);
key.set_value(VALUE_NAME, &cmd)
.context("failed to set llmtrim autostart in the registry Run key")?;
purge_legacy_autostart();
} else {
remove_autostart_everywhere();
}
Ok(())
}
#[cfg(windows)]
fn best_effort_delete(hive: winreg::HKEY, subkey: &str) {
use winreg::RegKey;
use winreg::enums::KEY_WRITE;
if let Ok(key) = RegKey::predef(hive).open_subkey_with_flags(subkey, KEY_WRITE) {
let _ = key.delete_value(VALUE_NAME);
}
}
#[cfg(windows)]
fn purge_legacy_autostart() {
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
best_effort_delete(HKEY_LOCAL_MACHINE, RUN_KEY);
best_effort_delete(HKEY_CURRENT_USER, STARTUP_APPROVED_KEY);
best_effort_delete(HKEY_LOCAL_MACHINE, STARTUP_APPROVED_KEY);
}
#[cfg(windows)]
fn remove_autostart_everywhere() {
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
best_effort_delete(HKEY_CURRENT_USER, RUN_KEY);
best_effort_delete(HKEY_LOCAL_MACHINE, RUN_KEY);
best_effort_delete(HKEY_CURRENT_USER, STARTUP_APPROVED_KEY);
best_effort_delete(HKEY_LOCAL_MACHINE, STARTUP_APPROVED_KEY);
}
#[cfg(target_os = "linux")]
fn xdg_autostart_dir(base: &std::path::Path) -> std::path::PathBuf {
base.join(".config").join("autostart")
}
#[cfg(target_os = "linux")]
fn desktop_entry(exe: &std::path::Path, port: u16) -> String {
format!(
"[Desktop Entry]\n\
Type=Application\n\
Version=1.0\n\
Name=llmtrim\n\
Comment=llmtrim startup script\n\
Exec={} serve --port {} --supervised\n\
StartupNotify=false\n\
Terminal=false",
exe.display(),
port
)
}
#[cfg(target_os = "linux")]
fn configure_in(enable: bool, port: u16, base: &std::path::Path) -> Result<()> {
let dir = xdg_autostart_dir(base);
let file = dir.join("llmtrim.desktop");
if enable {
let exe = std::env::current_exe().context("could not find the llmtrim executable")?;
std::fs::create_dir_all(&dir)
.with_context(|| format!("failed to create {}", dir.display()))?;
std::fs::write(&file, desktop_entry(&exe, port))
.with_context(|| format!("failed to write {}", file.display()))?;
} else if file.exists() {
std::fs::remove_file(&file)
.with_context(|| format!("failed to remove {}", file.display()))?;
}
Ok(())
}
#[cfg(target_os = "linux")]
fn configure_auto_launch(enable: bool, port: u16) -> Result<()> {
let home = std::env::var("HOME")
.context("HOME is not set — cannot determine XDG autostart directory")?;
configure_in(enable, port, std::path::Path::new(&home))
}
#[cfg(all(not(windows), not(target_os = "linux")))]
fn configure_auto_launch(enable: bool, port: u16) -> Result<()> {
use auto_launch::AutoLaunchBuilder;
let exe = std::env::current_exe().context("could not find the llmtrim executable")?;
let path = exe.to_string_lossy();
let port_arg = port.to_string();
let auto = AutoLaunchBuilder::new()
.set_app_name("llmtrim")
.set_app_path(path.as_ref())
.set_args(&["serve", "--port", port_arg.as_str(), "--supervised"])
.build()
.map_err(|e| anyhow::anyhow!("failed to configure autostart: {e}"))?;
if enable {
auto.enable()
.map_err(|e| anyhow::anyhow!("failed to enable autostart: {e}"))?;
} else {
auto.disable()
.map_err(|e| anyhow::anyhow!("failed to disable autostart: {e}"))?;
}
Ok(())
}
#[cfg(all(test, target_os = "linux"))]
mod tests {
use super::*;
use std::path::PathBuf;
struct TempDir(PathBuf);
impl TempDir {
fn new(suffix: &str) -> Self {
let dir = std::env::temp_dir().join(format!(
"llmtrim-autostart-test-{}-{}",
std::process::id(),
suffix
));
std::fs::create_dir_all(&dir).expect("create temp dir");
Self(dir)
}
fn path(&self) -> &std::path::Path {
&self.0
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[cfg(target_os = "linux")]
#[test]
fn configure_in_install_creates_desktop_file_with_expected_content() {
let dir = TempDir::new("install");
let base = dir.path();
configure_in(true, 8787, base).expect("configure_in enable");
let file = base
.join(".config")
.join("autostart")
.join("llmtrim.desktop");
assert!(file.exists(), ".desktop file not created");
let content = std::fs::read_to_string(&file).expect("read .desktop");
assert!(
content.contains("[Desktop Entry]"),
"missing [Desktop Entry]"
);
assert!(content.contains("Name=llmtrim"), "missing Name=");
assert!(
content.contains("serve --port 8787 --supervised"),
"missing serve invocation with port"
);
assert!(content.contains("Terminal=false"), "missing Terminal=false");
}
#[cfg(target_os = "linux")]
#[test]
fn configure_in_install_twice_is_idempotent() {
let dir = TempDir::new("idempotent");
let base = dir.path();
configure_in(true, 8787, base).expect("first enable");
configure_in(true, 8787, base).expect("second enable");
let autostart_dir = base.join(".config").join("autostart");
let count = std::fs::read_dir(&autostart_dir)
.expect("read autostart dir")
.filter_map(|e| e.ok())
.count();
assert_eq!(count, 1, "expected exactly 1 .desktop file, found {count}");
}
#[cfg(target_os = "linux")]
#[test]
fn configure_in_remove_deletes_desktop_file() {
let dir = TempDir::new("remove");
let base = dir.path();
configure_in(true, 8787, base).expect("enable");
let file = base
.join(".config")
.join("autostart")
.join("llmtrim.desktop");
assert!(file.exists(), "file should exist before disable");
configure_in(false, 8787, base).expect("disable");
assert!(!file.exists(), ".desktop file still present after disable");
}
#[cfg(target_os = "linux")]
#[test]
fn is_enabled_in_tracks_configure_in() {
let dir = TempDir::new("probe");
let base = dir.path();
assert!(!is_enabled_in(base), "fresh home → not enabled");
configure_in(true, 8787, base).expect("enable");
assert!(is_enabled_in(base), "enabled after configure");
configure_in(false, 8787, base).expect("disable");
assert!(!is_enabled_in(base), "disabled after remove");
}
#[cfg(target_os = "linux")]
#[test]
fn configure_in_remove_when_absent_is_ok_noop() {
let dir = TempDir::new("remove-absent");
let base = dir.path();
configure_in(false, 8787, base).expect("disable when absent must be Ok");
let file = base
.join(".config")
.join("autostart")
.join("llmtrim.desktop");
assert!(!file.exists(), ".desktop file should not exist");
}
}