summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-06 14:39:55 +0100
committertcmal <me@aria.rip>2024-08-13 17:07:19 +0100
commit89b5f3f87c8022f2d32c422c3516fca2f1538d69 (patch)
tree231457b673cafbfdaea32a8f6a9d8c0aba08a7d3
parent5359062bf58a5ff57dfa48492db57f9340ecfbee (diff)
Add tags/workspaces
-rw-r--r--src/clients/client.rs19
-rw-r--r--src/clients/mod.rs70
-rw-r--r--src/clients/monitors.rs39
-rw-r--r--src/config.rs24
-rw-r--r--src/keys.rs4
-rw-r--r--src/main.rs3
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<Client>,
+ /// 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<Item = &mut Client> {
- // 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!();
};