diff options
Diffstat (limited to 'src/clients.rs')
-rw-r--r-- | src/clients.rs | 178 |
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 { |