use parking_lot::Mutex; use super::*; // This info is global to the window manager. lazy_static! { static ref SUPPORTED_HINTS: Mutex> = Mutex::new(Vec::with_capacity(0)); static ref WM_NAME: Mutex> = 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 } } pub fn update_cached_wm_info(xconn: &Arc, root: ffi::Window) { *SUPPORTED_HINTS.lock() = self::get_supported_hints(xconn, root); *WM_NAME.lock() = self::get_wm_name(xconn, root); } fn get_supported_hints(xconn: &Arc, root: ffi::Window) -> Vec { let supported_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTED\0") } .expect("Failed to call XInternAtom (_NET_SUPPORTED)"); unsafe { self::get_property( xconn, root, supported_atom, ffi::XA_ATOM, ) }.unwrap_or_else(|_| Vec::with_capacity(0)) } fn get_wm_name(xconn: &Arc, root: ffi::Window) -> Option { let check_atom = unsafe { self::get_atom(xconn, b"_NET_SUPPORTING_WM_CHECK\0") } .expect("Failed to call XInternAtom (_NET_SUPPORTING_WM_CHECK)"); let wm_name_atom = unsafe { self::get_atom(xconn, b"_NET_WM_NAME\0") } .expect("Failed to call XInternAtom (_NET_WM_NAME)"); // 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 = unsafe { self::get_property( xconn, 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 = unsafe { self::get_property( xconn, 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(xconn, b"UTF8_STRING\0") } .expect("Failed to call XInternAtom (UTF8_STRING)"); let result = unsafe { self::get_property( xconn, 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 { unsafe { self::get_property( xconn, 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()) }