diff options
author | tcmal <me@aria.rip> | 2024-06-21 19:13:12 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-06-21 19:13:12 +0100 |
commit | 475253b7bcfd03a932c4b7efd969b3d2bf155035 (patch) | |
tree | 44789ce271d14c89cbff777e75f1e9ab6e1a5e64 /src/clients.rs | |
parent | c3e98e34ed7d42ef4271339de88f7131e7647442 (diff) |
refactor connection-related stuff out, make things a bit cleaner
Diffstat (limited to 'src/clients.rs')
-rw-r--r-- | src/clients.rs | 710 |
1 files changed, 0 insertions, 710 deletions
diff --git a/src/clients.rs b/src/clients.rs deleted file mode 100644 index e9efeab..0000000 --- a/src/clients.rs +++ /dev/null @@ -1,710 +0,0 @@ -use std::cmp::min; - -use crate::{config::BORDER_WIDTH, error::*, WM}; -use xcb::{ - x::{ - self, ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, - ConfigureRequestEvent, ConfigureWindow, Cw, DestroyNotifyEvent, Drawable, EventMask, - GetGeometry, GetProperty, GetWindowAttributes, MapRequestEvent, MapWindow, Pixmap, - SendEvent, SendEventDest, UnmapNotifyEvent, UnmapWindow, Window, - }, - xinerama::{self, ScreenInfo}, - BaseEvent, Connection, Extension, Xid, XidNew, -}; - -impl WM<'_> { - /// Update the client 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<bool> { - 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(()) - } - - /// Perform configure requests if we're happy with them, or they're for an unmanaged window. - 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(()) - } - - /// Removing destroyed windows from the client list and rearrange. - 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, 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() { - // Something special, don't manage it just let it do its thing. - return Ok(()); - } - - // Start managing, and map window - self.manage(self.conn, e.window()); - - self.conn.flush()?; - - Ok(()) - } - - /// When a window is unmapped, either stop managing it or update its state. - 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() { - self.conn.send_request(&ChangeProperty { - mode: xcb::x::PropMode::Replace, - window: e.window(), - property: self.atoms.wm_state, - r#type: self.atoms.wm_state, - data: &[0_u8, 0_u8], - }); - } else { - self.clients.remove_client(e.window()); - self.clients.rearrange(self.conn); - // TODO: 'disown' the window - unmanage(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, - urgent: false, - never_focus: 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 - c.sync_properties(conn, true); - - // XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); - c.set_event_mask( - conn, - EventMask::ENTER_WINDOW - | EventMask::FOCUS_CHANGE - | EventMask::PROPERTY_CHANGE - | EventMask::STRUCTURE_NOTIFY, - ); - // TODO: grabbuttons - - // set ewmh - self.conn.send_request(&ChangeProperty { - mode: xcb::x::PropMode::Append, - window: self.root, - property: self.atoms.net_client_list, - r#type: x::ATOM_WINDOW, - data: &[window], - }); - self.set_client_withdrawn(window, false); - - // TODO: XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ - self.refocus(mon, 0); - self.clients.rearrange_monitor(conn, mon); - } - - /// Set the given window as withdrawn / not withdrawn. - fn set_client_withdrawn(&self, window: Window, withdrawn: bool) { - self.conn.send_request(&ChangeProperty { - mode: xcb::x::PropMode::Replace, - window, - property: self.atoms.wm_state, - r#type: self.atoms.wm_state, - data: &[!withdrawn as u8, 0_u8], - }); - } -} - -/// 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. - /// The connection will be flushed after it is called. - arrange: &'static dyn Fn(&mut MonitorInfo, &Connection), - - /// A client list for each monitor. - mons: Vec<MonitorInfo>, - - /// Co-ordinates to the currently focused window. - focused: (usize, usize), -} - -/// Info stored for each monitor -#[derive(Debug)] -pub struct MonitorInfo { - /// Clients attached to that monitor - clients: Vec<Client>, - - /// The monitor's geometry - screen_info: MonitorGeometry, -} - -impl MonitorInfo { - /// Iterate over all tiled clients, returning a mutable reference to each. - pub fn clients_tiled_mut(&mut self) -> impl Iterator<Item = &mut Client> { - // TODO: tag filtering, floating - self.clients.iter_mut() - } -} - -impl ClientState { - /// Get the amount of monitors this state is currently aware of - pub fn monitor_count(&self) -> usize { - self.mons.len() - } - - /// Set the new amount of screens, without unmanaging any clients. - pub fn truncate_screens(&mut self, new_size: usize) { - // hack: working around 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 given screen's geometry, 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 - } - - /// Find the [`Client`] corresponding to the given window - pub fn find_client_mut(&mut self, window: Window) -> Option<&mut Client> { - let (mon, i) = self.find_client_pos(window)?; - Some(&mut self.mons[mon].clients[i]) - } - - /// Find the position of the client with the given window, returning (monitor, index) - pub 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 the position of the currently focused client. This position may be invalid. - pub fn focused(&self) -> (usize, usize) { - self.focused - } - - /// 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<Client> { - 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); - } - - /// Get the currently focused monitor - 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, - - urgent: bool, - never_focus: bool, -} - -impl Client { - /// Send a configure configure notify event with the current geometry. - /// 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. - /// This function does not check for success, so conn.flush() should be called after. - 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; - } - } - - /// Ensure this client is currently unmapped / invisible - /// This function does not check for success, so conn.flush() should be called after. - 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 - } - - /// Set the event mask for this window - /// This function does not check for success, so conn.flush() should be called after. - 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) = XWMHints::get(conn, self.window) else { - return; - }; - - if focused && hints.is_urgent() { - hints.set_urgent(false); - hints.set(conn, self.window); - } else { - self.urgent = hints.is_urgent(); - } - - self.never_focus = if hints.input_valid() { - !hints.input - } else { - false - }; - } -} - -struct XWMHints { - flags: u32, - input: bool, - initial_state: i32, - icon_pixmap: Pixmap, - icon_window: Window, - icon_x: i32, - icon_y: i32, - icon_mask: Pixmap, - window_group: u32, -} - -impl XWMHints { - pub fn get(conn: &Connection, window: Window) -> Option<Self> { - // https://github.com/mirror/libX11/blob/ff8706a5eae25b8bafce300527079f68a201d27f/src/GetHints.c#L106 - // https://github.com/mirror/libX11/blob/master/src/Xatomtype.h#L111 - let hints = conn - .wait_for_reply(conn.send_request(&GetProperty { - window: window, - delete: false, - property: x::ATOM_WM_HINTS, - r#type: x::ATOM_WM_HINTS, - long_offset: 0, - long_length: 9, - })) - .ok()?; - - if hints.r#type() != x::ATOM_WM_HINTS || hints.length() < 8 || hints.format() != 32 { - return None; - } - - let [flags, input, initial_state, icon_pixmap, icon_window, icon_x, icon_y, icon_mask, window_group] = - match hints.value::<u32>() { - [f, i, is, ip, iw, ix, iy, im, wg] => [f, i, is, ip, iw, ix, iy, im, wg], - [f, i, is, ip, iw, ix, iy, im] => [f, i, is, ip, iw, ix, iy, im, &0], - _ => unreachable!(), - }; - - unsafe { - Some(Self { - flags: *flags, - input: *input > 0, - initial_state: std::mem::transmute::<u32, i32>(*initial_state), - icon_pixmap: Pixmap::new(*icon_pixmap), - icon_window: Window::new(*icon_window), - icon_x: std::mem::transmute::<u32, i32>(*icon_x), - icon_y: std::mem::transmute::<u32, i32>(*icon_y), - icon_mask: Pixmap::new(*icon_mask), - window_group: *window_group, - }) - } - } - - pub fn set(&self, conn: &Connection, window: Window) { - todo!() - } - - pub fn is_urgent(&self) -> bool { - (self.flags & (1 << 8)) > 0 - } - - pub fn set_urgent(&mut self, urgent: bool) { - self.flags &= (1 << 8) - } - - fn input_valid(&self) -> bool { - (self.flags & (1 << 0)) > 0 - } -} - -impl Default for MonitorInfo { - fn default() -> Self { - Self { - clients: vec![], - screen_info: MonitorGeometry { - x_org: 0, - y_org: 0, - width: 0, - height: 0, - }, - } - } -} - -/// A simple tiling function -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); - } -} - -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 on the monitor's geometry. -#[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<ScreenInfo> for MonitorGeometry { - fn from(value: ScreenInfo) -> Self { - Self { - x_org: value.x_org, - y_org: value.y_org, - width: value.width, - height: value.height, - } - } -} |