use std::env;
use std::fs;
pub fn get_wm() -> Option<String> {
if let Ok(swaysock) = env::var("SWAYSOCK") {
if !swaysock.is_empty() {
return Some("sway".to_string());
}
}
if let Ok(hyprland) = env::var("HYPRLAND_INSTANCE_SIGNATURE") {
if !hyprland.is_empty() {
return Some("Hyprland".to_string());
}
}
if let Ok(runtime) = env::var("XDG_RUNTIME_DIR") {
let socket = env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
let path = format!("{}/{}", runtime, socket);
if fs::metadata(&path).is_ok() {
if let Some(wm) = scan_proc(WAYLAND_WMS) {
return Some(wm);
}
}
}
if env::var("DISPLAY").is_ok() {
if let Some(wm) = scan_proc(X11_WMS) {
return Some(wm);
}
}
scan_proc(ALL_WMS)
}
fn scan_proc(wm_names: &[&str]) -> Option<String> {
let proc_path = match fs::read_dir("/proc") {
Ok(p) => p,
Err(_) => return None,
};
for entry in proc_path.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !name_str.chars().all(|c| c.is_ascii_digit()) {
continue;
}
let comm_path = entry.path().join("comm");
if let Ok(comm) = fs::read_to_string(&comm_path) {
let process_name = comm.trim();
for &wm in wm_names {
if process_name.eq_ignore_ascii_case(wm) || process_name.contains(wm) {
return Some(normalize_wm(wm));
}
}
}
}
None
}
fn normalize_wm(wm: &str) -> String {
match wm {
"gnome-shell" | "GNOME Shell" => "Mutter",
"kwin_x11" | "kwin_wayland" | "kwin" => "KWin",
"WINDOWMAKER" => "wmaker",
other => other,
}
.to_string()
}
const WAYLAND_WMS: &[&str] = &[
"sway",
"wayfire",
"weston",
"gnome-shell",
"kwin_wayland",
"hikari",
"river",
"wlr-randr",
];
const X11_WMS: &[&str] = &[
"dwm",
"xmonad",
"openbox",
"fluxbox",
"i3",
"herbstluftwm",
"awesome",
"bspwm",
"kwin_x11",
];
const ALL_WMS: &[&str] = &[
"sway",
"wayfire",
"weston",
"gnome-shell",
"kwin_wayland",
"kwin_x11",
"dwm",
"xmonad",
"openbox",
"fluxbox",
"i3",
"herbstluftwm",
"awesome",
"bspwm",
"hikari",
"river",
"WINDOWMAKER",
];
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::EnvLock;
fn clear_env() -> EnvLock {
let vars = ["XDG_RUNTIME_DIR", "WAYLAND_DISPLAY", "DISPLAY"];
let env_lock = EnvLock::acquire(&vars);
for var in vars {
env_lock.remove_var(var);
}
env_lock
}
#[test]
fn test_normalize_wm_variants() {
assert_eq!(normalize_wm("gnome-shell"), "Mutter");
assert_eq!(normalize_wm("GNOME Shell"), "Mutter");
assert_eq!(normalize_wm("kwin_x11"), "KWin");
assert_eq!(normalize_wm("kwin_wayland"), "KWin");
assert_eq!(normalize_wm("kwin"), "KWin");
assert_eq!(normalize_wm("WINDOWMAKER"), "wmaker");
assert_eq!(normalize_wm("i3"), "i3"); }
#[test]
fn test_get_wm_fallback_to_none_without_env() {
let env_lock = clear_env();
let _ = get_wm(); drop(env_lock);
}
}