1 | use std::sync::Mutex; |
2 | |
3 | use once_cell::sync::Lazy; |
4 | |
5 | use super::*; |
6 | |
7 | // https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248 |
8 | pub const MOVERESIZE_TOPLEFT: isize = 0; |
9 | pub const MOVERESIZE_TOP: isize = 1; |
10 | pub const MOVERESIZE_TOPRIGHT: isize = 2; |
11 | pub const MOVERESIZE_RIGHT: isize = 3; |
12 | pub const MOVERESIZE_BOTTOMRIGHT: isize = 4; |
13 | pub const MOVERESIZE_BOTTOM: isize = 5; |
14 | pub const MOVERESIZE_BOTTOMLEFT: isize = 6; |
15 | pub const MOVERESIZE_LEFT: isize = 7; |
16 | pub const MOVERESIZE_MOVE: isize = 8; |
17 | |
18 | // This info is global to the window manager. |
19 | static SUPPORTED_HINTS: Lazy<Mutex<Vec<xproto::Atom>>> = |
20 | Lazy::new(|| Mutex::new(Vec::with_capacity(0))); |
21 | static WM_NAME: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None)); |
22 | |
23 | pub fn hint_is_supported(hint: xproto::Atom) -> bool { |
24 | (*SUPPORTED_HINTS.lock().unwrap()).contains(&hint) |
25 | } |
26 | |
27 | pub 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 | |
35 | impl 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 | |