oxwm

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

Fixed border issue on focus, changed keymaps to be modular, added j/k functionality to iterate stack

Commit
8c774ace1ea367d7d290895a8df02a2d0706798a
Parent
f198c24
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-09-29 11:18:41

Diff

diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 68b1b29..d4b315e 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -3,117 +3,105 @@ use anyhow::Result;
 use x11rb::connection::Connection;
 use x11rb::protocol::xproto::*;
 
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
 pub enum KeyAction {
-    SpawnTerminal,
-    CloseWindow,
-    CycleWindow,
+    Spawn,
+    KillClient,
+    FocusStack,
     Quit,
     None,
 }
 
-pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()> {
-    connection.grab_key(
-        false,
-        root,
-        ModMask::M1.into(),
-        keycodes::RETURN,
-        GrabMode::ASYNC,
-        GrabMode::ASYNC,
-    )?;
+#[derive(Debug)]
+pub enum Arg {
+    Str(&'static str),
+    Int(i32),
+    None,
+}
 
-    connection.grab_key(
-        false,
-        root,
-        (ModMask::M1 | ModMask::SHIFT).into(),
-        keycodes::Q,
-        GrabMode::ASYNC,
-        GrabMode::ASYNC,
-    )?;
+pub struct Key {
+    pub(crate) modifiers: &'static [KeyButMask],
+    pub(crate) key: Keycode,
+    pub(crate) func: KeyAction,
+    pub(crate) arg: Arg,
+}
 
-    connection.grab_key(
-        false,
-        root,
-        ModMask::M1.into(),
-        keycodes::Q,
-        GrabMode::ASYNC,
-        GrabMode::ASYNC,
-    )?;
+impl Key {
+    pub const fn new(
+        modifiers: &'static [KeyButMask],
+        key: Keycode,
+        func: KeyAction,
+        arg: Arg,
+    ) -> Self {
+        Self {
+            modifiers,
+            key,
+            func,
+            arg,
+        }
+    }
+}
 
-    connection.grab_key(
-        false,
-        root,
-        ModMask::M1.into(),
+const KEYBINDINGS: &[Key] = &[
+    Key::new(
+        &[KeyButMask::MOD1],
+        keycodes::RETURN,
+        KeyAction::Spawn,
+        Arg::Str("xclock"),
+    ),
+    Key::new(
+        &[KeyButMask::MOD1, KeyButMask::SHIFT],
+        keycodes::Q,
+        KeyAction::KillClient,
+        Arg::None,
+    ),
+    Key::new(&[KeyButMask::MOD1], keycodes::Q, KeyAction::Quit, Arg::None),
+    Key::new(
+        &[KeyButMask::MOD1],
         keycodes::J,
-        GrabMode::ASYNC,
-        GrabMode::ASYNC,
-    )?;
+        KeyAction::FocusStack,
+        Arg::Int(1),
+    ),
+    Key::new(
+        &[KeyButMask::MOD1],
+        keycodes::K,
+        KeyAction::FocusStack,
+        Arg::Int(-1),
+    ),
+];
+
+fn modifiers_to_mask(modifiers: &[KeyButMask]) -> u16 {
+    modifiers
+        .iter()
+        .fold(0u16, |acc, &modifier| acc | u16::from(modifier))
+}
+
+pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()> {
+    for keybinding in KEYBINDINGS {
+        let modifier_mask = modifiers_to_mask(keybinding.modifiers);
 
+        connection.grab_key(
+            false,
+            root,
+            modifier_mask.into(),
+            keybinding.key,
+            GrabMode::ASYNC,
+            GrabMode::ASYNC,
+        )?;
+    }
     Ok(())
 }
 
-pub fn handle_key_press(event: KeyPressEvent) -> Result<KeyAction> {
+pub fn handle_key_press(event: KeyPressEvent) -> Result<(KeyAction, &'static Arg)> {
     println!("KeyPress: detail={}, state={:?}", event.detail, event.state);
-    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) => {
-            KeyAction::CloseWindow
+
+    for keybinding in KEYBINDINGS {
+        let modifier_mask = modifiers_to_mask(keybinding.modifiers);
+
+        if event.detail == keybinding.key && event.state == modifier_mask.into() {
+            return Ok((keybinding.func, &keybinding.arg));
         }
-        (keycodes::Q, state) if state.contains(KeyButMask::MOD1) => KeyAction::Quit,
-        (keycodes::J, state) if state.contains(KeyButMask::MOD1) => KeyAction::CycleWindow,
-        _ => KeyAction::None,
-    };
-    Ok(action)
-}
+    }
 
-// pub struct Key {
-//     pub(crate) modifiers: &'static [KeyButMask], // List of modifiers
-//     pub(crate) key: Keycode,
-//     pub(crate) func: KeyAction,
-//     pub(crate) arg: Arg,
-// }
-//
-// impl Key {
-//     pub const fn new(
-//         modifiers: &'static [KeyButMask],
-//         key: Keycode,
-//         func: KeyAction,
-//         arg: Arg,
-//     ) -> Self {
-//         Self {
-//             modifiers,
-//             key,
-//             func,
-//             arg,
-//         }
-//     }
-// }
-//
-// const KEYBINDINGS: &[Key] = &[
-//     Key::new(
-//         &[KeyButMask::MOD1],
-//         keycodes::RETURN,
-//         KeyAction::Spawn,
-//         Arg::Str("xclock"),
-//     ),
-//     Key::new(
-//         &[KeyButMask::MOD1, KeyButMask::SHIFT],
-//         keycodes::Q,
-//         KeyAction::KillClient,
-//         Arg::None,
-//     ),
-//     Key::new(&[KeyButMask::MOD1], keycodes::Q, KeyAction::Quit, Arg::None),
-//     Key::new(
-//         &[KeyButMask::MOD1],
-//         keycodes::J,
-//         KeyAction::FocusStack,
-//         Arg::Int(1),
-//     ),
-//     Key::new(
-//         &[KeyButMask::MOD1],
-//         keycodes::K,
-//         KeyAction::FocusStack,
-//         Arg::Int(-1),
-//     ),
-// ];
-//
+    Ok((KeyAction::None, &Arg::None))
+}
diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs
index 79ea647..7c80204 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::{KeyAction, handle_key_press, setup_keybinds}; // Add KeyAction here
+pub use handlers::{Arg, 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 7b926e2..b344af5 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -1,4 +1,4 @@
-use crate::keyboard::{self, KeyAction}; // Add KeyAction to the import
+use crate::keyboard::{self, Arg, KeyAction};
 use crate::layout::Layout;
 use crate::layout::tiling::TilingLayout;
 
@@ -15,7 +15,7 @@ pub struct WindowManager {
     root: Window,
     screen: Screen,
     windows: Vec<Window>,
-    pub focused_window: Option<Window>,
+    focused_window: Option<Window>,
     layout: Box<dyn Layout>,
 }
 
@@ -60,13 +60,15 @@ impl WindowManager {
         }
     }
 
-    fn handle_key_action(&mut self, action: keyboard::KeyAction) -> Result<()> {
+    fn handle_key_action(&mut self, action: KeyAction, arg: &Arg) -> Result<()> {
         match action {
-            keyboard::KeyAction::SpawnTerminal => {
-                println!("Spawning terminal");
-                std::process::Command::new("xclock").spawn()?;
+            KeyAction::Spawn => {
+                if let Arg::Str(command) = arg {
+                    println!("Spawning: {}", command);
+                    std::process::Command::new(command).spawn()?;
+                }
             }
-            keyboard::KeyAction::CloseWindow => {
+            KeyAction::KillClient => {
                 println!("Closing focused window");
                 if let Some(focused) = self.focused_window {
                     match self.connection.kill_client(focused) {
@@ -80,28 +82,42 @@ impl WindowManager {
                     }
                 }
             }
-            keyboard::KeyAction::CycleWindow => {
-                println!("Cycling focus");
-                self.cycle_focus()?;
+            KeyAction::FocusStack => {
+                if let Arg::Int(direction) = arg {
+                    println!("FocusStack called with direction: {}", direction);
+                    println!("Windows: {:?}", self.windows);
+                    println!("Current focused: {:?}", self.focused_window);
+                    self.cycle_focus(*direction)?;
+                    println!("New focused: {:?}", self.focused_window);
+                }
             }
-            keyboard::KeyAction::Quit => {
+            KeyAction::Quit => {
                 println!("Quitting window manager");
                 std::process::exit(0);
             }
-            keyboard::KeyAction::None => {
+            KeyAction::None => {
                 //no-op
             }
         }
         Ok(())
     }
-    pub fn cycle_focus(&mut self) -> Result<()> {
+
+    pub fn cycle_focus(&mut self, direction: i32) -> 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();
+                let next_index = if direction > 0 {
+                    (current_index + 1) % self.windows.len()
+                } else {
+                    if current_index == 0 {
+                        self.windows.len() - 1
+                    } else {
+                        current_index - 1
+                    }
+                };
                 self.windows[next_index]
             } else {
                 self.windows[0]
@@ -115,11 +131,13 @@ impl WindowManager {
     }
 
     pub fn set_focus(&mut self, window: Option<Window>) -> Result<()> {
+        println!("set_focus called with: {:?}", window);
         self.focused_window = window;
 
         if let Some(win) = window {
             self.connection
                 .set_input_focus(InputFocus::POINTER_ROOT, win, x11rb::CURRENT_TIME)?;
+            self.connection.flush()?;
         }
 
         self.update_focus_visuals()?;
@@ -127,20 +145,30 @@ impl WindowManager {
     }
 
     fn update_focus_visuals(&self) -> Result<()> {
+        println!("Updating focus visuals for {} windows", self.windows.len());
         for &window in &self.windows {
-            let border_color = if self.focused_window == Some(window) {
-                0xff0000
+            let (border_color, border_width) = if self.focused_window == Some(window) {
+                println!("Window {} is FOCUSED (red border)", window);
+                (0xff0000, 2)
             } else {
-                0x888888
+                println!("Window {} is unfocused (gray border)", window);
+                (0x888888, 2)
             };
 
+            self.connection.configure_window(
+                window,
+                &ConfigureWindowAux::new().border_width(border_width),
+            )?;
+
             self.connection.change_window_attributes(
                 window,
                 &ChangeWindowAttributesAux::new().border_pixel(border_color),
             )?;
         }
+        self.connection.flush()?;
         Ok(())
     }
+
     fn handle_event(&mut self, event: Event) -> Result<()> {
         match event {
             Event::MapRequest(event) => {
@@ -148,7 +176,6 @@ impl WindowManager {
                 self.windows.push(event.window);
                 self.apply_layout()?;
                 self.set_focus(Some(event.window))?;
-                self.connection.flush()?;
             }
             Event::UnmapNotify(event) => {
                 if self.windows.contains(&event.window) {
@@ -163,32 +190,38 @@ impl WindowManager {
                 }
             }
             Event::KeyPress(event) => {
-                let action = keyboard::handle_key_press(event)?; // Remove &self.connection
-                self.handle_key_action(action)?;
+                let (action, arg) = keyboard::handle_key_press(event)?;
+                self.handle_key_action(action, arg)?;
             }
             _ => {}
         }
         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;
+        let border_width = 2u32;
 
         let geometries = self
             .layout
             .arrange(&self.windows, screen_width, screen_height);
 
         for (window, geometry) in self.windows.iter().zip(geometries.iter()) {
+            // Adjust for full borders
+            let adjusted_width = geometry.width.saturating_sub(2 * border_width);
+            let adjusted_height = geometry.height.saturating_sub(2 * border_width);
+
             self.connection.configure_window(
                 *window,
                 &ConfigureWindowAux::new()
                     .x(geometry.x_coordinate)
                     .y(geometry.y_coordinate)
-                    .width(geometry.width)
-                    .height(geometry.height)
-                    .border_width(1),
+                    .width(adjusted_width)
+                    .height(adjusted_height),
             )?;
         }
+        self.connection.flush()?;
         return Ok(());
     }
 
@@ -200,15 +233,15 @@ impl WindowManager {
             println!("Removed window {} from management", window);
 
             if self.focused_window == Some(window) {
-                self.focused_window = if self.windows.is_empty() {
+                let new_focus = if self.windows.is_empty() {
                     None
                 } else {
                     Some(self.windows[self.windows.len() - 1])
                 };
+                self.set_focus(new_focus)?;
             }
 
             self.apply_layout()?;
-            self.connection.flush()?;
         }
 
         Ok(())