Diff
diff --git a/readme.org b/readme.org
index 1f53c3d..8c99d73 100644
--- a/readme.org
+++ b/readme.org
@@ -2,13 +2,84 @@
#+STARTUP: overview
* OXWM β DWM but Better (and oxidized)
-A dynamic window manager written in /Rustπ¦/, inspired by dwm but designed to evolve
+A dynamic window manager written in Rust, inspired by dwm but designed to evolve
on its own. Configuration is done in Rust source code, keeping with the suckless
philosophy of *"edit + recompile."*
This project is still in its early stages. Currently, it can claim ownership of
an X display and log incoming events.
+* Project Structure
+
+** src/
+*** main.rs
+- main() :: Creates WindowManager and calls .run()
+
+*** window_manager.rs (CORE)
+Handles all X11 events and window management
+
+**** struct WindowManager
+- connection :: X11 connection
+- windows :: Vec<Window> (all managed windows)
+- focused_window :: Option<Window>
+- layout :: Box<dyn Layout>
+
+**** Methods
+- new() :: Initialize WM, grab root window
+- run() :: Main event loop
+- handle_event() :: Routes X11 events
+ - MapRequest β add window, apply layout
+ - UnmapNotify β remove window
+ - DestroyNotify β remove window
+ - KeyPress β get action, handle it
+- handle_key_action() :: Execute keyboard actions
+- remove_window() :: Remove from Vec, reapply layout
+- set_focus() :: Focus window, update visuals
+- cycle_focus() :: Move focus to next window
+- update_focus_visuals() :: Set border colors
+- apply_layout() :: Position all windows
+
+*** keyboard/
+**** mod.rs
+- Re-exports public interface
+
+**** keycodes.rs
+- Key constants (Q, J, RETURN, etc)
+
+**** handlers.rs
+- enum KeyAction :: SpawnTerminal, CloseWindow, CycleWindow, Quit, None
+- setup_keybinds() :: Register keys with X11
+- handle_key_press() :: Parse key β return KeyAction
+
+*** layout/
+**** mod.rs
+- Layout trait definition
+
+**** tiling.rs
+- TilingLayout :: Implementation
+ - arrange() :: Calculate window positions
+
+* Event Flow
+
+1. X11 event arrives β run() receives it
+2. handle_event() matches event type
+3. For KeyPress:
+ - keyboard::handle_key_press() β KeyAction
+ - handle_key_action() executes action
+4. For Map/Unmap:
+ - Modify windows Vec
+ - apply_layout() repositions everything
+ - update_focus_visuals() redraws borders
+
+* Key Bindings
+
+| Binding | Action |
+|---------------+----------------------|
+| Alt+Return | Spawn terminal |
+| Alt+J | Cycle focus |
+| Alt+Shift+Q | Close focused window |
+| Alt+Shift+Q | Quit WM |
+
* β Installation β Running with Nix Flakes
You can set up a reproducible development environment with Rust, Cargo, Xephyr, xterm, and
just by using the flake.
@@ -37,19 +108,19 @@ This should open a new Xephyr window. oxwm will attach to it and log X11
events in your host terminal. Clients like xterm/xclock will appear inside Xephyr.
* OXWM Todo List:
-** Reorganization Tasks
-- [ ] Move keyboard module to folder structure:
- - [ ] Create =src/keyboard/mod.rs= with re-exports
- - [ ] Move constants to =src/keyboard/keycodes.rs=
- - [ ] Move key handlers to =src/keyboard/handlers.rs=
- - [ ] Update imports in main.rs and window_manager.rs
+** TODO Reorganization Tasks [1/2]
+- [X] Move keyboard module to folder structure:
+ - [X] Create =src/keyboard/mod.rs= with re-exports
+ - [X] Move constants to =src/keyboard/keycodes.rs=
+ - [X] Move key handlers to =src/keyboard/handlers.rs=
+ - [X] Update imports in main.rs and window_manager.rs
- [ ] Create =src/config.rs= in root directory for future configuration system
-** Core Window Management
-- [ ] Fix layout after program is closed (handle UnmapNotify events)
- - [ ] Add UnmapNotify to event handling
- - [ ] Remove closed windows from windows vector
- - [ ] Re-apply layout after window removal
+** TODO Core Window Management [1/2]
+- [X] Fix layout after program is closed (handle UnmapNotify events)
+ - [X] Add UnmapNotify to event handling
+ - [X] Remove closed windows from windows vector
+ - [X] Re-apply layout after window removal
- [ ] Add keybind to swap focus between windows
- [ ] Track focused window in WindowManager struct
- [ ] Implement focus cycling logic
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 9684171..ea505ff 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -3,15 +3,25 @@ use anyhow::Result;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
+#[derive(Debug)]
+pub enum KeyAction {
+ SpawnTerminal,
+ CloseWindow,
+ CycleWindow,
+ Quit,
+ None,
+}
+
pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()> {
connection.grab_key(
false,
root,
- ModMask::M4.into(),
+ ModMask::M1.into(),
keycodes::RETURN,
GrabMode::ASYNC,
GrabMode::ASYNC,
)?;
+
connection.grab_key(
false,
root,
@@ -20,33 +30,38 @@ pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()>
GrabMode::ASYNC,
GrabMode::ASYNC,
)?;
+
connection.grab_key(
false,
root,
- ModMask::M4.into(),
+ ModMask::M1.into(),
keycodes::Q,
GrabMode::ASYNC,
GrabMode::ASYNC,
)?;
+
+ connection.grab_key(
+ false,
+ root,
+ ModMask::M1.into(),
+ keycodes::J,
+ GrabMode::ASYNC,
+ GrabMode::ASYNC,
+ )?;
+
Ok(())
}
-pub fn handle_key_press(connection: &impl Connection, event: KeyPressEvent) -> Result<()> {
+pub fn handle_key_press(event: KeyPressEvent) -> Result<KeyAction> {
println!("KeyPress: detail={}, state={:?}", event.detail, event.state);
- match (event.detail, event.state) {
- (keycodes::RETURN, state) if state.contains(KeyButMask::MOD1) => {
- println!("Spawning terminal");
- std::process::Command::new("xterm").spawn()?;
- }
+ let action = match (event.detail, event.state) {
+ (keycodes::RETURN, state) if state.contains(KeyButMask::MOD1) => KeyAction::SpawnTerminal,
(keycodes::Q, state) if state.contains(KeyButMask::MOD1 | KeyButMask::SHIFT) => {
- println!("Closing focused window");
- let focus_reply = connection.get_input_focus()?.reply()?;
- if focus_reply.focus != x11rb::NONE && focus_reply.focus != event.root {
- connection.kill_client(focus_reply.focus)?;
- connection.flush()?;
- }
+ KeyAction::CloseWindow
}
- _ => {}
- }
- Ok(())
+ (keycodes::Q, state) if state.contains(KeyButMask::MOD1) => KeyAction::Quit,
+ (keycodes::J, state) if state.contains(KeyButMask::MOD1) => KeyAction::CycleWindow,
+ _ => KeyAction::None,
+ };
+ Ok(action)
}
diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs
index 6a2c04c..79ea647 100644
--- a/src/keyboard/mod.rs
+++ b/src/keyboard/mod.rs
@@ -3,5 +3,5 @@ pub mod handlers;
pub mod keycodes;
// Re-export commonly used items for convenience
-pub use handlers::{handle_key_press, setup_keybinds};
+pub use handlers::{KeyAction, handle_key_press, setup_keybinds}; // Add KeyAction here
pub use keycodes::*;
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 329c1d2..7b926e2 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -1,5 +1,4 @@
-use crate::keyboard;
-// use crate::keys;
+use crate::keyboard::{self, KeyAction}; // Add KeyAction to the import
use crate::layout::Layout;
use crate::layout::tiling::TilingLayout;
@@ -16,6 +15,7 @@ pub struct WindowManager {
root: Window,
screen: Screen,
windows: Vec<Window>,
+ pub focused_window: Option<Window>,
layout: Box<dyn Layout>,
}
@@ -43,6 +43,7 @@ impl WindowManager {
root,
screen,
windows: Vec::new(),
+ focused_window: None,
layout: Box::new(TilingLayout),
});
}
@@ -59,28 +60,116 @@ impl WindowManager {
}
}
+ fn handle_key_action(&mut self, action: keyboard::KeyAction) -> Result<()> {
+ match action {
+ keyboard::KeyAction::SpawnTerminal => {
+ println!("Spawning terminal");
+ std::process::Command::new("xclock").spawn()?;
+ }
+ keyboard::KeyAction::CloseWindow => {
+ println!("Closing focused window");
+ if let Some(focused) = self.focused_window {
+ match self.connection.kill_client(focused) {
+ Ok(_) => {
+ self.connection.flush()?;
+ println!("Killed window {}", focused);
+ }
+ Err(e) => {
+ println!("Failed to kill window {}: {}", focused, e);
+ }
+ }
+ }
+ }
+ keyboard::KeyAction::CycleWindow => {
+ println!("Cycling focus");
+ self.cycle_focus()?;
+ }
+ keyboard::KeyAction::Quit => {
+ println!("Quitting window manager");
+ std::process::exit(0);
+ }
+ keyboard::KeyAction::None => {
+ //no-op
+ }
+ }
+ Ok(())
+ }
+ pub fn cycle_focus(&mut self) -> Result<()> {
+ if self.windows.is_empty() {
+ return Ok(());
+ }
+
+ let next_window = if let Some(current) = self.focused_window {
+ if let Some(current_index) = self.windows.iter().position(|&w| w == current) {
+ let next_index = (current_index + 1) % self.windows.len();
+ self.windows[next_index]
+ } else {
+ self.windows[0]
+ }
+ } else {
+ self.windows[0]
+ };
+
+ self.set_focus(Some(next_window))?;
+ Ok(())
+ }
+
+ pub fn set_focus(&mut self, window: Option<Window>) -> Result<()> {
+ self.focused_window = window;
+
+ if let Some(win) = window {
+ self.connection
+ .set_input_focus(InputFocus::POINTER_ROOT, win, x11rb::CURRENT_TIME)?;
+ }
+
+ self.update_focus_visuals()?;
+ Ok(())
+ }
+
+ fn update_focus_visuals(&self) -> Result<()> {
+ for &window in &self.windows {
+ let border_color = if self.focused_window == Some(window) {
+ 0xff0000
+ } else {
+ 0x888888
+ };
+
+ self.connection.change_window_attributes(
+ window,
+ &ChangeWindowAttributesAux::new().border_pixel(border_color),
+ )?;
+ }
+ Ok(())
+ }
fn handle_event(&mut self, event: Event) -> Result<()> {
match event {
Event::MapRequest(event) => {
self.connection.map_window(event.window)?;
self.windows.push(event.window);
self.apply_layout()?;
- self.connection.set_input_focus(
- InputFocus::POINTER_ROOT,
- event.window,
- x11rb::CURRENT_TIME,
- )?;
+ self.set_focus(Some(event.window))?;
self.connection.flush()?;
}
+ Event::UnmapNotify(event) => {
+ if self.windows.contains(&event.window) {
+ println!("Window {} unmapped, removing from layout", event.window);
+ self.remove_window(event.window)?;
+ }
+ }
+ Event::DestroyNotify(event) => {
+ if self.windows.contains(&event.window) {
+ println!("Window {} destroyed, removing from layout", event.window);
+ self.remove_window(event.window)?;
+ }
+ }
Event::KeyPress(event) => {
- println!("KeyPress event received!");
- keyboard::handle_key_press(&self.connection, event)?;
+ let action = keyboard::handle_key_press(event)?; // Remove &self.connection
+ self.handle_key_action(action)?;
}
_ => {}
}
- return Ok(());
+ Ok(())
}
-
fn apply_layout(&self) -> Result<()> {
let screen_width = self.screen.width_in_pixels as u32;
let screen_height = self.screen.height_in_pixels as u32;
@@ -102,4 +191,26 @@ impl WindowManager {
}
return Ok(());
}
+
+ fn remove_window(&mut self, window: Window) -> Result<()> {
+ let initial_count = self.windows.len();
+ self.windows.retain(|&w| w != window);
+
+ if self.windows.len() < initial_count {
+ println!("Removed window {} from management", window);
+
+ if self.focused_window == Some(window) {
+ self.focused_window = if self.windows.is_empty() {
+ None
+ } else {
+ Some(self.windows[self.windows.len() - 1])
+ };
+ }
+
+ self.apply_layout()?;
+ self.connection.flush()?;
+ }
+
+ Ok(())
+ }
}