leenfetch_core/modules/linux/desktop/
wm.rs1use std::env;
2use std::fs;
3
4pub fn get_wm() -> Option<String> {
5 if let Ok(swaysock) = env::var("SWAYSOCK") {
7 if !swaysock.is_empty() {
8 return Some("sway".to_string());
9 }
10 }
11
12 if let Ok(hyprland) = env::var("HYPRLAND_INSTANCE_SIGNATURE") {
13 if !hyprland.is_empty() {
14 return Some("Hyprland".to_string());
15 }
16 }
17
18 if let Ok(runtime) = env::var("XDG_RUNTIME_DIR") {
20 let socket = env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
21 let path = format!("{}/{}", runtime, socket);
22 if fs::metadata(&path).is_ok() {
23 if let Some(wm) = scan_proc(WAYLAND_WMS) {
24 return Some(wm);
25 }
26 }
27 }
28
29 if env::var("DISPLAY").is_ok() {
31 if let Some(wm) = scan_proc(X11_WMS) {
33 return Some(wm);
34 }
35 }
36
37 scan_proc(ALL_WMS)
39}
40
41fn scan_proc(wm_names: &[&str]) -> Option<String> {
43 let proc_path = match fs::read_dir("/proc") {
44 Ok(p) => p,
45 Err(_) => return None,
46 };
47
48 for entry in proc_path.flatten() {
49 let name = entry.file_name();
50 let name_str = name.to_string_lossy();
51
52 if !name_str.chars().all(|c| c.is_ascii_digit()) {
54 continue;
55 }
56
57 let comm_path = entry.path().join("comm");
59 if let Ok(comm) = fs::read_to_string(&comm_path) {
60 let process_name = comm.trim();
61 for &wm in wm_names {
62 if process_name.eq_ignore_ascii_case(wm) || process_name.contains(wm) {
63 return Some(normalize_wm(wm));
64 }
65 }
66 }
67 }
68
69 None
70}
71
72fn normalize_wm(wm: &str) -> String {
73 match wm {
74 "gnome-shell" | "GNOME Shell" => "Mutter",
75 "kwin_x11" | "kwin_wayland" | "kwin" => "KWin",
76 "WINDOWMAKER" => "wmaker",
77 other => other,
78 }
79 .to_string()
80}
81
82const WAYLAND_WMS: &[&str] = &[
84 "sway",
85 "wayfire",
86 "weston",
87 "gnome-shell",
88 "kwin_wayland",
89 "hikari",
90 "river",
91 "wlr-randr",
92];
93
94const X11_WMS: &[&str] = &[
96 "dwm",
97 "xmonad",
98 "openbox",
99 "fluxbox",
100 "i3",
101 "herbstluftwm",
102 "awesome",
103 "bspwm",
104 "kwin_x11",
105];
106
107const ALL_WMS: &[&str] = &[
109 "sway",
110 "wayfire",
111 "weston",
112 "gnome-shell",
113 "kwin_wayland",
114 "kwin_x11",
115 "dwm",
116 "xmonad",
117 "openbox",
118 "fluxbox",
119 "i3",
120 "herbstluftwm",
121 "awesome",
122 "bspwm",
123 "hikari",
124 "river",
125 "WINDOWMAKER",
126];
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::test_utils::EnvLock;
132
133 fn clear_env() -> EnvLock {
134 let vars = ["XDG_RUNTIME_DIR", "WAYLAND_DISPLAY", "DISPLAY"];
135 let env_lock = EnvLock::acquire(&vars);
136 for var in vars {
137 env_lock.remove_var(var);
138 }
139 env_lock
140 }
141
142 #[test]
143 fn test_normalize_wm_variants() {
144 assert_eq!(normalize_wm("gnome-shell"), "Mutter");
145 assert_eq!(normalize_wm("GNOME Shell"), "Mutter");
146 assert_eq!(normalize_wm("kwin_x11"), "KWin");
147 assert_eq!(normalize_wm("kwin_wayland"), "KWin");
148 assert_eq!(normalize_wm("kwin"), "KWin");
149 assert_eq!(normalize_wm("WINDOWMAKER"), "wmaker");
150 assert_eq!(normalize_wm("i3"), "i3"); }
152
153 #[test]
154 fn test_get_wm_fallback_to_none_without_env() {
155 let env_lock = clear_env();
156
157 let _ = get_wm(); drop(env_lock);
161 }
162}