summaryrefslogtreecommitdiff
path: root/src/clients.rs
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-06-06 21:39:23 +0100
committertcmal <me@aria.rip>2024-06-06 21:39:23 +0100
commit66e3423828892a72e5e525f2dc8d5ad91e634445 (patch)
tree5dfd94005edbde91adc7a18e5b9a81a5c87816ec /src/clients.rs
parent266d39668bcee886cc7fecdb0f2c7b0b3302b20e (diff)
track focus and draw borders
Diffstat (limited to 'src/clients.rs')
-rw-r--r--src/clients.rs178
1 files changed, 144 insertions, 34 deletions
diff --git a/src/clients.rs b/src/clients.rs
index d5bd8a9..75ed38a 100644
--- a/src/clients.rs
+++ b/src/clients.rs
@@ -2,15 +2,17 @@ use std::cmp::min;
use xcb::{
x::{
- ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent, ConfigureWindow,
- DestroyNotifyEvent, EventMask, GetWindowAttributes, MapRequestEvent, MapWindow, SendEvent,
- SendEventDest, UnmapNotifyEvent, UnmapWindow, Window,
+ ChangeWindowAttributes, ConfigWindow, ConfigureNotifyEvent, ConfigureRequestEvent,
+ ConfigureWindow, Cw, DestroyNotifyEvent, Drawable, EventMask, GetGeometry,
+ GetWindowAttributes, MapRequestEvent, MapWindow, SendEvent, SendEventDest,
+ UnmapNotifyEvent, UnmapWindow, Window,
},
xinerama::{self, ScreenInfo},
BaseEvent, Connection, Extension, Xid,
};
use crate::{error::*, WM};
+const BORDER_WIDTH: u16 = 3;
impl WM<'_> {
/// Update the client state's recorded monitors and monitor sizes
@@ -126,8 +128,7 @@ impl WM<'_> {
}
// Start managing, and map window
- self.clients.manage(self.conn, e.window());
- // TODO: clear focus
+ self.manage(self.conn, e.window());
self.conn.flush()?;
@@ -148,6 +149,70 @@ impl WM<'_> {
Ok(())
}
+
+ /// 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.clients.focused_mon();
+
+ let Ok(geom) = conn.wait_for_reply(conn.send_request(&GetGeometry {
+ drawable: Drawable::Window(window),
+ })) else {
+ return; // window stopped existing, so we can't manage it
+ };
+
+ self.unfocus();
+
+ // TODO: inserting at index 0 is why dwm uses linked lists, maybe this can be improved
+ self.clients.mons[mon].clients.insert(
+ 0,
+ Client {
+ window,
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ border_width: BORDER_WIDTH,
+ mapped: false,
+ },
+ );
+
+ // TODO: Clamp window size to monitor
+
+ let c = &mut self.clients.mons[mon].clients[0];
+
+ c.set_geom(
+ conn,
+ geom.x(),
+ geom.y(),
+ geom.width(),
+ geom.height(),
+ BORDER_WIDTH,
+ );
+ c.set_border(conn, self.colours.border_normal());
+ c.ensure_mapped(conn);
+
+ // TODO: updatewindowtype
+ // TODO: updatesizehints
+ // TODO: updatewmhints
+
+ // XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
+ c.select_input(
+ conn,
+ EventMask::ENTER_WINDOW
+ | EventMask::FOCUS_CHANGE
+ | EventMask::PROPERTY_CHANGE
+ | EventMask::STRUCTURE_NOTIFY,
+ );
+ // TODO: grabbuttons
+ // TODO: add to NetClientList
+ // TODO: XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */
+ // TODO: setclientstate(c, NormalState);
+
+ self.refocus(mon, 0);
+ self.clients.rearrange_monitor(conn, mon);
+ }
}
/// Holds state related to the window manager's clients
@@ -156,15 +221,16 @@ 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>,
+
+ focused: (usize, usize),
}
impl Default for ClientState {
fn default() -> Self {
Self {
arrange: &tile,
- selected_monitor: 0,
+ focused: (0, 0),
mons: vec![],
}
}
@@ -173,7 +239,7 @@ 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("selected_monitor", &self.selected_monitor)
+ .field("focused", &self.focused)
.field("mons", &self.mons)
.finish()
}
@@ -251,38 +317,57 @@ impl ClientState {
.find(|c| c.window == window)
}
- /// 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.mons.iter_mut() {
+ /// Find the position of the client with the given window, returning (monitor, index)
+ pub(crate) 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(mon.clients.remove(pos));
+ return Some((pos_mon, pos));
}
}
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;
+ /// 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)
+ }
- // 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,
- },
- );
+ /// 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;
+ }
- self.rearrange_monitor(conn, mon)
+ if mon >= self.mons.len() {
+ mon = self.mons.len() - 1;
+ }
+
+ if self.mons[mon].clients.is_empty() {
+ return None;
+ }
+
+ if i >= self.mons[mon].clients.len() {
+ i = self.mons[mon].clients.len() - 1;
+ }
+
+ self.focused = (mon, i);
+ Some(&mut self.mons[mon].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
+ }
+ }
+
+ /// 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> {
+ let (mon, i) = self.find_client_pos(window)?;
+ Some(self.mons[mon].clients.remove(i))
}
/// Rearrange all clients, reconfiguring them as needed.
@@ -298,6 +383,10 @@ impl ClientState {
fn rearrange_monitor(&mut self, conn: &Connection, mon: usize) {
(self.arrange)(&mut self.mons[mon], conn);
}
+
+ fn focused_mon(&self) -> usize {
+ self.focused.0
+ }
}
/// Information about a single client / window
@@ -369,8 +458,17 @@ impl Client {
});
}
+ /// Set the border of the X11 window to the given value.
+ /// This sends a request but doesn't wait for the response.
+ pub fn set_border(&self, conn: &Connection, colour: u32) {
+ conn.send_request(&ChangeWindowAttributes {
+ window: self.window(),
+ value_list: &[Cw::BorderPixel(colour)],
+ });
+ }
+
/// Ensure this client is currently mapped / visible
- fn ensure_mapped(&mut self, conn: &Connection) {
+ pub fn ensure_mapped(&mut self, conn: &Connection) {
if !self.mapped {
conn.send_request(&MapWindow {
window: self.window,
@@ -380,7 +478,7 @@ impl Client {
}
/// Ensure this client is currently unmapped / invisible
- fn ensure_unmapped(&mut self, conn: &Connection) {
+ pub fn ensure_unmapped(&mut self, conn: &Connection) {
if self.mapped {
conn.send_request(&UnmapWindow {
window: self.window,
@@ -388,6 +486,18 @@ impl Client {
self.mapped = false;
}
}
+
+ /// Get the associated window
+ pub fn window(&self) -> Window {
+ self.window
+ }
+
+ fn select_input(&self, conn: &Connection, event_mask: EventMask) {
+ conn.send_request(&ChangeWindowAttributes {
+ window: self.window(),
+ value_list: &[Cw::EventMask(event_mask)],
+ });
+ }
}
impl Default for MonitorInfo {