libmacchina/
winman.rs

1//! This module provides a set of functions that detect the name of the window manager the host is
2//! running.
3
4use 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}