From 2b9d2da2d8424087eb5c08dfe0ea72938cfd1be9 Mon Sep 17 00:00:00 2001 From: tcmal Date: Thu, 6 Jun 2024 18:06:29 +0100 Subject: arrange and map clients --- src/clients.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 31 deletions(-) diff --git a/src/clients.rs b/src/clients.rs index 23780b5..940efee 100644 --- a/src/clients.rs +++ b/src/clients.rs @@ -1,8 +1,10 @@ +use std::cmp::min; + use xcb::{ x::{ ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, ConfigureWindow, - DestroyNotifyEvent, EventMask, GetWindowAttributes, GetWindowAttributesReply, - MapRequestEvent, MapWindow, SendEvent, SendEventDest, UnmapNotifyEvent, Window, + DestroyNotifyEvent, EventMask, GetWindowAttributes, MapRequestEvent, MapWindow, SendEvent, + SendEventDest, UnmapNotifyEvent, UnmapWindow, Window, }, xinerama::{self, ScreenInfo}, BaseEvent, Connection, Extension, Xid, @@ -65,9 +67,9 @@ impl WM<'_> { /// 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)?; + self.clients.rearrange(self.conn); + self.conn.flush()?; } - Ok(()) } @@ -77,7 +79,8 @@ impl WM<'_> { // TODO: Allow changing some properties: // - Border width // - Size and position if floating - c.configure_notify(self.conn)?; + c.configure_notify(self.conn); + self.conn.flush()?; } else { // Configure it as requested, and sort the rest when we actually map the window self.conn @@ -86,10 +89,9 @@ impl WM<'_> { value_list: &[ ConfigWindow::X(e.x().into()), ConfigWindow::Y(e.y().into()), + ConfigWindow::Width(e.width().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()), ], }))? @@ -101,7 +103,8 @@ impl WM<'_> { /// 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)?; + self.clients.rearrange(self.conn); + self.conn.flush()?; } Ok(()) @@ -123,9 +126,7 @@ impl WM<'_> { } // Start managing, and map window - self.clients.manage(e.window(), &attrs)?; - self.conn.send_request(&MapWindow { window: e.window() }); - + self.clients.manage(self.conn, e.window()); // TODO: clear focus self.conn.flush()?; @@ -150,8 +151,32 @@ impl WM<'_> { /// Holds state related to the window manager's clients /// This contains a list of clients per monitor, alongside info on that monitor's screen size. -#[derive(Debug, Default)] -pub struct ClientState(Vec); +pub struct ClientState { + /// The current arranging function. + /// This function is expected to ensure that all clients are the correct size, reconfigure them if needed, and map/unmap as needed. + arrange: &'static dyn Fn(&mut MonitorInfo, &Connection), + selected_monitor: usize, + mons: Vec, +} + +impl Default for ClientState { + fn default() -> Self { + Self { + arrange: &tile, + selected_monitor: 0, + mons: vec![], + } + } +} + +impl std::fmt::Debug for ClientState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClientState") + .field("selected_monitor", &self.selected_monitor) + .field("mons", &self.mons) + .finish() + } +} /// Info stored for a single monitor #[derive(Debug)] @@ -163,6 +188,13 @@ pub struct MonitorInfo { screen_info: MonitorGeometry, } +impl MonitorInfo { + fn clients_tiled_mut(&mut self) -> impl Iterator { + // TODO: tag filtering, floating + self.clients.iter_mut() + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MonitorGeometry { pub x_org: i16, @@ -187,32 +219,32 @@ impl ClientState { pub fn truncate_screens(&mut self, new_size: usize) { // hack: double borrow stuff let mut moved_clients = vec![]; - for old in self.0.drain(new_size - self.0.len()..self.0.len()) { + for old in self.mons.drain(new_size - self.mons.len()..self.mons.len()) { moved_clients.extend(old.clients.into_iter()); } - self.0[0].clients.extend(moved_clients); + self.mons[0].clients.extend(moved_clients); } /// Set the info for the given screen, resizing the monitor list if necessary. /// 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()) + while i >= self.mons.len() { + self.mons.push(MonitorInfo::default()) } - let dirty = self.0[i].screen_info != info; - self.0[i].screen_info = info; + let dirty = self.mons[i].screen_info != info; + self.mons[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() + self.mons.len() } /// Find the [`Client`] corresponding to the given window pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> { - self.0 + self.mons .iter_mut() .flat_map(|mi| mi.clients.iter_mut()) .find(|c| c.window == window) @@ -221,7 +253,7 @@ impl ClientState { /// 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() { + for mon in self.mons.iter_mut() { if let Some(pos) = mon.clients.iter().position(|c| c.window == window) { return Some(mon.clients.remove(pos)); } @@ -229,14 +261,41 @@ impl ClientState { None } + /// Start managing the given window, adding it to the client list and ensuring its configuration is valid. + /// This function sends multiple requests without checking them, so conn.flush() should be called after. + fn manage(&mut self, conn: &Connection, window: Window) { + // TODO: inherit from parent if window is transient + let mon = self.selected_monitor; + + // TODO: inserting at index 0 is why dwm uses linked lists, maybe this can be improved + self.mons[mon].clients.insert( + 0, + Client { + window, + x: 0, + y: 0, + width: 0, + height: 0, + border_width: 0, + mapped: false, + }, + ); + + self.rearrange_monitor(conn, mon) + } + /// Rearrange all clients, reconfiguring them as needed. - pub fn rearrange(&mut self, _conn: &Connection) -> Result<()> { - todo!() + /// This function sends multiple requests without checking them, so conn.flush() should be called after. + pub fn rearrange(&mut self, conn: &Connection) { + for mon in 0..self.monitor_count() { + self.rearrange_monitor(conn, mon); + } } - /// 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!() + /// Rearrange a specific monitor + /// This function sends multiple requests without checking them, so conn.flush() should be called after. + fn rearrange_monitor(&mut self, conn: &Connection, mon: usize) { + (self.arrange)(&mut self.mons[mon], conn); } } @@ -251,11 +310,13 @@ pub struct Client { width: u16, height: u16, border_width: u16, + mapped: bool, } impl Client { - fn configure_notify(&self, conn: &Connection) -> Result<()> { - conn.check_request(conn.send_request_checked(&SendEvent { + /// This function does not check for success, so conn.flush() should be called after. + fn configure_notify(&self, conn: &Connection) { + conn.send_request(&SendEvent { destination: SendEventDest::Window(self.window), event_mask: EventMask::STRUCTURE_NOTIFY, event: &ConfigureNotifyEvent::new( @@ -270,9 +331,61 @@ impl Client { false, ), propagate: false, - }))?; + }); + } - Ok(()) + /// Set this client's geometry, also updating the X11 window if needed. + 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()), + ], + }); + } + + /// Ensure this client is currently mapped / visible + fn ensure_mapped(&mut self, conn: &Connection) { + if !self.mapped { + conn.send_request(&MapWindow { + window: self.window, + }); + self.mapped = true; + } + } + + /// Ensure this client is currently unmapped / invisible + fn ensure_unmapped(&mut self, conn: &Connection) { + if self.mapped { + conn.send_request(&UnmapWindow { + window: self.window, + }); + self.mapped = false; + } } } @@ -289,3 +402,59 @@ impl Default for MonitorInfo { } } } + +fn tile(mon: &mut MonitorInfo, conn: &Connection) { + if mon.clients.is_empty() { + return; + } + + let n = mon.clients_tiled_mut().count(); + let nmaster = 1; + let mfact = 0.6; + + let MonitorGeometry { + x_org, + y_org, + width: mon_width, + height: mon_height, + } = mon.screen_info; + + let main_width = if nmaster == 0 { + 0 + } else if n > nmaster { + ((mon.screen_info.width as f64) * mfact) as u16 + } else { + mon.screen_info.width + }; + + let (mut main_y, mut second_y) = (0, 0); + for (i, c) in mon.clients_tiled_mut().enumerate() { + if i < nmaster { + let h = (mon_height - main_y) / (min(nmaster, n) - i) as u16; + c.set_geom( + conn, + x_org, + y_org + main_y as i16, + main_width - (2 * c.border_width), + h - (2 * c.border_width), + c.border_width, + ); + + main_y += h; + } else { + let h = (mon_height - second_y) / (n - i) as u16; + c.set_geom( + conn, + x_org + main_width as i16, + y_org + second_y as i16, + mon_width - main_width - (2 * c.border_width), + h - (2 * c.border_width), + c.border_width, + ); + + second_y += h; + } + + c.ensure_mapped(conn); + } +} -- cgit v1.2.3