1use crate::extra;
5use crate::traits::ReadoutError;
6
7use std::process::{Command, Stdio};
8
9#[cfg(target_os = "linux")]
10use wayland_sys::{client::*, ffi_dispatch};
11
12#[cfg(target_os = "linux")]
13use nix::sys::socket::{sockopt, GetSockOpt};
14
15#[cfg(target_os = "linux")]
16use std::os::fd::AsRawFd;
17
18#[cfg(target_os = "linux")]
19pub fn detect_wayland_window_manager() -> Result<String, ReadoutError> {
20 if !is_lib_available() {
21 return Err(ReadoutError::MetricNotAvailable);
22 }
23
24 let display_ptr = unsafe {
25 ffi_dispatch!(
26 wayland_client_handle(),
27 wl_display_connect,
28 ::std::ptr::null()
29 )
30 };
31
32 if display_ptr.is_null() {
33 return Err(ReadoutError::MetricNotAvailable);
34 }
35
36 let display_fd =
37 unsafe { ffi_dispatch!(wayland_client_handle(), wl_display_get_fd, display_ptr) }
38 .as_raw_fd();
39
40 let pid = sockopt::PeerCredentials
41 .get(display_fd)
42 .map_err(|_| ReadoutError::MetricNotAvailable)?
43 .pid();
44
45 Ok(extra::pop_newline(std::fs::read_to_string(format!(
46 "/proc/{}/comm",
47 pid
48 ))?))
49}
50
51pub fn detect_xorg_window_manager() -> Result<String, ReadoutError> {
52 if extra::which("xprop") {
53 let xprop_id = Command::new("xprop")
54 .args(["-root", "-notype", "_NET_SUPPORTING_WM_CHECK"])
55 .stdout(Stdio::piped())
56 .stderr(Stdio::piped())
57 .spawn()
58 .expect("ERROR: failed to spawn \"xprop\" process");
59
60 let xprop_id_output = xprop_id
61 .wait_with_output()
62 .expect("ERROR: failed to wait for \"xprop\" process to exit");
63
64 let window_manager_id_info = String::from_utf8(xprop_id_output.stdout)
65 .expect("ERROR: \"xprop -root -notype _NET_SUPPORTING_WM_CHECK\" process stdout was not valid UTF-8");
66
67 let window_manager_id = window_manager_id_info.split(' ').last().unwrap_or_default();
68
69 let xprop_property = Command::new("xprop")
70 .args([
71 "-id",
72 window_manager_id,
73 "-notype",
74 "-len",
75 "25",
76 "-f",
77 "_NET_WM_NAME",
78 "8t",
79 ])
80 .stdout(Stdio::piped())
81 .stderr(Stdio::piped())
82 .spawn()
83 .expect("ERROR: failed to spawn \"xprop\" process");
84
85 let xprop_property_output = xprop_property
86 .wait_with_output()
87 .expect("ERROR: failed to wait for \"xprop\" process to exit");
88
89 let window_manager_name_info = String::from_utf8(xprop_property_output.stdout)
90 .unwrap_or_else(|_| {
91 panic!(
92 "ERROR: \"xprop -id {window_manager_id} -notype -len 25
93 -f _NET_WM_NAME 8t\" process stdout was not valid UTF-8"
94 )
95 });
96
97 if let Some(line) = window_manager_name_info
98 .lines()
99 .find(|line| line.starts_with("_NET_WM_NAME"))
100 {
101 return Ok(line
102 .split_once('=')
103 .unwrap_or_default()
104 .1
105 .trim()
106 .replace(['\"', '\''], ""));
107 };
108 }
109
110 if extra::which("wmctrl") {
111 let wmctrl = Command::new("wmctrl")
112 .arg("-m")
113 .stdout(Stdio::piped())
114 .stderr(Stdio::piped())
115 .spawn()
116 .expect("ERROR: failed to spawn \"wmctrl\" process");
117
118 let wmctrl_out = wmctrl
119 .stdout
120 .expect("ERROR: failed to open \"wmctrl\" stdout");
121
122 let head = Command::new("head")
123 .args(["-n", "1"])
124 .stdin(Stdio::from(wmctrl_out))
125 .stdout(Stdio::piped())
126 .spawn()
127 .expect("ERROR: failed to spawn \"head\" process");
128
129 let output = head
130 .wait_with_output()
131 .expect("ERROR: failed to wait for \"head\" process to exit");
132
133 let window_manager = String::from_utf8(output.stdout)
134 .expect("ERROR: \"wmctrl -m | head -n1\" process stdout was not valid UTF-8");
135
136 let winman_name =
137 extra::pop_newline(String::from(window_manager.replace("Name:", "").trim()));
138
139 if winman_name == "N/A" || winman_name.is_empty() {
140 return Err(ReadoutError::Other(
141 "Window manager not available — perhaps it's not EWMH-compliant.".to_string(),
142 ));
143 }
144
145 return Ok(winman_name);
146 }
147
148 Err(ReadoutError::Other(
149 "\"wmctrl\" must be installed to display your window manager.".to_string(),
150 ))
151}