1pub fn detect_wm() -> Option<String> {
15 if let Ok(wm) = std::env::var("WINDOW_MANAGER") {
17 let wm = wm.trim().to_string();
18 if !wm.is_empty() {
19 return Some(wm);
20 }
21 }
22
23 #[cfg(target_os = "linux")]
24 {
25 let is_wayland = std::env::var("WAYLAND_DISPLAY").is_ok()
26 || std::env::var("XDG_SESSION_TYPE")
27 .map(|v| v.to_lowercase() == "wayland")
28 .unwrap_or(false);
29
30 if is_wayland {
31 if let Some(wm) = probe_wayland_compositor() {
32 return Some(wm);
33 }
34 }
35
36 if let Some(wm) = probe_wm_process() {
38 return Some(wm);
39 }
40
41 if let Ok(output) = std::process::Command::new("wmctrl").arg("-m").output() {
43 if let Ok(stdout) = String::from_utf8(output.stdout) {
44 for line in stdout.lines() {
45 if let Some(rest) = line.trim().strip_prefix("Name:") {
46 let name = rest.trim();
47 if !name.is_empty() && name != "N/A" {
48 return Some(name.to_string());
49 }
50 }
51 }
52 }
53 }
54 }
55
56 #[cfg(target_os = "macos")]
57 {
58 return Some("Quartz Compositor".to_string());
60 }
61
62 #[cfg(target_os = "windows")]
63 {
64 return Some("Desktop Window Manager".to_string());
65 }
66
67 #[allow(unreachable_code)]
68 None
69}
70
71#[cfg(target_os = "linux")]
72fn probe_wayland_compositor() -> Option<String> {
73 const COMPOSITORS: &[(&str, &str)] = &[
75 ("sway", "Sway"),
76 ("hyprland", "Hyprland"),
77 ("kwin_wayland", "KWin"),
78 ("mutter", "Mutter"),
79 ("wayfire", "Wayfire"),
80 ("river", "River"),
81 ("niri", "Niri"),
82 ("labwc", "labwc"),
83 ("weston", "Weston"),
84 ("cage", "Cage"),
85 ("gamescope", "gamescope"),
86 ("mir", "Mir"),
87 ];
88 probe_from_process_list(COMPOSITORS)
89}
90
91#[cfg(target_os = "linux")]
92fn probe_wm_process() -> Option<String> {
93 const WMS: &[(&str, &str)] = &[
94 ("openbox", "Openbox"),
95 ("i3", "i3"),
96 ("bspwm", "bspwm"),
97 ("xmonad", "XMonad"),
98 ("awesome", "Awesome"),
99 ("dwm", "dwm"),
100 ("fluxbox", "Fluxbox"),
101 ("icewm", "IceWM"),
102 ("jwm", "JWM"),
103 ("kwin_x11", "KWin"),
104 ("mutter", "Mutter"),
105 ("marco", "Marco"),
106 ("xfwm4", "Xfwm4"),
107 ("compiz", "Compiz"),
108 ("metacity", "Metacity"),
109 ("enlightenment", "Enlightenment"),
110 ("herbstluftwm", "herbstluftwm"),
111 ("qtile", "Qtile"),
112 ("spectrwm", "spectrwm"),
113 ("cwm", "cwm"),
114 ("2bwm", "2bwm"),
115 ("leftwm", "LeftWM"),
116 ("wmderland", "wmderland"),
117 ("penrose", "Penrose"),
118 ];
119 probe_from_process_list(WMS)
120}
121
122#[cfg(target_os = "linux")]
123fn probe_from_process_list(candidates: &[(&str, &str)]) -> Option<String> {
124 let Ok(entries) = std::fs::read_dir("/proc") else {
125 return None;
126 };
127 for entry in entries.filter_map(|e| e.ok()) {
128 let path = entry.path();
129 if !path.is_dir() {
130 continue;
131 }
132 let comm_path = path.join("comm");
133 let Ok(comm) = std::fs::read_to_string(&comm_path) else {
134 continue;
135 };
136 let comm = comm.trim().to_lowercase();
137 for (proc_name, display_name) in candidates {
138 if comm == *proc_name || comm.starts_with(proc_name) {
139 return Some(display_name.to_string());
140 }
141 }
142 }
143 None
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_detect_wm_does_not_panic() {
152 let _ = detect_wm();
154 }
155}