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