1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use std::sync::Mutex;

use once_cell::sync::Lazy;

use super::*;

// https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248
pub const MOVERESIZE_TOPLEFT: isize = 0;
pub const MOVERESIZE_TOP: isize = 1;
pub const MOVERESIZE_TOPRIGHT: isize = 2;
pub const MOVERESIZE_RIGHT: isize = 3;
pub const MOVERESIZE_BOTTOMRIGHT: isize = 4;
pub const MOVERESIZE_BOTTOM: isize = 5;
pub const MOVERESIZE_BOTTOMLEFT: isize = 6;
pub const MOVERESIZE_LEFT: isize = 7;
pub const MOVERESIZE_MOVE: isize = 8;

// This info is global to the window manager.
static SUPPORTED_HINTS: Lazy<Mutex<Vec<ffi::Atom>>> =
    Lazy::new(|| Mutex::new(Vec::with_capacity(0)));
static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));

pub fn hint_is_supported(hint: ffi::Atom) -> bool {
    (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint)
}

pub fn wm_name_is_one_of(names: &[&str]) -> bool {
    if let Some(ref name) = *WM_NAME.lock().unwrap() {
        names.contains(&name.as_str())
    } else {
        false
    }
}

impl XConnection {
    pub fn update_cached_wm_info(&self, root: ffi::Window) {
        *SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root);
        *WM_NAME.lock().unwrap() = self.get_wm_name(root);
    }

    fn get_supported_hints(&self, root: ffi::Window) -> Vec<ffi::Atom> {
        let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") };
        self.get_property(root, supported_atom, ffi::XA_ATOM)
            .unwrap_or_else(|_| Vec::with_capacity(0))
    }

    fn get_wm_name(&self, root: ffi::Window) -> Option<String> {
        let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") };
        let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") };

        // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
        // it working and being supported. This has been reported upstream, but due to the
        // inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
        // regardless of whether or not the WM claims to support it.
        //
        // Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
        // in 0.72.
        /*if !supported_hints.contains(&check_atom) {
            return None;
        }*/

        // IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
        // provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
        /*if !supported_hints.contains(&wm_name_atom) {
            return None;
        }*/

        // Of the WMs tested, only xmonad and dwm fail to provide a WM name.

        // Querying this property on the root window will give us the ID of a child window created by
        // the WM.
        let root_window_wm_check = {
            let result = self.get_property(root, check_atom, ffi::XA_WINDOW);

            let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());

            wm_check?
        };

        // Querying the same property on the child window we were given, we should get this child
        // window's ID again.
        let child_window_wm_check = {
            let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW);

            let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());

            wm_check?
        };

        // These values should be the same.
        if root_window_wm_check != child_window_wm_check {
            return None;
        }

        // All of that work gives us a window ID that we can get the WM name from.
        let wm_name = {
            let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") };

            let result = self.get_property(root_window_wm_check, wm_name_atom, utf8_string_atom);

            // IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
            // string. For more fun trivia, IceWM is also unique in including version and uname
            // information in this string (this means you'll have to be careful if you want to match
            // against it, though).
            // The unofficial 1.4 fork of IceWM still includes the extra details, but properly
            // returns a UTF8 string that isn't null-terminated.
            let no_utf8 = if let Err(ref err) = result {
                err.is_actual_property_type(ffi::XA_STRING)
            } else {
                false
            };

            if no_utf8 {
                self.get_property(root_window_wm_check, wm_name_atom, ffi::XA_STRING)
            } else {
                result
            }
        }
        .ok();

        wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
    }
}