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 | |
parent | c3e98e34ed7d42ef4271339de88f7131e7647442 (diff) |
refactor connection-related stuff out, make things a bit cleaner
Diffstat (limited to 'src/clients')
-rw-r--r-- | src/clients/client.rs | 174 | ||||
-rw-r--r-- | src/clients/hints.rs | 104 | ||||
-rw-r--r-- | src/clients/mod.rs | 425 | ||||
-rw-r--r-- | src/clients/monitors.rs | 55 | ||||
-rw-r--r-- | src/clients/tile.rs | 68 |
5 files changed, 826 insertions, 0 deletions
diff --git a/src/clients/client.rs b/src/clients/client.rs new file mode 100644 index 0000000..a7b1a8d --- /dev/null +++ b/src/clients/client.rs @@ -0,0 +1,174 @@ +use xcb::{ + x::{ + ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, + ConfigureWindow, Cw, EventMask, MapWindow, SendEvent, SendEventDest, Window, + }, + Xid, +}; + +use crate::{config::BORDER_WIDTH, conn_info::Connection}; + +use super::hints; + +/// 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 { + pub const fn new(window: Window) -> Self { + Self { + window, + x: 0, + y: 0, + width: 0, + height: 0, + border_width: BORDER_WIDTH, + mapped: false, + urgent: false, + never_focus: false, + } + } + + /// Send a configure configure notify event with the current geometry. + /// This function does not check for success, so `conn.flush()` should be called after. + pub 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. + pub 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; + } + } + + /// Get the associated window + pub const 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. + pub 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) = hints::Xwm::get(conn, self.window) else { + return; + }; + + if focused && hints.is_urgent() { + hints.set_urgent(false); + hints.apply(conn, self.window); + } else { + self.urgent = hints.is_urgent(); + } + + self.never_focus = hints.input().is_some_and(|i| !i); + } + + /// Set the given window as withdrawn / not withdrawn. + pub fn set_withdrawn(&self, conn: &Connection<'_>, withdrawn: bool) { + conn.send_request(&ChangeProperty { + mode: xcb::x::PropMode::Replace, + window: self.window, + property: conn.atoms.wm_state, + r#type: conn.atoms.wm_state, + data: &[u8::from(!withdrawn), 0_u8], + }); + } + + pub fn set_urgent(&mut self, urgent: bool) { + self.urgent = urgent; + } + + pub const fn border_width(&self) -> u16 { + self.border_width + } + + pub const fn never_focus(&self) -> bool { + self.never_focus + } +} diff --git a/src/clients/hints.rs b/src/clients/hints.rs new file mode 100644 index 0000000..0250401 --- /dev/null +++ b/src/clients/hints.rs @@ -0,0 +1,104 @@ +use xcb::{ + x::{self, ChangeProperty, GetProperty, Pixmap, PropMode, Window}, + Xid, XidNew, +}; + +use crate::conn_info::Connection; + +pub struct Xwm { + 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 Xwm { + /// Get the EWM hints for the given window, if they exist and are valid. + 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, + 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, + }) + } + } + + /// Set these WM hints on the given window. + /// This function does not check for success, so `conn.flush()` should be called after. + pub fn apply(&self, conn: &Connection<'_>, window: Window) { + conn.send_request(&ChangeProperty { + mode: PropMode::Replace, + window, + property: x::ATOM_WM_HINTS, + r#type: x::ATOM_WM_HINTS, + data: unsafe { + &[ + self.flags, + u32::from(self.input), + std::mem::transmute::<i32, u32>(self.initial_state), + self.icon_pixmap.resource_id(), + self.icon_window.resource_id(), + std::mem::transmute::<i32, u32>(self.icon_x), + std::mem::transmute::<i32, u32>(self.icon_y), + self.icon_mask.resource_id(), + self.window_group, + ] + }, + }); + } + + /// If the window is flagged as urgent + pub const fn is_urgent(&self) -> bool { + (self.flags & 1 << 8) > 0 + } + + /// Set the urgent flag. [`Self::apply`] should be called afterwards. + pub fn set_urgent(&mut self, urgent: bool) { + self.flags &= u32::from(urgent) << 8; + } + + /// Whether the window has the `input` flag set, unset, or not specified. + pub const fn input(&self) -> Option<bool> { + if (self.flags & 1 << 0) > 0 { + Some(self.input) + } else { + None + } + } +} diff --git a/src/clients/mod.rs b/src/clients/mod.rs new file mode 100644 index 0000000..d07de74 --- /dev/null +++ b/src/clients/mod.rs @@ -0,0 +1,425 @@ +use crate::{ + config::BORDER_WIDTH, + conn_info::Connection, + error::{Error, Result}, + WM, +}; +use xcb::{ + x::{ + self, ChangeProperty, ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, + ConfigureWindow, DeleteProperty, DestroyNotifyEvent, Drawable, EventMask, GetGeometry, + GetWindowAttributes, InputFocus, MapRequestEvent, SetInputFocus, UnmapNotifyEvent, Window, + }, + xinerama::{self}, + BaseEvent, Extension, +}; + +pub use client::Client; +pub use monitors::*; + +mod client; +mod hints; +mod monitors; +mod tile; + +impl WM<'_> { + /// 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.send_and_check_request(&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(()) + } + + /// 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.conn.root() { + self.clients.update_geometry(&self.conn)?; + self.conn.flush()?; + } + Ok(()) + } + + /// Removing destroyed windows from the client list and rearrange. + pub(crate) fn handle_destroy_notify(&mut self, e: &DestroyNotifyEvent) -> Result<()> { + self.clients.unmanage(&self.conn, e.window(), true); + 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, or us doing actual mapping. Don't manage it just let it do its thing. + return Ok(()); + } + + // Start managing, and map window + self.clients.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 let Some(c) = self.clients.find_client_mut(e.window()) { + if e.is_from_send_event() { + c.set_withdrawn(&self.conn, true); + } else { + self.clients.unmanage(&self.conn, e.window(), false); + } + self.conn.flush()?; + } + + Ok(()) + } +} + +/// 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), +} + +impl ClientState { + /// Update the recorded monitors and monitor sizes, retiling if necessary. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. + pub fn update_geometry(&mut self, conn: &Connection<'_>) -> Result<()> { + let mut dirty = false; + if conn.active_extensions().any(|e| e == Extension::Xinerama) { + let reply = conn.wait_for_reply(conn.send_request(&xinerama::QueryScreens {}))?; + + // Monitor removed, move its clients away + if reply.screen_info().len() > self.monitor_count() { + dirty = true; + self.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.set_monitor_geometry(i, (*monitor).into()); + } + } else { + // Only one screen + if self.monitor_count() > 1 { + dirty = true; + self.truncate_screens(1); + } + + // TODO: it looks like this won't actually update when the screen size changes? + let setup = conn.get_setup(); + let screen = setup + .roots() + .nth(conn.screen_num()) + .ok_or(Error::NoSuchScreen)?; + + dirty |= self.set_monitor_geometry( + 0, + MonitorGeometry { + x_org: 0, + y_org: 0, + width: screen.width_in_pixels(), + height: screen.height_in_pixels(), + }, + ); + } + + if dirty { + self.rearrange(conn); + } + 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. + pub fn manage(&mut self, conn: &Connection<'_>, window: Window) { + // TODO: inherit from parent if window is transient + let mon = self.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 + }; + + // We're about to invalidate focus position + self.unfocus(conn); + // TODO: inserting at index 0 is why dwm uses linked lists, maybe this can be improved + self.mons[mon].clients.insert(0, Client::new(window)); + + // TODO: Clamp window size to monitor + let c = &mut self.mons[mon].clients[0]; + c.set_geom( + conn, + geom.x(), + geom.y(), + geom.width(), + geom.height(), + BORDER_WIDTH, + ); + c.set_border(conn, conn.colours.border_normal()); + c.ensure_mapped(conn); + + // TODO: updatewindowtype + // TODO: updatesizehints + c.sync_properties(conn, true); + + c.set_event_mask( + conn, + EventMask::ENTER_WINDOW + | EventMask::FOCUS_CHANGE + | EventMask::PROPERTY_CHANGE + | EventMask::STRUCTURE_NOTIFY, + ); + + // TODO: grabbuttons + + // Add to net_client_list + conn.send_request(&ChangeProperty { + mode: xcb::x::PropMode::Append, + window: conn.root(), + property: conn.atoms.net_client_list, + r#type: x::ATOM_WINDOW, + data: &[window], + }); + c.set_withdrawn(conn, false); + + // TODO: XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + self.refocus(conn, mon, 0); + self.rearrange_monitor(conn, mon); + } + + /// Stop managing the given window, and also unset attributes unless `already_destroyed` is true. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. + pub fn unmanage(&mut self, conn: &Connection<'_>, window: Window, already_destroyed: bool) { + let Some((mon, i)) = self.find_client_pos(window) else { + return; + }; + let c = self.mons[mon].clients.remove(i); + + if !already_destroyed { + c.set_event_mask(conn, EventMask::empty()); + // TODO: Ungrab button + c.set_withdrawn(conn, true); + } + self.rearrange(conn); + } + + /// Refocus on the client with the given co-ordinates, setting X11 properties as required. + /// If the given index is invalid, focus on the root instead. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. + pub fn refocus(&mut self, conn: &Connection<'_>, mon: usize, i: usize) { + self.unfocus(conn); + if let Some(new) = self.set_focused(mon, i) { + new.set_border(conn, conn.colours.border_focused()); + new.set_urgent(false); + if !new.never_focus() { + // XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + conn.send_request(&SetInputFocus { + revert_to: InputFocus::PointerRoot, + focus: new.window(), + time: x::CURRENT_TIME, + }); + // XChangeProperty(dpy, root, netatom[NetActiveWindow], + // XA_WINDOW, 32, PropModeReplace, + // (unsigned char *) &(c->win), 1); + // TODO: sendevent(c, wmatom[WMTakeFocus]); + } + } else { + conn.send_request(&SetInputFocus { + revert_to: InputFocus::PointerRoot, + focus: conn.root(), + time: x::CURRENT_TIME, + }); + conn.send_request(&DeleteProperty { + window: conn.root(), + property: conn.atoms.net_active_window, + }); + } + } + + /// Unfocus the currently focused window, if it exists. + /// This function sends multiple requests without checking them, so `conn.flush()` should be called after. + pub fn unfocus(&mut self, conn: &Connection<'_>) { + if let Some(old) = self.focused_mut() { + old.set_border(conn, conn.colours.border_normal()); + // TODO: clear some properties + } + } + + /// 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. + 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. + 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 a reference to the currently focused client, if it exists. + pub fn focused(&self) -> Option<&Client> { + self.client(self.focused.0, self.focused.1) + } + + /// 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) + } + + /// Get the position of the currently focused client. This position may be invalid. + pub fn is_focused(&self, e: Window) -> bool { + self.focused().is_some_and(|c| c.window() == e) + } + + /// 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(&self, mon: usize, i: usize) -> Option<&Client> { + if mon < self.mons.len() && i < self.mons[mon].clients.len() { + Some(&self.mons[mon].clients[i]) + } else { + None + } + } + + /// 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 + } + } + + /// 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 + const fn focused_mon(&self) -> usize { + self.focused.0 + } +} + +impl Default for ClientState { + fn default() -> Self { + Self { + arrange: &tile::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_non_exhaustive() + } +} diff --git a/src/clients/monitors.rs b/src/clients/monitors.rs new file mode 100644 index 0000000..0a9bcde --- /dev/null +++ b/src/clients/monitors.rs @@ -0,0 +1,55 @@ +use xcb::xinerama::ScreenInfo; + +use super::Client; + +/// Info stored for each monitor +#[derive(Debug)] +pub struct MonitorInfo { + /// Clients attached to that monitor + pub clients: Vec<Client>, + + /// The monitor's geometry + pub 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 Default for MonitorInfo { + fn default() -> Self { + Self { + clients: vec![], + screen_info: MonitorGeometry { + x_org: 0, + y_org: 0, + width: 0, + height: 0, + }, + } + } +} + +/// 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, + } + } +} diff --git a/src/clients/tile.rs b/src/clients/tile.rs new file mode 100644 index 0000000..4a2d4fc --- /dev/null +++ b/src/clients/tile.rs @@ -0,0 +1,68 @@ +use std::cmp::min; + +use crate::conn_info::Connection; + +use super::{MonitorGeometry, MonitorInfo}; + +/// A simple tiling function +#[allow( + clippy::cast_sign_loss, + clippy::cast_possible_wrap, + clippy::cast_possible_truncation, + clippy::cast_lossless +)] +pub 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); + } +} |