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