summaryrefslogtreecommitdiff
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
parent266d39668bcee886cc7fecdb0f2c7b0b3302b20e (diff)
track focus and draw borders
-rw-r--r--src/clients.rs178
-rw-r--r--src/colours.rs44
-rw-r--r--src/focus.rs66
-rw-r--r--src/main.rs13
4 files changed, 254 insertions, 47 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 {
diff --git a/src/colours.rs b/src/colours.rs
new file mode 100644
index 0000000..6f862c3
--- /dev/null
+++ b/src/colours.rs
@@ -0,0 +1,44 @@
+use crate::error::*;
+use xcb::{
+ x::{AllocColor, Colormap},
+ Connection,
+};
+
+pub struct Colours {
+ cmap: Colormap,
+ border_normal: u32,
+ border_focused: u32,
+}
+
+impl Colours {
+ pub fn new_with(conn: &Connection, cmap: Colormap) -> Result<Self> {
+ let (border_normal, border_focused) = (
+ conn.wait_for_reply(conn.send_request(&AllocColor {
+ cmap,
+ red: 0,
+ green: 0,
+ blue: 0,
+ }))?,
+ conn.wait_for_reply(conn.send_request(&AllocColor {
+ cmap,
+ red: u16::MAX,
+ green: 0,
+ blue: u16::MAX,
+ }))?,
+ );
+
+ Ok(Self {
+ cmap,
+ border_normal: border_normal.pixel(),
+ border_focused: border_focused.pixel(),
+ })
+ }
+
+ pub fn border_normal(&self) -> u32 {
+ self.border_normal
+ }
+
+ pub fn border_focused(&self) -> u32 {
+ self.border_focused
+ }
+}
diff --git a/src/focus.rs b/src/focus.rs
index ea4f021..a237e24 100644
--- a/src/focus.rs
+++ b/src/focus.rs
@@ -1,20 +1,68 @@
-use xcb::x::{EnterNotifyEvent, FocusInEvent, MotionNotifyEvent};
+use xcb::x::{
+ self, ChangeWindowAttributes, Cw, EnterNotifyEvent, FocusInEvent, InputFocus, NotifyDetail,
+ NotifyMode, SetInputFocus, Window,
+};
-use crate::{error::*, WM};
+use crate::{clients::Client, error::*, WM};
impl WM<'_> {
- pub(crate) fn handle_enter_notify(&self, _e: EnterNotifyEvent) -> Result<()> {
- // todo!()
+ pub(crate) fn handle_enter_notify(&mut self, e: EnterNotifyEvent) -> Result<()> {
+ if (e.mode() != NotifyMode::Normal || e.detail() == NotifyDetail::Inferior)
+ && e.event() != self.root
+ {
+ return Ok(());
+ }
+
+ self.focus_window(e.event());
+ self.conn.flush()?;
+
Ok(())
}
- pub(crate) fn handle_focus_in(&self, _e: FocusInEvent) -> Result<()> {
- // todo!()
+ pub(crate) fn handle_focus_in(&mut self, e: FocusInEvent) -> Result<()> {
+ if self
+ .clients
+ .focused_mut()
+ .map(|c| c.window() != e.event())
+ .unwrap_or(true)
+ {
+ self.focus_window(e.event());
+ }
Ok(())
}
- pub(crate) fn handle_motion_notify(&self, _e: MotionNotifyEvent) -> Result<()> {
- // todo!()
- Ok(())
+ /// Attempt to focus the given window, falling back to the root if the window isn't our client.
+ /// This function sends multiple requests without checking them, so conn.flush() should be called after.
+ pub fn focus_window(&mut self, window: Window) {
+ if let Some((mon, i)) = self.clients.find_client_pos(window) {
+ self.refocus(mon, i);
+ } else {
+ self.conn.send_request(&SetInputFocus {
+ revert_to: InputFocus::PointerRoot,
+ focus: window,
+ time: x::CURRENT_TIME,
+ });
+ // TODO: XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
+ }
+ }
+
+ /// Refocus on the client with the given co-ordinates, setting the X11 properties as required.
+ /// This function sends multiple requests without checking them, so conn.flush() should be called after.
+ pub fn refocus(&mut self, mon: usize, i: usize) {
+ self.unfocus();
+ if let Some(new) = self.clients.set_focused(mon, i) {
+ new.set_border(self.conn, self.colours.border_focused());
+ // TODO: reset urgent flag
+ // TODO: something to do with grabbuttons
+ // TODO: set input focus
+ // TODO: set active window
+ // TODO: send wmtakefocus event
+ }
+ }
+
+ pub fn unfocus(&mut self) {
+ if let Some(old) = self.clients.focused_mut() {
+ old.set_border(self.conn, self.colours.border_normal());
+ }
}
}
diff --git a/src/main.rs b/src/main.rs
index 6559ad7..e5ec003 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
use atoms::InternedAtoms;
use clients::ClientState;
+use colours::Colours;
use cursors::Cursors;
use error::*;
use keys::KeyboardInfo;
@@ -10,6 +11,7 @@ use xcb::{
mod atoms;
mod clients;
+mod colours;
mod cursors;
mod error;
mod focus;
@@ -42,6 +44,9 @@ struct WM<'a> {
/// WM client state
clients: ClientState,
+ /// Cached colours,
+ colours: Colours,
+
/// Cached cursors
cursors: Cursors,
@@ -74,6 +79,7 @@ impl WM<'_> {
.map_err(|_| Error::OtherWMRunning)?;
Ok(WM {
+ colours: Colours::new_with(conn, screen.default_colormap())?,
atoms: InternedAtoms::new_with(conn)?,
cursors: Cursors::new_with(conn)?,
keyboard_state: KeyboardInfo::new_with(conn)?,
@@ -95,9 +101,8 @@ impl WM<'_> {
x::EventMask::SUBSTRUCTURE_REDIRECT
| x::EventMask::SUBSTRUCTURE_NOTIFY
| x::EventMask::BUTTON_PRESS
- | x::EventMask::POINTER_MOTION
| x::EventMask::ENTER_WINDOW
- | x::EventMask::LEAVE_WINDOW
+ | x::EventMask::FOCUS_CHANGE
| x::EventMask::STRUCTURE_NOTIFY
| x::EventMask::PROPERTY_CHANGE,
),
@@ -133,7 +138,6 @@ impl WM<'_> {
// See focus.rs
Event::X(x::Event::EnterNotify(e)) => self.handle_enter_notify(e)?,
Event::X(x::Event::FocusIn(e)) => self.handle_focus_in(e)?,
- Event::X(x::Event::MotionNotify(e)) => self.handle_motion_notify(e)?,
// See below
Event::X(x::Event::PropertyNotify(e)) => self.handle_property_notify(e)?,
@@ -144,7 +148,8 @@ impl WM<'_> {
/// Handle a property notify event, by doing *todo*
fn handle_property_notify(&self, _e: PropertyNotifyEvent) -> Result<()> {
- todo!()
+ println!("TODO: handle_property_notify");
+ Ok(())
}
}