use xcb::{ x::{ ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, ConfigureWindow, Cw, EventMask, MapWindow, SendEvent, SendEventDest, Window, }, VoidCookie, VoidCookieChecked, Xid, }; use crate::{config::BORDER_WIDTH, conn_info::Connection}; use super::hints; /// Information about a single client / window #[derive(Debug)] pub struct Client { /// The corresponding X11 window window: Window, x: i16, y: i16, width: u16, height: u16, border_width: u16, mapped: bool, urgent: bool, never_focus: bool, } impl Client { pub const fn new(window: Window) -> Self { Self { window, x: 0, y: 0, width: 0, height: 0, border_width: BORDER_WIDTH, mapped: false, urgent: false, never_focus: false, } } /// Send a configure configure notify event with the current geometry. /// This function does not check for success, so `conn.flush()` should be called after. pub fn configure_notify(&self, conn: &Connection<'_>) { conn.send_request(&SendEvent { destination: SendEventDest::Window(self.window), event_mask: EventMask::STRUCTURE_NOTIFY, event: &ConfigureNotifyEvent::new( self.window, self.window, Window::none(), self.x, self.y, self.width, self.height, self.border_width, false, ), propagate: false, }); } /// Set this client's geometry, also updating the X11 window if needed. /// This function does not check for success, so `conn.flush()` should be called after. pub fn set_geom( &mut self, conn: &Connection<'_>, x: i16, y: i16, width: u16, height: u16, border_width: u16, ) { if (x, y, width, height, border_width) == (self.x, self.y, self.width, self.height, self.border_width) { return; } self.x = x; self.y = y; self.width = width; self.height = height; self.border_width = border_width; conn.send_request(&ConfigureWindow { window: self.window, value_list: &[ ConfigWindow::X(self.x.into()), ConfigWindow::Y(self.y.into()), ConfigWindow::Width(self.width.into()), ConfigWindow::Height(self.height.into()), ConfigWindow::BorderWidth(self.border_width.into()), ], }); } /// Set the border colour of the X11 window to the given value (see `crate::colours::Colours`) /// This function does not check for success, so `conn.flush()` should be called after. pub fn set_border(&self, conn: &Connection<'_>, colour: u32) { conn.send_request(&ChangeWindowAttributes { window: self.window(), value_list: &[Cw::BorderPixel(colour)], }); } /// Ensure this client is currently mapped / visible /// This function does not check for success, so `conn.flush()` should be called after. pub fn ensure_mapped(&mut self, conn: &Connection<'_>) { if !self.mapped { conn.send_request(&MapWindow { window: self.window, }); self.mapped = true; } } /// Get the associated window pub const fn window(&self) -> Window { self.window } /// Set the event mask for this window /// This function does not check for success, so `conn.flush()` should be called after. pub fn set_event_mask( &self, conn: &Connection<'_>, event_mask: EventMask, ) -> VoidCookieChecked { conn.send_request_checked(&ChangeWindowAttributes { window: self.window(), value_list: &[Cw::EventMask(event_mask)], }) } /// Sync the non-geometry related properties with EWMH hints /// This function does not check for success, so `conn.flush()` should be called after. pub fn sync_properties(&mut self, conn: &Connection<'_>, focused: bool) { let Some(mut hints) = hints::Xwm::get(conn, self.window) else { return; }; if focused && hints.is_urgent() { hints.set_urgent(false); hints.apply(conn, self.window); } else { self.urgent = hints.is_urgent(); } self.never_focus = hints.input().is_some_and(|i| !i); } /// Set the given window as withdrawn / not withdrawn. pub fn set_withdrawn(&self, conn: &Connection<'_>, withdrawn: bool) -> VoidCookieChecked { conn.send_request_checked(&ChangeProperty { mode: xcb::x::PropMode::Replace, window: self.window, property: conn.atoms.wm_state, r#type: conn.atoms.wm_state, data: &[u32::from(!withdrawn), 0_u32], }) } pub fn set_urgent(&mut self, urgent: bool) { self.urgent = urgent; } pub const fn border_width(&self) -> u16 { self.border_width } pub const fn never_focus(&self) -> bool { self.never_focus } }