winit/platform_impl/linux/x11/util/
wm.rs

1use std::sync::Mutex;
2
3use super::*;
4
5// https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248
6pub const MOVERESIZE_TOPLEFT: isize = 0;
7pub const MOVERESIZE_TOP: isize = 1;
8pub const MOVERESIZE_TOPRIGHT: isize = 2;
9pub const MOVERESIZE_RIGHT: isize = 3;
10pub const MOVERESIZE_BOTTOMRIGHT: isize = 4;
11pub const MOVERESIZE_BOTTOM: isize = 5;
12pub const MOVERESIZE_BOTTOMLEFT: isize = 6;
13pub const MOVERESIZE_LEFT: isize = 7;
14pub const MOVERESIZE_MOVE: isize = 8;
15
16// This info is global to the window manager.
17static SUPPORTED_HINTS: Mutex<Vec<xproto::Atom>> = Mutex::new(Vec::new());
18static WM_NAME: Mutex<Option<String>> = Mutex::new(None);
19
20pub fn hint_is_supported(hint: xproto::Atom) -> bool {
21    (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint)
22}
23
24pub fn wm_name_is_one_of(names: &[&str]) -> bool {
25    if let Some(ref name) = *WM_NAME.lock().unwrap() {
26        names.contains(&name.as_str())
27    } else {
28        false
29    }
30}
31
32impl XConnection {
33    pub fn update_cached_wm_info(&self, root: xproto::Window) {
34        *SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root);
35        *WM_NAME.lock().unwrap() = self.get_wm_name(root);
36    }
37
38    fn get_supported_hints(&self, root: xproto::Window) -> Vec<xproto::Atom> {
39        let atoms = self.atoms();
40        let supported_atom = atoms[_NET_SUPPORTED];
41        self.get_property(root, supported_atom, xproto::Atom::from(xproto::AtomEnum::ATOM))
42            .unwrap_or_else(|_| Vec::with_capacity(0))
43    }
44
45    #[allow(clippy::useless_conversion)]
46    fn get_wm_name(&self, root: xproto::Window) -> Option<String> {
47        let atoms = self.atoms();
48        let check_atom = atoms[_NET_SUPPORTING_WM_CHECK];
49        let wm_name_atom = atoms[_NET_WM_NAME];
50
51        // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
52        // it working and being supported. This has been reported upstream, but due to the
53        // inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
54        // regardless of whether or not the WM claims to support it.
55        //
56        // Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be
57        // fixed in 0.72.
58        // if !supported_hints.contains(&check_atom) {
59        // return None;
60        // }
61
62        // IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
63        // provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
64        // if !supported_hints.contains(&wm_name_atom) {
65        // return None;
66        // }
67
68        // Of the WMs tested, only xmonad and dwm fail to provide a WM name.
69
70        // Querying this property on the root window will give us the ID of a child window created
71        // by the WM.
72        let root_window_wm_check = {
73            let result = self.get_property::<xproto::Window>(
74                root,
75                check_atom,
76                xproto::Atom::from(xproto::AtomEnum::WINDOW),
77            );
78
79            let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
80
81            wm_check?
82        };
83
84        // Querying the same property on the child window we were given, we should get this child
85        // window's ID again.
86        let child_window_wm_check = {
87            let result = self.get_property::<xproto::Window>(
88                root_window_wm_check.into(),
89                check_atom,
90                xproto::Atom::from(xproto::AtomEnum::WINDOW),
91            );
92
93            let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
94
95            wm_check?
96        };
97
98        // These values should be the same.
99        if root_window_wm_check != child_window_wm_check {
100            return None;
101        }
102
103        // All of that work gives us a window ID that we can get the WM name from.
104        let wm_name = {
105            let atoms = self.atoms();
106            let utf8_string_atom = atoms[UTF8_STRING];
107
108            let result =
109                self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom);
110
111            // IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
112            // string. For more fun trivia, IceWM is also unique in including version and uname
113            // information in this string (this means you'll have to be careful if you want to match
114            // against it, though).
115            // The unofficial 1.4 fork of IceWM still includes the extra details, but properly
116            // returns a UTF8 string that isn't null-terminated.
117            let no_utf8 = if let Err(ref err) = result {
118                err.is_actual_property_type(xproto::Atom::from(xproto::AtomEnum::STRING))
119            } else {
120                false
121            };
122
123            if no_utf8 {
124                self.get_property(
125                    root_window_wm_check.into(),
126                    wm_name_atom,
127                    xproto::Atom::from(xproto::AtomEnum::STRING),
128                )
129            } else {
130                result
131            }
132        }
133        .ok();
134
135        wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
136    }
137}