use std::cmp::min; use xcb::{ x::{ ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, ConfigureWindow, Cw, DestroyNotifyEvent, Drawable, EventMask, GetGeometry, GetWindowAttributes, MapRequestEvent, MapWindow, SendEvent, SendEventDest, UnmapNotifyEvent, UnmapWindow, Window, }, xinerama::{self, ScreenInfo}, BaseEvent, Connection, Extension, Xid, }; use crate::{error::*, WM}; const BORDER_WIDTH: u16 = 3; impl WM<'_> { /// Update the client state's recorded monitors and monitor sizes /// 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() .any(|e| e == Extension::Xinerama) { let reply = self .conn .wait_for_reply(self.conn.send_request(&xinerama::QueryScreens {}))?; // 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() { 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); } // TODO: it looks like this won't actually update when the screen size changes? let setup = self.conn.get_setup(); let screen = setup .roots() .nth(self.screen_num as usize) .ok_or(Error::NoSuchScreen)?; dirty |= self.clients.set_monitor_geometry( 0, MonitorGeometry { x_org: 0, y_org: 0, width: screen.width_in_pixels(), height: screen.height_in_pixels(), }, ); } Ok(dirty) } /// 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.conn.flush()?; } Ok(()) } /// 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); self.conn.flush()?; } 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::Width(e.width().into()), ConfigWindow::Height(e.height().into()), ConfigWindow::BorderWidth(e.border_width().into()), ConfigWindow::StackMode(e.stack_mode()), ], }))? } Ok(()) } /// 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.conn.flush()?; } Ok(()) } /// 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.manage(self.conn, e.window()); self.conn.flush()?; Ok(()) } /// 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()); self.clients.rearrange(self.conn); // TODO: 'disown' the window - unmange(c, 0) } } Ok(()) } /// 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.clients.focused_mon(); let Ok(geom) = conn.wait_for_reply(conn.send_request(&GetGeometry { drawable: Drawable::Window(window), })) else { return; // window stopped existing, so we can't manage it }; self.unfocus(); // TODO: inserting at index 0 is why dwm uses linked lists, maybe this can be improved self.clients.mons[mon].clients.insert( 0, Client { window, x: 0, y: 0, width: 0, height: 0, border_width: BORDER_WIDTH, mapped: false, }, ); // TODO: Clamp window size to monitor let c = &mut self.clients.mons[mon].clients[0]; c.set_geom( conn, geom.x(), geom.y(), geom.width(), geom.height(), BORDER_WIDTH, ); c.set_border(conn, self.colours.border_normal()); c.ensure_mapped(conn); // TODO: updatewindowtype // TODO: updatesizehints // TODO: updatewmhints // XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); c.select_input( conn, EventMask::ENTER_WINDOW | EventMask::FOCUS_CHANGE | EventMask::PROPERTY_CHANGE | EventMask::STRUCTURE_NOTIFY, ); // TODO: grabbuttons // TODO: add to NetClientList // TODO: XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ // TODO: setclientstate(c, NormalState); self.refocus(mon, 0); self.clients.rearrange_monitor(conn, mon); } } /// 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. 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), mons: Vec, focused: (usize, usize), } impl Default for ClientState { fn default() -> Self { Self { arrange: &tile, focused: (0, 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("focused", &self.focused) .field("mons", &self.mons) .finish() } } /// Info stored for a single monitor #[derive(Debug)] pub struct MonitorInfo { /// Clients attached to that monitor clients: Vec, /// The monitor's geometry 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, 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 { /// Set the new amount of screens, moving clients away if necessary pub fn truncate_screens(&mut self, new_size: usize) { // hack: double borrow stuff let mut moved_clients = vec![]; for old in self.mons.drain(new_size - self.mons.len()..self.mons.len()) { moved_clients.extend(old.clients.into_iter()); } 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.mons.len() { self.mons.push(MonitorInfo::default()) } 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.mons.len() } /// Find the [`Client`] corresponding to the given window pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> { self.mons .iter_mut() .flat_map(|mi| mi.clients.iter_mut()) .find(|c| c.window == window) } /// Find the position of the client with the given window, returning (monitor, index) pub(crate) fn find_client_pos(&mut self, window: Window) -> Option<(usize, usize)> { for (pos_mon, mon) in self.mons.iter_mut().enumerate() { if let Some(pos) = mon.clients.iter().position(|c| c.window == window) { return Some((pos_mon, pos)); } } None } /// Get a mutable reference to the currently focused client, if it exists. pub fn focused_mut(&mut self) -> Option<&mut Client> { self.client_mut(self.focused.0, self.focused.1) } /// Set the currently focused client, returning a mutable reference to it if the co-ordinates are valid. pub fn set_focused(&mut self, mut mon: usize, mut i: usize) -> Option<&mut Client> { if self.mons.is_empty() { return None; } if mon >= self.mons.len() { mon = self.mons.len() - 1; } if self.mons[mon].clients.is_empty() { return None; } if i >= self.mons[mon].clients.len() { i = self.mons[mon].clients.len() - 1; } self.focused = (mon, i); Some(&mut self.mons[mon].clients[i]) } /// Get a mutable reference to the client at the given co-ordinates, if they are valid. pub fn client_mut(&mut self, mon: usize, i: usize) -> Option<&mut Client> { if mon < self.mons.len() && i < self.mons[mon].clients.len() { Some(&mut self.mons[mon].clients[i]) } else { None } } /// 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 { let (mon, i) = self.find_client_pos(window)?; Some(self.mons[mon].clients.remove(i)) } /// Rearrange all clients, reconfiguring them as needed. /// 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); } } /// 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); } fn focused_mon(&self) -> usize { self.focused.0 } } /// 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, } impl Client { /// 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( 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. 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 of the X11 window to the given value. /// This sends a request but doesn't wait for the response. 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 pub 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 pub fn ensure_unmapped(&mut self, conn: &Connection) { if self.mapped { conn.send_request(&UnmapWindow { window: self.window, }); self.mapped = false; } } /// Get the associated window pub fn window(&self) -> Window { self.window } fn select_input(&self, conn: &Connection, event_mask: EventMask) { conn.send_request(&ChangeWindowAttributes { window: self.window(), value_list: &[Cw::EventMask(event_mask)], }); } } impl Default for MonitorInfo { fn default() -> Self { Self { clients: vec![], screen_info: MonitorGeometry { x_org: 0, y_org: 0, width: 0, height: 0, }, } } } 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); } }