summaryrefslogtreecommitdiff
path: root/src/clients.rs
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-06-21 19:13:12 +0100
committertcmal <me@aria.rip>2024-06-21 19:13:12 +0100
commit475253b7bcfd03a932c4b7efd969b3d2bf155035 (patch)
tree44789ce271d14c89cbff777e75f1e9ab6e1a5e64 /src/clients.rs
parentc3e98e34ed7d42ef4271339de88f7131e7647442 (diff)
refactor connection-related stuff out, make things a bit cleaner
Diffstat (limited to 'src/clients.rs')
-rw-r--r--src/clients.rs710
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,
- }
- }
-}