summaryrefslogtreecommitdiff
path: root/src/clients/mod.rs
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-13 22:27:16 +0100
committertcmal <me@aria.rip>2024-08-14 17:45:21 +0100
commit6fd934872a3c9f868bed5bd2f5ee33f0cb748912 (patch)
tree13f844d164ac7ffe44484ad24bed50ef6393b776 /src/clients/mod.rs
parentf9d5af7f060ada6f224861967e31bb74a0d24e18 (diff)
Move to one list of clients, shared across monitors
Diffstat (limited to 'src/clients/mod.rs')
-rw-r--r--src/clients/mod.rs367
1 files changed, 185 insertions, 182 deletions
diff --git a/src/clients/mod.rs b/src/clients/mod.rs
index 29219b6..7ea6f48 100644
--- a/src/clients/mod.rs
+++ b/src/clients/mod.rs
@@ -38,6 +38,9 @@ mod tile;
/// The tag a client has, similar to a workspace in most WMs.
pub type Tag = u8;
+pub type MonitorIdx = usize;
+pub type ClientIdx = usize;
+
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<()> {
@@ -56,9 +59,6 @@ impl WM<'_> {
);
}
- // TODO: Allow changing some properties:
- // - Border width
- // - Size and position if floating
c.configure_notify(&self.conn);
} else {
// Configure it as requested, and sort the rest when we actually map the window
@@ -128,71 +128,27 @@ impl WM<'_> {
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<'_>),
+ arrange: &'static dyn Fn(&Connection<'_>, &mut [Client], &MonitorInfo),
+
+ /// Clients attached to that monitor
+ pub clients: Vec<Client>,
- /// A client list for each monitor.
+ /// Information for each monitor.
mons: Vec<MonitorInfo>,
- /// Co-ordinates to the currently focused window.
- focused: (usize, usize),
+ /// Index of the currently focused client
+ focused_client: Option<ClientIdx>,
}
impl ClientState {
- /// Update the recorded monitors and monitor sizes, retiling if necessary.
- 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.
pub fn manage(&mut self, conn: &Connection<'_>, window: Window) {
- let mut mon = self.focused_mon();
+ let mut tag = self.focused_mon().focused_tag.create_tag();
let mut floating = false;
if let Some(parent) = hints::transient_for(conn, window) {
floating = true;
- if let Some((parent_mon, _)) = self.find_client_pos(parent) {
- mon = parent_mon;
+ if let Some(c) = self.find_client_mut(parent) {
+ tag = c.tag;
}
}
@@ -202,19 +158,15 @@ impl ClientState {
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
- let tag = self.mons[mon].focused_tag.create_tag();
- self.mons[mon].clients.insert(0, Client::new(window, tag));
+ self.clients.push(Client::new(window, tag));
let mon_geom @ MonitorGeometry {
width: mon_width,
height: mon_height,
..
- } = self.mons[mon].screen_info;
+ } = self.focused_mon().screen_info;
- let c = &mut self.mons[mon].clients[0];
+ let c = &mut self.clients[0];
#[allow(clippy::cast_sign_loss)]
c.set_geom(
conn,
@@ -253,25 +205,35 @@ impl ClientState {
});
c.set_withdrawn(conn, false);
- self.refocus(conn, mon, 0);
- self.rearrange_monitor(conn, mon);
+ self.refocus(conn, self.clients.len() - 1);
+ self.rearrange(conn);
}
/// Stop managing the given destroyed window
pub fn unmanage_destroyed(&mut self, conn: &Connection<'_>, window: Window) {
- let Some((mon, i)) = self.find_client_pos(window) else {
+ let Some(i) = self.find_client_pos(window) else {
return;
};
- self.mons[mon].clients.remove(i);
+ let focused_mon = self.focused_mon().screen_info;
+
+ self.clients.remove(i);
+
+ self.focused_client = self.focused_client.filter(|f| *f < self.clients.len());
+ if self.focused_client.is_none() {
+ self.focused_client = self
+ .clients
+ .iter()
+ .position(|c| focused_mon.contains(c.x(), c.y()));
+ }
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.
- pub fn refocus(&mut self, conn: &Connection<'_>, mon: usize, i: usize) {
+ pub fn refocus(&mut self, conn: &Connection<'_>, i: ClientIdx) {
self.unfocus(conn);
- if let Some(new) = self.set_focused(mon, i) {
+ if let Some(new) = self.set_focused(i) {
new.set_border(conn, conn.colours.border_focused());
new.sync_hints(conn, true);
buttons::grab(conn, new.window(), true);
@@ -290,6 +252,7 @@ impl ClientState {
});
conn.send_event(new.window(), conn.atoms.wm_take_focus);
}
+ // TODO: Update focused tag
} else {
conn.send_request(&SetInputFocus {
revert_to: InputFocus::PointerRoot,
@@ -313,92 +276,147 @@ impl ClientState {
/// Go to the next or previous window in the current monitor, looping around if needed
pub fn change_focus(&mut self, conn: &Connection<'_>, increase: bool) {
- if self.focused.0 >= self.mons.len() {
+ if self.clients.is_empty() {
return;
}
- let mon = self.focused.0;
- if self.mons[mon].clients.is_empty() {
- return;
- }
+ let curr_focused = self.focused_client.unwrap_or(0);
+ let look_through = self
+ .clients
+ .iter()
+ .enumerate()
+ .filter(|(_, c)| self.focused_mon().screen_info.contains(c.x(), c.y()))
+ .map(|(i, _)| i);
+
+ let new_idx = if increase {
+ look_through
+ .cycle()
+ .skip_while(|i| *i != curr_focused)
+ .nth(1)
+ .unwrap_or(0)
+ } else {
+ look_through
+ .rev()
+ .cycle()
+ .skip_while(|i| *i != curr_focused)
+ .nth(1)
+ .unwrap_or(0)
+ };
- self.refocus(
- conn,
- mon,
- if increase {
- (self.focused.1 + 1) % self.mons[mon].clients.len()
- } else if self.focused.1 > 0 {
- self.focused.1 - 1
- } else {
- self.mons[mon].clients.len() - 1
- },
- );
+ self.refocus(conn, new_idx);
}
/// Toggle whether the client with the given position is floating
- pub fn toggle_floating(&mut self, conn: &Connection<'_>, (mon, pos): (usize, usize)) {
- let c = &mut self.mons[mon].clients[pos];
+ pub fn toggle_floating(&mut self, conn: &Connection<'_>, pos: ClientIdx) {
+ let c = &mut self.clients[pos];
if c.tiled() {
c.set_floating(conn);
} else {
c.set_tiled(conn);
}
- self.rearrange_monitor(conn, mon);
+ self.rearrange_mon(conn, self.client_mon_idx(pos));
}
/// Toggle whether the client with the given position is fullscreen
- pub fn toggle_fullscreen(&mut self, conn: &Connection<'_>, (mon, pos): (usize, usize)) {
- let mon_info = &mut self.mons[mon];
- let c = &mut mon_info.clients[pos];
+ pub fn toggle_fullscreen(&mut self, conn: &Connection<'_>, pos: ClientIdx) {
+ let mon = self.client_mon_idx(pos);
+ let mon_info = &self.mons[mon];
+ let c = &mut self.clients[pos];
if c.tiled() {
c.set_fullscreen(conn, &mon_info.screen_info);
} else {
c.set_tiled(conn);
}
- self.rearrange_monitor(conn, mon);
+ self.rearrange_mon(conn, mon);
}
/// Set the focused tag for the given monitor
- pub fn set_mon_tag_focus(&mut self, conn: &Connection<'_>, mon: usize, tag_focus: TagFocus) {
+ pub fn set_mon_tag_focus(
+ &mut self,
+ conn: &Connection<'_>,
+ mon: MonitorIdx,
+ tag_focus: TagFocus,
+ ) {
// Hide windows from currently focused tag filter
- let curr_focus = self.mons[mon].focused_tag;
- self.mons[mon]
- .clients
+ let mon_info = &self.mons[mon];
+ let curr_focus = mon_info.focused_tag;
+ self.clients
.iter_mut()
.filter(|c| curr_focus.matches(c.tag))
+ .filter(|c| mon_info.screen_info.contains(c.x(), c.y()))
.for_each(|c| c.ensure_unmapped(conn));
debug!("setting tag focus to {:?} on mon {}", tag_focus, mon);
self.mons[mon].focused_tag = tag_focus;
self.unfocus(conn);
- self.rearrange_monitor(conn, mon);
+ self.rearrange_mon(conn, mon);
}
/// Set the tag for the given client
- pub fn set_client_tag(&mut self, conn: &Connection<'_>, (mon, pos): (usize, usize), tag: Tag) {
- if let Some(c) = self.client_mut(mon, pos) {
- if c.tag == tag {
- return;
+ pub fn set_client_tag(&mut self, conn: &Connection<'_>, pos: ClientIdx, tag: Tag) {
+ let c = self.client_mut(pos);
+ if c.tag == tag {
+ return;
+ }
+
+ debug!("moving client with window {:?} to tag {}", c.window(), tag);
+ c.tag = tag;
+ c.ensure_unmapped(conn);
+ self.unfocus(conn);
+ self.rearrange_mon(conn, self.client_mon_idx(pos));
+ }
+
+ /// Update the recorded monitors and monitor sizes, retiling if necessary.
+ 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.mons.len() {
+ 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.mons.len() > 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)?;
- debug!("moving client with window {:?} to tag {}", c.window(), tag);
- c.tag = tag;
- c.ensure_unmapped(conn);
- self.unfocus(conn);
- self.rearrange_monitor(conn, mon);
+ dirty |= self.set_monitor_geometry(
+ 0,
+ MonitorGeometry {
+ x_org: 0,
+ y_org: 0,
+ width: screen.width_in_pixels(),
+ height: screen.height_in_pixels(),
+ },
+ );
}
- }
- /// Get the amount of monitors this state is currently aware of
- pub fn monitor_count(&self) -> usize {
- self.mons.len()
+ if dirty {
+ self.rearrange(conn);
+ }
+ Ok(())
}
- /// Set the new amount of screens, without unmanaging any clients.
+ /// Set the new amount of screens
fn truncate_screens(&mut self, new_size: usize) {
- // HACK: working around double borrow stuff
match new_size.cmp(&self.mons.len()) {
Ordering::Greater => {
for _ in 0..new_size - self.mons.len() {
@@ -406,11 +424,7 @@ impl ClientState {
}
}
Ordering::Less => {
- 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);
+ self.mons.drain(new_size - self.mons.len()..self.mons.len());
}
Ordering::Equal => (),
}
@@ -418,7 +432,7 @@ impl ClientState {
/// 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 {
+ fn set_monitor_geometry(&mut self, i: MonitorIdx, info: MonitorGeometry) -> bool {
while i >= self.mons.len() {
self.mons.push(MonitorInfo::default());
}
@@ -430,39 +444,28 @@ impl ClientState {
/// 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])
+ let i = self.find_client_pos(window)?;
+ Some(&mut self.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));
- }
- }
+ /// Find the position of the client with the given window, returning an index
+ pub fn find_client_pos(&self, window: Window) -> Option<ClientIdx> {
+ self.clients.iter().position(|c| c.window() == window)
+ }
- None
+ /// Get the index of the currently focused client, if it exists
+ pub const fn focused_pos(&self) -> Option<ClientIdx> {
+ self.focused_client
}
/// 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 the position of the currently focused client, if it exists
- pub fn focused_pos(&self) -> Option<(usize, usize)> {
- let (mon, pos) = self.focused;
- if mon < self.mons.len() && pos < self.mons[mon].clients.len() {
- Some((mon, pos))
- } else {
- None
- }
+ self.focused_client.map(|i| self.client(i))
}
/// 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)
+ self.focused_client.map(|i| self.client_mut(i))
}
/// Get the position of the currently focused client. This position may be invalid.
@@ -470,66 +473,64 @@ impl ClientState {
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() {
+ /// Set the currently focused client, returning a mutable reference to the client.
+ pub fn set_focused(&mut self, mut i: ClientIdx) -> Option<&mut Client> {
+ if self.clients.is_empty() {
return None;
}
- if i >= self.mons[mon].clients.len() {
- i = self.mons[mon].clients.len() - 1;
+ if i >= self.clients.len() {
+ i = self.clients.len() - 1;
}
- self.focused = (mon, i);
- Some(&mut self.mons[mon].clients[i])
+ self.focused_client = Some(i);
+ Some(&mut self.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 reference to the client at the given index
+ pub fn client(&self, i: ClientIdx) -> &Client {
+ &self.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
- }
+ /// Get a mutable reference to the client at the given index
+ pub fn client_mut(&mut self, i: ClientIdx) -> &mut Client {
+ &mut self.clients[i]
}
/// Rearrange all clients, reconfiguring them as needed.
pub fn rearrange(&mut self, conn: &Connection<'_>) {
- for mon in 0..self.monitor_count() {
- self.rearrange_monitor(conn, mon);
+ for mon in 0..self.mons.len() {
+ self.rearrange_mon(conn, mon);
}
}
/// Rearrange a specific monitor
- fn rearrange_monitor(&mut self, conn: &Connection<'_>, mon: usize) {
- (self.arrange)(&mut self.mons[mon], conn);
+ pub fn rearrange_mon(&mut self, conn: &Connection<'_>, mon: MonitorIdx) {
+ (self.arrange)(conn, &mut self.clients, &self.mons[mon]);
+ }
+
+ /// Get the index of the currently focused monitor
+ pub fn focused_mon_idx(&self) -> MonitorIdx {
+ self.focused_client.map_or(0, |i| self.client_mon_idx(i))
+ }
+
+ /// Get the info for the currently focused monitor
+ pub fn focused_mon(&self) -> &MonitorInfo {
+ &self.mons[self.focused_mon_idx()]
}
- /// Get the currently focused monitor
- pub const fn focused_mon(&self) -> usize {
- self.focused.0
+ /// Get the monitor info for the client with the given index
+ pub fn client_mon_idx(&self, pos: ClientIdx) -> MonitorIdx {
+ let c = self.client(pos);
+ self.mons
+ .iter()
+ .position(|m| m.screen_info.contains(c.x(), c.y()))
+ .unwrap_or_else(|| self.mons.len() - 1)
}
- /// Get the geometry of the monitor with the given index
- pub fn mon_geometry(&self, mon: usize) -> MonitorGeometry {
- self.mons[mon].screen_info
+ /// Get the monitor info for the client with the given index
+ pub fn client_mon(&self, pos: ClientIdx) -> &MonitorInfo {
+ &self.mons[self.client_mon_idx(pos)]
}
}
@@ -537,8 +538,9 @@ impl Default for ClientState {
fn default() -> Self {
Self {
arrange: &tile::tile,
- focused: (0, 0),
mons: vec![],
+ clients: vec![],
+ focused_client: None,
}
}
}
@@ -546,8 +548,9 @@ impl Default for ClientState {
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("focused_client", &self.focused_client)
.field("mons", &self.mons)
+ .field("clients", &self.clients)
.finish_non_exhaustive()
}
}