use std::cell::{Cell, RefCell};
use x11rb::errors::ReplyError;
use x11rb::protocol::xproto::{ConnectionExt, MapState, QueryTreeReply, Window};
use x11rb::protocol::ErrorKind;
use x11rb::x11_utils::X11Error;
use x11rb::xcb_ffi::XCBConnection;
#[cfg_attr(debug_assertions, derive(Debug))]
pub struct AncestorVisibilityState {
ancestry: AncestryList,
own_window_viewable: Cell<bool>,
}
#[cfg_attr(debug_assertions, derive(Debug))]
struct AncestryList {
inner: RefCell<Vec<Ancestor>>,
}
impl AncestryList {
pub fn new(own_window: Window) -> Self {
Self { inner: RefCell::new(vec![Ancestor { id: own_window, mapped: false.into() }]) }
}
pub fn pop_id(&self) -> Option<Window> {
self.inner.borrow_mut().pop().map(|a| a.id)
}
pub fn last_id(&self) -> Option<Window> {
self.inner.borrow().last().map(|a| a.id)
}
pub fn push(&self, ancestor: Ancestor) {
self.inner.borrow_mut().push(ancestor);
}
pub fn parent_id(&self) -> Option<Window> {
self.inner.borrow().get(1).map(|a| a.id)
}
pub fn remove_window(&self, id: Window) -> bool {
let mut inner = self.inner.borrow_mut();
let Some(index) = inner.iter().position(|a| a.id == id) else {
return false;
};
inner.truncate(index.saturating_add(1));
true
}
pub fn remove_after_window(&self, id: Window) -> bool {
let mut inner = self.inner.borrow_mut();
let Some(index) = inner.iter().position(|a| a.id == id) else {
return false;
};
inner.truncate(index.saturating_add(2));
true
}
pub fn check_all_mapped(&self) -> bool {
self.inner.borrow().iter().all(|a| a.mapped.get())
}
pub fn set_mapped(&self, window: Window, mapped: bool) -> bool {
let inner = self.inner.borrow();
let Some(ancestor) = inner.iter().find(|a| a.id == window) else {
return false;
};
ancestor.mapped.set(mapped);
true
}
}
#[cfg_attr(debug_assertions, derive(Debug))]
struct Ancestor {
id: Window,
mapped: Cell<bool>,
}
impl AncestorVisibilityState {
pub fn discover(connection: &XCBConnection, own_window_id: Window) -> Result<Self, ReplyError> {
let this = Self {
ancestry: AncestryList::new(own_window_id),
own_window_viewable: Cell::new(false),
};
this.try_regenerate_from_last_window(connection)?;
Ok(this)
}
pub fn own_window_is_viewable(&self) -> bool {
self.own_window_viewable.get()
}
pub fn parent_id(&self) -> Option<Window> {
self.ancestry.parent_id()
}
pub fn window_mapped(&self, window_id: Window) -> bool {
if !self.ancestry.set_mapped(window_id, true) {
return false;
}
if self.own_window_viewable.get() {
return false;
}
let all_mapped = self.ancestry.check_all_mapped();
if all_mapped {
self.own_window_viewable.set(true);
}
all_mapped
}
pub fn window_unmapped(&self, window_id: Window) {
if !self.ancestry.set_mapped(window_id, false) {
return;
}
self.own_window_viewable.set(false);
}
pub fn window_destroyed(&self, window_id: Window, connection: &XCBConnection) {
if !self.ancestry.remove_window(window_id) {
return;
}
self.regenerate_from_last_window(connection);
}
pub fn window_reparented(
&self, window_id: Window, new_parent: Window, connection: &XCBConnection,
) {
if !self.ancestry.remove_after_window(window_id) {
return;
}
self.ancestry.push(Ancestor { id: new_parent, mapped: Cell::new(false) });
self.regenerate_from_last_window(connection);
}
pub fn regenerate_from_last_window(&self, connection: &XCBConnection) {
if let Err(e) = self.try_regenerate_from_last_window(connection) {
crate::warn!("Failed to generate window ancestry list: {}", e)
}
}
fn try_regenerate_from_last_window(
&self, connection: &XCBConnection,
) -> Result<(), ReplyError> {
let Some(mut current_window) = self.ancestry.pop_id() else { return Ok(()) };
loop {
let Some((mapped, tree)) = fetch_window_info(connection, current_window)? else {
crate::warn!("Failed to get info for window {}: XBadWindow", current_window);
let Some(previous_parent) = self.ancestry.pop_id() else {
break;
};
current_window = previous_parent;
continue;
};
if tree.parent == current_window {
break;
}
if let Some(child_id) = self.ancestry.last_id() {
if !tree.children.contains(&child_id) {
crate::warn!(
"Children of parent {} does not contain {}: {:?}",
current_window,
child_id,
&tree.children
);
let Some(_) = self.ancestry.pop_id() else { unreachable!() };
current_window = child_id;
continue;
}
}
self.ancestry.push(Ancestor { id: current_window, mapped: mapped.into() });
if tree.parent == tree.root {
break;
}
current_window = tree.parent;
}
self.own_window_viewable.set(self.ancestry.check_all_mapped());
Ok(())
}
}
fn fetch_window_info(
connection: &XCBConnection, window: Window,
) -> Result<Option<(bool, QueryTreeReply)>, ReplyError> {
let attrs_cookie = connection.get_window_attributes(window)?;
let tree_cookie = connection.query_tree(window)?;
let mapped = match attrs_cookie.reply() {
Ok(attr) => attr.map_state != MapState::UNMAPPED,
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => {
tree_cookie.discard_reply_and_errors();
return Ok(None);
}
Err(e) => {
tree_cookie.discard_reply_and_errors();
return Err(e);
}
};
let tree = match tree_cookie.reply() {
Ok(tree) => tree,
Err(ReplyError::X11Error(X11Error { error_kind: ErrorKind::Window, .. })) => {
return Ok(None)
}
Err(e) => return Err(e),
};
Ok(Some((mapped, tree)))
}