use std::sync::OnceLock;
use x11rb::protocol::xproto::{Atom, AtomEnum, Window};
use super::connection::{X11Connection, X11Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WmCapabilities {
pub ewmh_wm_present: bool,
pub move_resize: bool,
}
impl WmCapabilities {
pub const NONE: Self = Self {
ewmh_wm_present: false,
move_resize: false,
};
pub fn supports_custom_chrome(self) -> bool {
self.ewmh_wm_present && self.move_resize
}
}
pub fn validate_wm_check(root_value: Option<u32>, child_value: Option<u32>) -> Option<Window> {
match root_value {
Some(win) if win != 0 && child_value == Some(win) => Some(win),
_ => None,
}
}
pub fn supports_atom(supported: &[Atom], atom: Atom) -> bool {
supported.contains(&atom)
}
fn probe_uncached() -> Result<WmCapabilities, X11Error> {
let conn = X11Connection::open()?;
let atoms = conn.atoms();
let root_check = conn
.get_property_full(
conn.root(),
atoms.net_supporting_wm_check,
AtomEnum::WINDOW.into(),
)?
.and_then(|value| value.as_u32());
let child_check = match root_check {
Some(win) => conn
.get_property_full(win, atoms.net_supporting_wm_check, AtomEnum::WINDOW.into())
.ok()
.flatten()
.and_then(|value| value.as_u32()),
None => None,
};
let ewmh_wm_present = validate_wm_check(root_check, child_check).is_some();
if !ewmh_wm_present {
return Ok(WmCapabilities::NONE);
}
let supported = conn
.get_property_full(conn.root(), atoms.net_supported, AtomEnum::ATOM.into())?
.map(|value| value.as_u32s())
.unwrap_or_default();
Ok(WmCapabilities {
ewmh_wm_present,
move_resize: supports_atom(&supported, atoms.net_wm_moveresize),
})
}
pub fn capabilities() -> WmCapabilities {
static CACHE: OnceLock<WmCapabilities> = OnceLock::new();
*CACHE.get_or_init(|| {
use crate::window_system::{WindowSystem, active_window_system};
if active_window_system() != WindowSystem::X11 {
return WmCapabilities::NONE;
}
match probe_uncached() {
Ok(caps) => caps,
Err(err) => {
eprintln!(
"teksilo-platform: X11 window-manager probe failed ({err}); \
keeping native window decorations"
);
WmCapabilities::NONE
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wm_check_needs_the_child_to_point_at_itself() {
assert_eq!(validate_wm_check(Some(0x200), Some(0x200)), Some(0x200));
}
#[test]
fn stale_wm_check_is_rejected() {
assert_eq!(validate_wm_check(Some(0x200), None), None);
assert_eq!(validate_wm_check(Some(0x200), Some(0x999)), None);
}
#[test]
fn absent_wm_check_means_no_window_manager() {
assert_eq!(validate_wm_check(None, None), None);
assert_eq!(validate_wm_check(Some(0), Some(0)), None);
}
#[test]
fn custom_chrome_needs_both_a_wm_and_move_resize() {
assert!(
WmCapabilities {
ewmh_wm_present: true,
move_resize: true
}
.supports_custom_chrome()
);
assert!(
!WmCapabilities {
ewmh_wm_present: true,
move_resize: false
}
.supports_custom_chrome()
);
assert!(
!WmCapabilities {
ewmh_wm_present: false,
move_resize: true
}
.supports_custom_chrome()
);
assert!(!WmCapabilities::NONE.supports_custom_chrome());
}
#[test]
fn supported_atom_lookup() {
assert!(supports_atom(&[10, 20, 30], 20));
assert!(!supports_atom(&[10, 30], 20));
assert!(!supports_atom(&[], 20));
}
#[test]
#[ignore = "requires a live X server; run with --ignored"]
fn x11_probe_against_a_live_server() {
let caps = probe_uncached().expect("connect to $DISPLAY");
eprintln!("probed window manager: {caps:?}");
assert!(
caps.ewmh_wm_present,
"no EWMH window manager found on $DISPLAY — custom chrome would be refused"
);
assert!(
caps.move_resize,
"_NET_WM_MOVERESIZE missing from _NET_SUPPORTED — custom chrome would be refused"
);
}
}