| 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 | |