summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/clients.rs231
1 files 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<MonitorInfo>);
+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<MonitorInfo>,
+}
+
+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<Item = &mut Client> {
+ // 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<Client> {
- 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);
+ }
+}