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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use parking_lot::Mutex;

use super::*;

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

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

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

impl XConnection {
    pub fn update_cached_wm_info(&self, root: ffi::Window) {
        *SUPPORTED_HINTS.lock() = self.get_supported_hints(root);
        *WM_NAME.lock() = 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.get(0).cloned());

            if let Some(wm_check) = wm_check {
                wm_check
            } else {
                return None;
            }
        };

        // 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.get(0).cloned());

            if let Some(wm_check) = wm_check {
                wm_check
            } else {
                return None;
            }
        };

        // 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())
    }
}