oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git

Finished killing windows dymanically and correctly

Commit
b5923a9d80843926e1e90c170cbd916d2ebd95e2
Parent
7ed4196
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-09-29 03:07:35

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(())
+    }
 }