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