use fslock::LockFile;
use std::{env, fs, path::PathBuf};
fn sanitize_component(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
let ok = ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '_');
out.push(if ok { ch } else { '_' });
}
out
}
fn truncate_ascii(mut s: String, max_len: usize) -> String {
if s.len() > max_len {
s.truncate(max_len);
}
s
}
pub fn session_key() -> String {
if let Ok(sig) = env::var("HYPRLAND_INSTANCE_SIGNATURE") {
return truncate_ascii(format!("hypr-{}", sanitize_component(&sig)), 80);
}
if env::var("SWAYSOCK").is_ok() {
return "sway".to_string();
}
if let Ok(disp) = env::var("WAYLAND_DISPLAY") {
return truncate_ascii(format!("wayland-{}", sanitize_component(&disp)), 80);
}
"unknown".to_string()
}
fn lock_path() -> anyhow::Result<PathBuf> {
let runtime =
env::var_os("XDG_RUNTIME_DIR").ok_or_else(|| anyhow::anyhow!("XDG_RUNTIME_DIR not set"))?;
let runtime = PathBuf::from(runtime);
Ok(runtime
.join("randpaper")
.join(format!("randpaper-{}.lock", session_key())))
}
pub fn single_instance_guard() -> anyhow::Result<Option<LockFile>> {
let path = lock_path()?;
if let Some(dir) = path.parent() {
fs::create_dir_all(dir)?;
}
let mut lock = LockFile::open(&path)?;
if lock.try_lock_with_pid()? {
Ok(Some(lock))
} else {
Ok(None)
}
}