diff --git a/Cargo.toml b/Cargo.toml
index 184953f..16f2527 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,3 +14,5 @@ slotmap = "1.0.6"
[[bin]]
name = "xcrab-msg"
path = "src/msg/main.rs"
+
+[features]
diff --git a/src/main.rs b/src/main.rs
index b889d2c..952abfb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,7 +14,6 @@
// along with this program. If not, see .
#![warn(clippy::pedantic)]
-#![allow(clippy::too_many_lines)]
use std::fmt::{Debug, Display};
diff --git a/src/x11/client.rs b/src/x11/client.rs
index e24bd60..63ee162 100644
--- a/src/x11/client.rs
+++ b/src/x11/client.rs
@@ -93,6 +93,36 @@ struct Rectangle {
contents: RectangleContents,
}
+impl Rectangle {
+ fn unwrap_pane(&self) -> &Pane {
+ match &self.contents {
+ RectangleContents::Pane(pane) => pane,
+ RectangleContents::Client(_) => unreachable!(),
+ }
+ }
+
+ fn unwrap_client(&self) -> &Client {
+ match &self.contents {
+ RectangleContents::Pane(_) => unreachable!(),
+ RectangleContents::Client(client) => client,
+ }
+ }
+
+ fn unwrap_pane_mut(&mut self) -> &mut Pane {
+ match &mut self.contents {
+ RectangleContents::Pane(pane) => pane,
+ RectangleContents::Client(_) => unreachable!(),
+ }
+ }
+
+ fn unwrap_client_mut(&mut self) -> &mut Client {
+ match &mut self.contents {
+ RectangleContents::Pane(_) => unreachable!(),
+ RectangleContents::Client(client) => client,
+ }
+ }
+}
+
#[derive(Debug, Clone)]
enum RectangleContents {
Pane(Pane),
@@ -117,175 +147,287 @@ impl XcrabWindowManager {
XcrabWindowManager::default()
}
+ /// Given the `rect_key` from a `parent -> rect` relationship, makes A
+ /// `parent -> new_pane -> rect` relationship, then returns `new_pane_key`
+ fn insert_pane_above(
+ &mut self,
+ rect_key: XcrabKey,
+ directionality: Directionality,
+ ) -> Option {
+ let rect = self.rects.get(rect_key)?;
+ let rect_dimensions = rect.cached_dimensions;
+ let parent_key = rect.parent;
+
+ let new_pane = Rectangle {
+ parent: parent_key,
+ cached_dimensions: rect_dimensions,
+ contents: RectangleContents::Pane(Pane {
+ children: vec![rect_key],
+ directionality,
+ }),
+ };
+
+ let new_pane_key = if parent_key == rect_key {
+ // the given node was the root node
+
+ // this new pane will be the new root, so it becomes its own parent
+ self.rects.insert_with_key(|key| Rectangle {
+ parent: key,
+ ..new_pane
+ })
+ } else {
+ // the given node was not the root node, and thus has a parent
+
+ let new_pane_key = self.rects.insert(new_pane);
+
+ let parent_pane = self.rects.get_mut(parent_key).unwrap().unwrap_pane_mut();
+ let index = parent_pane
+ .children
+ .iter()
+ .copied()
+ .position(|v| v == rect_key)
+ .unwrap();
+ // replace the "parent -> rect" relationship with a "parent -> new_pane" relationship
+ parent_pane.children[index] = new_pane_key;
+
+ new_pane_key
+ };
+
+ let rect = self.rects.get_mut(rect_key).unwrap();
+ rect.parent = new_pane_key;
+
+ Some(new_pane_key)
+ }
+
+ /// Adds a new client.
pub async fn add_client(
&mut self,
conn: &mut Dpy,
win: Window,
) -> Result<()> {
- self.add_client_direction(conn, win, Direction::Right).await
+ // use rand::prelude::SliceRandom;
+ // let direction = *[
+ // Direction::Up,
+ // Direction::Down,
+ // Direction::Left,
+ // Direction::Right,
+ // ]
+ // .choose(&mut rand::thread_rng())
+ // .unwrap();
+ self.add_client_direction(conn, win, Direction::Right)
+ .await
}
+ /// Adds a new client in the given direction from the focused window.
pub async fn add_client_direction(
&mut self,
conn: &mut Dpy,
win: Window,
direction: Direction,
) -> Result<()> {
+ #[allow(clippy::enum_glob_use)]
+ use {Direction::*, Directionality::*};
+
+ let focused = match self.focused {
+ Some(v) => v,
+ None => return self.add_first_client(conn, win).await,
+ };
+
+ // this code path is somewhat difficult to understand, so i added some comments
+
+ // frame the window
let frame = frame(conn, win).await?;
- if let Some(focused) = self.focused {
- #[allow(clippy::enum_glob_use)]
- use {Direction::*, Directionality::*};
+ // the XcrabKey to the focused client
+ let focused_client_key = *self
+ .clients
+ .get(&focused)
+ .ok_or(XcrabError::ClientDoesntExist)?;
- // this code path is somewhat difficult to understand, so i added some comments
+ // the directionality we want to find: if we are tiling Up or Down, we
+ // want a Vertical pane, and for Left or Right we want a Horizontal one.
+ let target_directionality = match direction {
+ Up | Down => Vertical,
+ Left | Right => Horizontal,
+ };
- // the XcrabKey to the focused client
- let focused_client_key = *self
- .clients
- .get(&focused)
- .ok_or(XcrabError::ClientDoesntExist)?;
+ // this var will be used in the upcoming loop
+ let mut child_key = focused_client_key;
- // the directionality we want to find: if we are tiling Up or Down, we
- // want a Vertical pane, and for Left or Right we want a Horizontal one.
- let target_directionality = match direction {
- Up | Down => Vertical,
- Left | Right => Horizontal,
- };
+ // go up the chain (using `Rectangle.parent`) until you find a pane with the correct directionality
+ let parent_key = loop {
+ let parent_key = self.rects.get(child_key).unwrap().parent;
- // this var will be used in the upcoming loop
- let mut child_key = focused_client_key;
+ if parent_key == child_key {
+ // uh oh, we hit the top, now we will wrap the root client
+ // in a new pane and make this new pane the root
- // go up the chain (using `Rectangle.parent`) until you find a pane with the correct directionality
- let parent_key = loop {
- let parent_key = self.rects.get(child_key).unwrap().parent;
-
- if parent_key == child_key {
- // uh oh, we hit the top, time to create a pane
-
- child_key = focused_client_key;
- let child = self.rects.get(child_key).unwrap();
- // parent of the focused client
- let parent_key = child.parent;
-
- // the new pane
- let new_pane = Rectangle {
- parent: parent_key,
- cached_dimensions: child.cached_dimensions,
- contents: RectangleContents::Pane(Pane {
- // its child is the focused client
- children: vec![child_key],
- directionality: target_directionality,
- }),
- };
-
- let new_pane_key = if child_key == parent_key {
- self.rects.insert_with_key(|key| Rectangle {
- parent: key,
- ..new_pane
- })
- } else {
- self.rects.insert(new_pane)
- };
-
- // create the new_pane -> child relationship
- self.rects.get_mut(child_key).unwrap().parent = new_pane_key;
-
- let parent = self.rects.get_mut(parent_key).unwrap();
- match &mut parent.contents {
- RectangleContents::Pane(pane) => {
- let index = pane
- .children
- .iter()
- .copied()
- .position(|v| v == child_key)
- .unwrap();
- // remove the parent -> child relation and replace it with parent -> new_pane
- pane.children[index] = new_pane_key;
- }
- // this means that the focused client is the root client
- RectangleContents::Client(_) => {}
- }
-
- break new_pane_key;
- }
-
- let parent = self.rects.get(parent_key).unwrap();
-
- match &parent.contents {
- RectangleContents::Pane(pane) => {
- if pane.directionality == target_directionality {
- // yay! found it
- break parent_key;
- }
-
- // nope, continue
- child_key = parent_key;
- }
- // parents should never be clients, only panes
- RectangleContents::Client(_) => unreachable!(),
- }
- };
-
- // `parent_key` now holds the key for the pane with the target
- // directionality, and `child_key` holds the child key which will
- // be used to find where to insert our new client
-
- // the key to the newly created client
- let new_rect_key = self.rects.insert(Rectangle {
- parent: parent_key,
- // this default will be overriden by the `update_rectangle` down below
- cached_dimensions: Dimensions::default(),
- contents: RectangleContents::Client(Client { frame }),
- });
-
- // the Pane of the Rectangle of `parent_key`
- let parent = match &mut self.rects.get_mut(parent_key).unwrap().contents {
- RectangleContents::Pane(pane) => pane,
- RectangleContents::Client(_) => unreachable!(),
- };
-
- // the index which we want to `insert` at, found using `child_key`
- let mut index = parent
- .children
- .iter()
- .copied()
- .position(|v| v == child_key)
- .unwrap();
-
- if let Down | Right = direction {
- index += 1;
+ break self
+ .insert_pane_above(child_key, target_directionality)
+ .unwrap();
}
- // insert the new rect
- parent.children.insert(index, new_rect_key);
+ let parent = self.rects.get(parent_key).unwrap();
- self.clients.insert(win, new_rect_key);
+ if parent.unwrap_pane().directionality == target_directionality {
+ // yay! found it
+ break parent_key;
+ }
- self.focused = Some(win);
+ // nope, continue
+ child_key = parent_key;
+ };
- // update the parent rectangle to also update all the siblings of out new rect
- self.update_rectangle(conn, parent_key, None).await?;
- } else {
- let root_geo = conn.default_root().geometry_immediate_async(conn).await?;
+ // `parent_key` now holds the key for the pane with the target
+ // directionality, and `child_key` holds the child key which will
+ // be used to find where to insert our new client
- let key = self.rects.insert_with_key(|key| Rectangle {
- parent: key,
- cached_dimensions: Dimensions {
- x: root_geo.x.try_into().unwrap(),
- y: root_geo.y.try_into().unwrap(),
- width: root_geo.width,
- height: root_geo.height,
- },
- contents: RectangleContents::Client(Client { frame }),
- });
+ // the key to the newly created client
+ let new_rect_key = self.rects.insert(Rectangle {
+ parent: parent_key,
+ // this default will be overriden by the `update_rectangle` down below
+ cached_dimensions: Dimensions::default(),
+ contents: RectangleContents::Client(Client { frame }),
+ });
- self.clients.insert(win, key);
+ // the Pane of the Rectangle of `parent_key`
+ let parent_pane = self.rects.get_mut(parent_key).unwrap().unwrap_pane_mut();
- self.focused = Some(win);
+ // the index which we want to `insert` at, found using `child_key`
+ let mut index = parent_pane
+ .children
+ .iter()
+ .copied()
+ .position(|v| v == child_key)
+ .unwrap();
- self.update_rectangle(conn, key, None).await?;
+ if let Down | Right = direction {
+ index += 1;
}
+ // insert the new rect
+ parent_pane.children.insert(index, new_rect_key);
+
+ self.clients.insert(win, new_rect_key);
+
+ self.focused = Some(win);
+
+ // update the parent rectangle to also update all the siblings of our new rect
+ self.update_rectangle(conn, parent_key, None).await?;
+
+ frame.map(conn).await?;
+
+ Ok(())
+ }
+
+ /// Adds a new client in the given direction directly adjacent to the focused window, creating a new pane if needed.
+ pub async fn add_client_direction_immediate(
+ &mut self,
+ conn: &mut Dpy,
+ win: Window,
+ direction: Direction,
+ ) -> Result<()> {
+ #[allow(clippy::enum_glob_use)]
+ use {Direction::*, Directionality::*};
+
+ let focused = match self.focused {
+ Some(v) => v,
+ None => return self.add_first_client(conn, win).await,
+ };
+
+ // frame the window
+ let frame = frame(conn, win).await?;
+
+ // get the focused client
+ let focused_client_key = *self.clients.get(&focused).unwrap();
+ let focused_client = self.rects.get(focused_client_key).unwrap();
+
+ // get the parent of the focused client
+ let mut parent_key = focused_client.parent;
+ let parent_pane_dir = match &self.rects.get(parent_key).unwrap().contents {
+ RectangleContents::Pane(pane) => Some(pane.directionality),
+ RectangleContents::Client(_) => None,
+ };
+
+ // find the target directionality
+ let target_directionality = match direction {
+ Up | Down => Vertical,
+ Left | Right => Horizontal,
+ };
+
+ // if the parent's directionality is wrong...
+ // note: the `None` case is hit if we are the root client
+ if parent_pane_dir.is_none() || parent_pane_dir.unwrap() != target_directionality {
+ // insert a pane above the client with the right directionality
+ parent_key = self
+ .insert_pane_above(focused_client_key, target_directionality)
+ .unwrap();
+ }
+
+ // create the rect
+ let new_rect_key = self.rects.insert(Rectangle {
+ parent: parent_key,
+ // this default will be overriden by the `update_rectangle` down below
+ cached_dimensions: Dimensions::default(),
+ contents: RectangleContents::Client(Client { frame }),
+ });
+
+ // get the parent of the focused client (may have been modified above)
+ let parent_pane = self.rects.get_mut(parent_key).unwrap().unwrap_pane_mut();
+
+ // get the index we want to insert at
+ let mut index = parent_pane
+ .children
+ .iter()
+ .copied()
+ .position(|v| v == focused_client_key)
+ .unwrap();
+
+ if let Down | Right = direction {
+ index += 1;
+ }
+
+ // insert
+ parent_pane.children.insert(index, new_rect_key);
+
+ self.clients.insert(win, new_rect_key);
+
+ self.focused = Some(win);
+
+ // update
+ self.update_rectangle(conn, parent_key, None).await?;
+
+ frame.map(conn).await?;
+
+ Ok(())
+ }
+
+ async fn add_first_client(
+ &mut self,
+ conn: &mut Dpy,
+ win: Window,
+ ) -> Result<()> {
+ let frame = frame(conn, win).await?;
+
+ let root_geo = conn.default_root().geometry_immediate_async(conn).await?;
+
+ let key = self.rects.insert_with_key(|key| Rectangle {
+ parent: key,
+ cached_dimensions: Dimensions {
+ x: root_geo.x.try_into().unwrap(),
+ y: root_geo.y.try_into().unwrap(),
+ width: root_geo.width,
+ height: root_geo.height,
+ },
+ contents: RectangleContents::Client(Client { frame }),
+ });
+
+ self.clients.insert(win, key);
+
+ self.focused = Some(win);
+
+ self.update_rectangle(conn, key, None).await?;
+
frame.map(conn).await?;
Ok(())
@@ -360,22 +502,15 @@ impl XcrabWindowManager {
let client = self.rects.get(client_key).unwrap();
- match client.contents {
- RectangleContents::Pane(_) => unreachable!(),
- RectangleContents::Client(client) => {
- client.frame.unframe(conn).await?;
- }
- }
+ client.unwrap_client().frame.unframe(conn).await?;
let parent_key = client.parent;
let parent = self.rects.get_mut(parent_key).unwrap();
- match &mut parent.contents {
- RectangleContents::Pane(pane) => {
- pane.children.retain(|&v| v != client_key);
- }
- RectangleContents::Client(_) => unreachable!(),
- }
+ parent
+ .unwrap_pane_mut()
+ .children
+ .retain(|&v| v != client_key);
self.update_rectangle(conn, parent_key, None).await?;