From 640515ed4dd83281f28206beb557b1f497b1353b Mon Sep 17 00:00:00 2001 From: tcmal Date: Wed, 5 Jun 2024 22:36:08 +0100 Subject: wip: start managing clients --- src/clients.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 181 insertions(+), 25 deletions(-) (limited to 'src/clients.rs') diff --git a/src/clients.rs b/src/clients.rs index 341f73c..23780b5 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -1,17 +1,20 @@ use xcb::{ x::{ - ConfigureNotifyEvent, ConfigureRequestEvent, DestroyNotifyEvent, MapRequestEvent, - UnmapNotifyEvent, + ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, ConfigureWindow, + DestroyNotifyEvent, EventMask, GetWindowAttributes, GetWindowAttributesReply, + MapRequestEvent, MapWindow, SendEvent, SendEventDest, UnmapNotifyEvent, Window, }, xinerama::{self, ScreenInfo}, - Extension, + BaseEvent, Connection, Extension, Xid, }; use crate::{error::*, WM}; impl WM<'_> { /// Update the client state's recorded monitors and monitor sizes - pub(crate) fn update_geometry(&mut self) -> Result<()> { + /// Returns true if any values changed, meaning windows should be re-tiled. + pub(crate) fn update_geometry(&mut self) -> Result { + let mut dirty = false; if self .conn .active_extensions() @@ -23,16 +26,18 @@ impl WM<'_> { // Monitor removed, move its clients away if reply.screen_info().len() > self.clients.monitor_count() { + dirty = true; self.clients.truncate_screens(reply.screen_info().len()); } // Update screen info & add new client lists if needed for (i, monitor) in reply.screen_info().iter().enumerate() { - self.clients.set_screen_info(i, *monitor); + dirty |= self.clients.set_monitor_geometry(i, (*monitor).into()); } } else { // Only one screen if self.clients.monitor_count() > 1 { + dirty = true; self.clients.truncate_screens(1); } @@ -43,9 +48,9 @@ impl WM<'_> { .nth(self.screen_num as usize) .ok_or(Error::NoSuchScreen)?; - self.clients.set_screen_info( + dirty |= self.clients.set_monitor_geometry( 0, - ScreenInfo { + MonitorGeometry { x_org: 0, y_org: 0, width: screen.width_in_pixels(), @@ -54,23 +59,92 @@ impl WM<'_> { ); } - Ok(()) + Ok(dirty) } - pub(crate) fn handle_configure_request(&mut self, _e: ConfigureRequestEvent) -> Result<()> { - todo!() + /// Update our monitor geometry if the root window is reconfigured + pub(crate) fn handle_configure_notify(&mut self, e: ConfigureNotifyEvent) -> Result<()> { + if e.window() == self.root && self.update_geometry()? { + self.clients.rearrange(self.conn)?; + } + + Ok(()) } - pub(crate) fn handle_configure_notify(&mut self, _e: ConfigureNotifyEvent) -> Result<()> { - todo!() + + /// Handle a configure request, by checking it's valid and performing it if so + pub(crate) fn handle_configure_request(&mut self, e: ConfigureRequestEvent) -> Result<()> { + if let Some(c) = self.clients.find_client_mut(e.window()) { + // TODO: Allow changing some properties: + // - Border width + // - Size and position if floating + c.configure_notify(self.conn)?; + } else { + // Configure it as requested, and sort the rest when we actually map the window + self.conn + .check_request(self.conn.send_request_checked(&ConfigureWindow { + window: e.window(), + value_list: &[ + ConfigWindow::X(e.x().into()), + ConfigWindow::Y(e.y().into()), + ConfigWindow::Height(e.height().into()), + ConfigWindow::Width(e.height().into()), + ConfigWindow::BorderWidth(e.border_width().into()), + ConfigWindow::Sibling(e.parent()), + ConfigWindow::StackMode(e.stack_mode()), + ], + }))? + } + + Ok(()) } - pub(crate) fn handle_destroy_notify(&mut self, _e: DestroyNotifyEvent) -> Result<()> { - todo!() + + /// Handle a destroyed window, removing it from the client list and rearranging. + pub(crate) fn handle_destroy_notify(&mut self, e: DestroyNotifyEvent) -> Result<()> { + if self.clients.remove_client(e.window()).is_some() { + self.clients.rearrange(self.conn)?; + } + + Ok(()) } - pub(crate) fn handle_map_request(&mut self, _e: MapRequestEvent) -> Result<()> { - todo!() + + /// Map a window on request, starting to manage it if needed. + pub(crate) fn handle_map_request(&mut self, e: MapRequestEvent) -> Result<()> { + // Ignore already managed windows + if self.clients.find_client_mut(e.window()).is_some() { + return Ok(()); + } + + let attrs = self.conn.wait_for_reply( + self.conn + .send_request(&GetWindowAttributes { window: e.window() }), + )?; + if attrs.override_redirect() { + return Ok(()); + } + + // Start managing, and map window + self.clients.manage(e.window(), &attrs)?; + self.conn.send_request(&MapWindow { window: e.window() }); + + // TODO: clear focus + + self.conn.flush()?; + + Ok(()) } - pub(crate) fn handle_unmap_notify(&mut self, _e: UnmapNotifyEvent) -> Result<()> { - todo!() + + /// Handle a window being unmapped by updating its client state, or stop managing it. + pub(crate) fn handle_unmap_notify(&mut self, e: UnmapNotifyEvent) -> Result<()> { + if self.clients.find_client_mut(e.window()).is_some() { + if e.is_from_send_event() { + // TODO: set client state to withdrawn + } else { + self.clients.remove_client(e.window()); + // TODO: 'disown' the window - unmange(c, 0) + } + } + + Ok(()) } } @@ -86,13 +160,26 @@ pub struct MonitorInfo { clients: Vec, /// The monitor's geometry - screen_info: ScreenInfo, + screen_info: MonitorGeometry, } -/// Information about a single client / window -#[derive(Debug)] -pub struct Client { - // TODO +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MonitorGeometry { + pub x_org: i16, + pub y_org: i16, + pub width: u16, + pub height: u16, +} + +impl From for MonitorGeometry { + fn from(value: ScreenInfo) -> Self { + Self { + x_org: value.x_org, + y_org: value.y_org, + width: value.width, + height: value.height, + } + } } impl ClientState { @@ -107,24 +194,93 @@ impl ClientState { } /// Set the info for the given screen, resizing the monitor list if necessary. - pub fn set_screen_info(&mut self, i: usize, info: ScreenInfo) { + /// Returns true if the new info is different from the old one. + pub fn set_monitor_geometry(&mut self, i: usize, info: MonitorGeometry) -> bool { while i >= self.0.len() { self.0.push(MonitorInfo::default()) } + let dirty = self.0[i].screen_info != info; self.0[i].screen_info = info; + + dirty } /// Get the amount of monitors this state is currently aware of pub fn monitor_count(&self) -> usize { self.0.len() } + + /// Find the [`Client`] corresponding to the given window + pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> { + self.0 + .iter_mut() + .flat_map(|mi| mi.clients.iter_mut()) + .find(|c| c.window == window) + } + + /// Remove the client associated with the given window. + /// This doesn't perform any of the associated X11 stuff + pub fn remove_client(&mut self, window: Window) -> Option { + for mon in self.0.iter_mut() { + if let Some(pos) = mon.clients.iter().position(|c| c.window == window) { + return Some(mon.clients.remove(pos)); + } + } + None + } + + /// Rearrange all clients, reconfiguring them as needed. + pub fn rearrange(&mut self, _conn: &Connection) -> Result<()> { + todo!() + } + + /// Start managing the given window, adding it to the client list and ensuring its configuration is valid. + fn manage(&self, _window: Window, _attrs: &GetWindowAttributesReply) -> Result<()> { + todo!() + } +} + +/// 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, +} + +impl Client { + fn configure_notify(&self, conn: &Connection) -> Result<()> { + conn.check_request(conn.send_request_checked(&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, + }))?; + + Ok(()) + } } impl Default for MonitorInfo { fn default() -> Self { Self { clients: vec![], - screen_info: ScreenInfo { + screen_info: MonitorGeometry { x_org: 0, y_org: 0, width: 0, -- cgit v1.2.3