diff options
Diffstat (limited to 'src/clients/client.rs')
-rw-r--r-- | src/clients/client.rs | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/clients/client.rs b/src/clients/client.rs new file mode 100644 index 0000000..a7b1a8d --- /dev/null +++ b/src/clients/client.rs @@ -0,0 +1,174 @@ +use xcb::{ + x::{ + ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, + ConfigureWindow, Cw, EventMask, MapWindow, SendEvent, SendEventDest, Window, + }, + 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) { + conn.send_request(&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) { + conn.send_request(&ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: conn.atoms.wm_state, + r#type: conn.atoms.wm_state, + data: &[u8::from(!withdrawn), 0_u8], + }); + } + + 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 + } +} |