From 89b5f3f87c8022f2d32c422c3516fca2f1538d69 Mon Sep 17 00:00:00 2001 From: tcmal Date: Tue, 6 Aug 2024 14:39:55 +0100 Subject: Add tags/workspaces --- src/clients/client.rs | 19 +++++++++++--- src/clients/mod.rs | 70 +++++++++++++++++++++++++++++++++---------------- src/clients/monitors.rs | 39 ++++++++++++++++++++++++--- src/config.rs | 24 ++++++++++++++++- src/keys.rs | 4 +-- src/main.rs | 3 ++- 6 files changed, 126 insertions(+), 33 deletions(-) diff --git a/src/clients/client.rs b/src/clients/client.rs index 6ab664b..c8fca6c 100644 --- a/src/clients/client.rs +++ b/src/clients/client.rs @@ -2,14 +2,14 @@ use xcb::{ x::{ self, ChangeProperty, ChangeWindowAttributes, ConfigWindow, ConfigWindowMask, ConfigureNotifyEvent, ConfigureWindow, Cw, EventMask, MapWindow, PropMode, SendEvent, - SendEventDest, StackMode, Window, + SendEventDest, StackMode, UnmapWindow, Window, }, VoidCookieChecked, Xid, }; use crate::{config::BORDER_WIDTH, conn_info::Connection}; -use super::{hints, MonitorGeometry}; +use super::{hints, MonitorGeometry, Tag}; /// Information about a single client / window #[derive(Debug)] @@ -28,6 +28,8 @@ pub struct Client { urgent: bool, never_focus: bool, layout_mode: LayoutMode, + + pub tag: Tag, } /// How this window's geometry is determined, @@ -44,7 +46,7 @@ pub enum LayoutMode { } impl Client { - pub const fn new(window: Window) -> Self { + pub const fn new(window: Window, tag: Tag) -> Self { Self { window, x: 0, @@ -56,6 +58,7 @@ impl Client { urgent: false, never_focus: false, layout_mode: LayoutMode::Tiled, + tag, } } @@ -226,6 +229,16 @@ impl Client { }); } + /// Ensure this client is currently unmapped + pub fn ensure_unmapped(&mut self, conn: &Connection<'_>) { + if self.mapped { + conn.send_request(&UnmapWindow { + window: self.window, + }); + self.mapped = false; + } + } + /// Ensure this client is currently mapped / visible pub fn ensure_mapped(&mut self, conn: &Connection<'_>) { if !self.mapped { diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 7fdb739..1abab20 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -5,6 +5,7 @@ use std::cmp::{min, Ordering}; use crate::{ config::BORDER_WIDTH, conn_info::Connection, + debug, error::{Error, Result}, WM, }; @@ -15,7 +16,7 @@ use xcb::{ EventMask, GetGeometry, GetWindowAttributes, InputFocus, MapRequestEvent, PropMode, SetInputFocus, UnmapNotifyEvent, Window, }, - xinerama, BaseEvent, Extension, + xinerama, Extension, }; pub use client::*; @@ -33,6 +34,9 @@ mod monitors; mod tile; +/// The tag a client has, similar to a workspace in most WMs. +pub type Tag = u8; + 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<()> { @@ -83,7 +87,7 @@ impl WM<'_> { /// Removing destroyed windows from the client list and rearrange. pub(crate) fn handle_destroy_notify(&mut self, e: &DestroyNotifyEvent) { - self.clients.unmanage(&self.conn, e.window(), true); + self.clients.unmanage(&self.conn, e.window()); } /// Map a window, starting to manage it if needed. @@ -108,16 +112,12 @@ impl WM<'_> { Ok(()) } - /// When a window is unmapped, either stop managing it or update its state. + /// When a window is unmapped, update its state. pub(crate) fn handle_unmap_notify(&mut self, e: &UnmapNotifyEvent) { if let Some(c) = self.clients.find_client_mut(e.window()) { - if e.is_from_send_event() { - let cookie = c.set_withdrawn(&self.conn, true); - // The above may fail if the window has already been destroyed - just discard the error here. - let _ = self.conn.check_request(cookie); - } else { - self.clients.unmanage(&self.conn, e.window(), false); - } + let cookie = c.set_withdrawn(&self.conn, true); + // The above may fail if the window has already been destroyed - just discard the error here. + let _ = self.conn.check_request(cookie); } } } @@ -204,7 +204,8 @@ impl ClientState { // 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)); + let tag = self.mons[mon].focused_tag.create_tag(); + self.mons[mon].clients.insert(0, Client::new(window, tag)); let mon_geom @ MonitorGeometry { width: mon_width, @@ -257,21 +258,13 @@ impl ClientState { self.rearrange_monitor(conn, mon); } - /// Stop managing the given window, and also unset attributes unless `already_destroyed` is true. - pub fn unmanage(&mut self, conn: &Connection<'_>, window: Window, already_destroyed: bool) { + /// Stop managing the given window + pub fn unmanage(&mut self, conn: &Connection<'_>, window: Window) { 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::NO_EVENT); - // TODO: Ungrab button - let cookie = c.set_withdrawn(conn, true); + self.mons[mon].clients.remove(i); - // If any of the above requests fail, it's just a race condition and the window is already destroyed, so discard the error here. - let _ = conn.check_request(cookie); - } self.rearrange(conn); } @@ -366,6 +359,37 @@ impl ClientState { self.rearrange_monitor(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) { + // Hide windows from currently focused tag filter + let curr_focus = self.mons[mon].focused_tag; + self.mons[mon] + .clients + .iter_mut() + .filter(|c| curr_focus.matches(c.tag)) + .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); + } + + /// 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; + } + + 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); + } + } + /// Get the amount of monitors this state is currently aware of pub fn monitor_count(&self) -> usize { self.mons.len() @@ -498,7 +522,7 @@ impl ClientState { } /// Get the currently focused monitor - const fn focused_mon(&self) -> usize { + pub const fn focused_mon(&self) -> usize { self.focused.0 } diff --git a/src/clients/monitors.rs b/src/clients/monitors.rs index 6790632..16348ea 100644 --- a/src/clients/monitors.rs +++ b/src/clients/monitors.rs @@ -1,6 +1,6 @@ use xcb::xinerama::ScreenInfo; -use super::Client; +use super::{Client, Tag}; /// Info stored for each monitor #[derive(Debug)] @@ -8,15 +8,47 @@ pub struct MonitorInfo { /// Clients attached to that monitor pub clients: Vec, + /// How clients should be filtered by tag + pub focused_tag: TagFocus, + /// The monitor's geometry pub screen_info: MonitorGeometry, } +/// How clients are currently being filtered by tag +#[derive(Debug, Clone, Copy)] +pub enum TagFocus { + /// Only show clients with the given tag + Tag(Tag), + + /// Show all clients + All, +} +impl TagFocus { + /// Check if a client with the given tag should be displayed when using this filter + pub const fn matches(&self, tag: Tag) -> bool { + match self { + Self::Tag(x) => *x == tag, + Self::All => true, + } + } + + /// Get the tag that new clients should be assigned when using this filter + pub const fn create_tag(&self) -> Tag { + match self { + Self::Tag(x) => *x, + Self::All => 1, + } + } +} + impl MonitorInfo { /// Iterate over all tiled clients, returning a mutable reference to each. pub fn clients_tiled_mut(&mut self) -> impl Iterator { - // TODO: tag filtering - self.clients.iter_mut().filter(|c| c.tiled()) + self.clients + .iter_mut() + .filter(|c| c.tiled()) + .filter(|c| self.focused_tag.matches(c.tag)) } } @@ -30,6 +62,7 @@ impl Default for MonitorInfo { width: 0, height: 0, }, + focused_tag: TagFocus::Tag(1), } } } diff --git a/src/config.rs b/src/config.rs index 9cbfc40..3182ba7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,8 +6,11 @@ use xkeysym::Keysym; use crate::{ bind, + clients::TagFocus, conn_info::Colour, - helpers::{focus_next, focus_prev, spawn, toggle_floating, toggle_fullscreen}, + helpers::{ + focus_next, focus_prev, set_tag, set_tag_focus, spawn, toggle_floating, toggle_fullscreen, + }, keys::{Keybind, Keybinds}, }; @@ -34,6 +37,25 @@ pub const KEYBINDS: Keybinds = Keybinds(&[ bind!(MAIN_MODIFIER , k -> &focus_prev), bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , space -> &toggle_floating), bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , f -> &toggle_fullscreen), + bind!(MAIN_MODIFIER , _1 -> &|wm| set_tag_focus(wm, TagFocus::Tag(1))), + bind!(MAIN_MODIFIER , _2 -> &|wm| set_tag_focus(wm, TagFocus::Tag(2))), + bind!(MAIN_MODIFIER , _3 -> &|wm| set_tag_focus(wm, TagFocus::Tag(3))), + bind!(MAIN_MODIFIER , _4 -> &|wm| set_tag_focus(wm, TagFocus::Tag(4))), + bind!(MAIN_MODIFIER , _5 -> &|wm| set_tag_focus(wm, TagFocus::Tag(5))), + bind!(MAIN_MODIFIER , _6 -> &|wm| set_tag_focus(wm, TagFocus::Tag(6))), + bind!(MAIN_MODIFIER , _7 -> &|wm| set_tag_focus(wm, TagFocus::Tag(7))), + bind!(MAIN_MODIFIER , _8 -> &|wm| set_tag_focus(wm, TagFocus::Tag(8))), + bind!(MAIN_MODIFIER , _9 -> &|wm| set_tag_focus(wm, TagFocus::Tag(9))), + bind!(MAIN_MODIFIER , _0 -> &|wm| set_tag_focus(wm, TagFocus::All)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _1 -> &|wm| set_tag(wm, 1)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _2 -> &|wm| set_tag(wm, 2)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _3 -> &|wm| set_tag(wm, 3)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _4 -> &|wm| set_tag(wm, 4)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _5 -> &|wm| set_tag(wm, 5)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _6 -> &|wm| set_tag(wm, 6)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _7 -> &|wm| set_tag(wm, 7)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _8 -> &|wm| set_tag(wm, 8)), + bind!(MAIN_MODIFIER.union(ModMask::SHIFT) , _9 -> &|wm| set_tag(wm, 9)), // { MODKEY, XK_j, focusstack, {.i = +1 } }, // { MODKEY, XK_k, focusstack, {.i = -1 } }, // { MODKEY, XK_i, incnmaster, {.i = +1 } }, diff --git a/src/keys.rs b/src/keys.rs index aa84da6..33192e2 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -95,10 +95,10 @@ impl Keybinds { /// Attempt to run the action for the matching keybind, if it exists. pub fn dispatch(&self, wm: &mut WM<'_>, key: Keysym, modifiers: ModMask) { - debug!("received {key:?}"); + debug!("received {modifiers:?} {key:?}"); if let Some(bind) = self .binds() - .find(|b| b.key == key && modifiers.contains(b.modifiers)) + .find(|b| b.key == key && modifiers == b.modifiers) { debug!("found action"); (bind.action)(wm); diff --git a/src/main.rs b/src/main.rs index 122f735..02f83c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ use xcb::{ Connection as RawConnection, Event, Extension, Xid, }; +pub mod buttons; pub mod clients; pub mod config; pub mod conn_info; @@ -143,7 +144,7 @@ impl<'a> WM<'a> { return; } - if dbg!(e.r#type()) == dbg!(self.conn.atoms.net_wm_state) { + if e.r#type() == self.conn.atoms.net_wm_state { let x::ClientMessageData::Data32(data) = e.data() else { unreachable!(); }; -- cgit v1.2.3