gardesk/gar / 6957153

Browse files

Integrate Lua config into WindowManager

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6957153f763288b05f143c4df55065e4ee7c08bc
Parents
66eb4cf
Tree
55047a4

3 changed files

StatusFile+-
M gar/src/core/mod.rs 19 2
M gar/src/x11/error.rs 3 0
M gar/src/x11/events.rs 99 190
gar/src/core/mod.rsmodified
@@ -9,15 +9,18 @@ pub use window::Window;
99
 pub use workspace::Workspace;
1010
 
1111
 use std::collections::HashMap;
12
+use std::sync::{Arc, Mutex};
1213
 use x11rb::protocol::xproto::Window as XWindow;
1314
 
14
-use crate::config::Config;
15
+use crate::config::{Config, LuaConfig, LuaState};
1516
 use crate::x11::Connection;
1617
 use crate::Result;
1718
 
1819
 pub struct WindowManager {
1920
     pub conn: Connection,
2021
     pub config: Config,
22
+    pub lua_config: LuaConfig,
23
+    pub lua_state: Arc<Mutex<LuaState>>,
2124
     pub workspaces: Vec<Workspace>,
2225
     pub monitors: Vec<Monitor>,
2326
     pub windows: HashMap<XWindow, Window>,
@@ -32,9 +35,23 @@ impl WindowManager {
3235
             .map(|i| Workspace::new(i, i.to_string()))
3336
             .collect();
3437
 
38
+        // Initialize Lua config
39
+        let lua_config = LuaConfig::new().map_err(|e| crate::Error::Config(e.to_string()))?;
40
+        let lua_state = lua_config.state();
41
+
42
+        // Load configuration
43
+        lua_config
44
+            .load()
45
+            .map_err(|e| crate::Error::Config(e.to_string()))?;
46
+
47
+        // Get config values from Lua state
48
+        let config = lua_state.lock().unwrap().config.clone();
49
+
3550
         Ok(Self {
3651
             conn,
37
-            config: Config::default(),
52
+            config,
53
+            lua_config,
54
+            lua_state,
3855
             workspaces,
3956
             monitors: Vec::new(),
4057
             windows: HashMap::new(),
gar/src/x11/error.rsmodified
@@ -25,4 +25,7 @@ pub enum Error {
2525
 
2626
     #[error("IO error: {0}")]
2727
     Io(#[from] std::io::Error),
28
+
29
+    #[error("Config error: {0}")]
30
+    Config(String),
2831
 }
gar/src/x11/events.rsmodified
@@ -7,161 +7,15 @@ use x11rb::protocol::xproto::{
77
 };
88
 use x11rb::protocol::Event;
99
 
10
+use crate::config::Action;
1011
 use crate::core::{Direction, Node, WindowManager};
1112
 use crate::Result;
1213
 
13
-// Keysym constants
14
-const XK_RETURN: u32 = 0xff0d;
15
-const XK_Q: u32 = 0x71;
16
-const XK_E: u32 = 0x65;
17
-const XK_LEFT: u32 = 0xff51;
18
-const XK_UP: u32 = 0xff52;
19
-const XK_RIGHT: u32 = 0xff53;
20
-const XK_DOWN: u32 = 0xff54;
21
-const XK_1: u32 = 0x31;
22
-const XK_2: u32 = 0x32;
23
-const XK_3: u32 = 0x33;
24
-const XK_4: u32 = 0x34;
25
-const XK_5: u32 = 0x35;
26
-const XK_6: u32 = 0x36;
27
-const XK_7: u32 = 0x37;
28
-const XK_8: u32 = 0x38;
29
-const XK_9: u32 = 0x39;
30
-const XK_0: u32 = 0x30;
31
-
32
-/// Keybind action types
33
-#[derive(Debug, Clone)]
34
-enum Action {
35
-    SpawnTerminal,
36
-    CloseWindow,
37
-    Focus(Direction),
38
-    Swap(Direction),
39
-    Resize(Direction),
40
-    Equalize,
41
-    SwitchWorkspace(usize),
42
-    MoveToWorkspace(usize),
43
-}
44
-
45
-struct Keybind {
46
-    modifiers: ModMask,
47
-    keysym: u32,
48
-    action: Action,
49
-}
50
-
5114
 impl WindowManager {
52
-    /// Get all keybinds to register.
53
-    /// NOTE: Using Alt (M1) instead of Super (M4) for testing in nested X
54
-    fn keybinds() -> Vec<Keybind> {
55
-        vec![
56
-            // Alt+Return: spawn terminal
57
-            Keybind {
58
-                modifiers: ModMask::M1,
59
-                keysym: XK_RETURN,
60
-                action: Action::SpawnTerminal,
61
-            },
62
-            // Alt+Q: close window
63
-            Keybind {
64
-                modifiers: ModMask::M1,
65
-                keysym: XK_Q,
66
-                action: Action::CloseWindow,
67
-            },
68
-            // Alt+E: equalize splits
69
-            Keybind {
70
-                modifiers: ModMask::M1,
71
-                keysym: XK_E,
72
-                action: Action::Equalize,
73
-            },
74
-            // Alt+Arrows: focus navigation
75
-            Keybind {
76
-                modifiers: ModMask::M1,
77
-                keysym: XK_LEFT,
78
-                action: Action::Focus(Direction::Left),
79
-            },
80
-            Keybind {
81
-                modifiers: ModMask::M1,
82
-                keysym: XK_RIGHT,
83
-                action: Action::Focus(Direction::Right),
84
-            },
85
-            Keybind {
86
-                modifiers: ModMask::M1,
87
-                keysym: XK_UP,
88
-                action: Action::Focus(Direction::Up),
89
-            },
90
-            Keybind {
91
-                modifiers: ModMask::M1,
92
-                keysym: XK_DOWN,
93
-                action: Action::Focus(Direction::Down),
94
-            },
95
-            // Alt+Shift+Arrows: swap windows
96
-            Keybind {
97
-                modifiers: ModMask::M1 | ModMask::SHIFT,
98
-                keysym: XK_LEFT,
99
-                action: Action::Swap(Direction::Left),
100
-            },
101
-            Keybind {
102
-                modifiers: ModMask::M1 | ModMask::SHIFT,
103
-                keysym: XK_RIGHT,
104
-                action: Action::Swap(Direction::Right),
105
-            },
106
-            Keybind {
107
-                modifiers: ModMask::M1 | ModMask::SHIFT,
108
-                keysym: XK_UP,
109
-                action: Action::Swap(Direction::Up),
110
-            },
111
-            Keybind {
112
-                modifiers: ModMask::M1 | ModMask::SHIFT,
113
-                keysym: XK_DOWN,
114
-                action: Action::Swap(Direction::Down),
115
-            },
116
-            // Alt+Ctrl+Arrows: resize
117
-            Keybind {
118
-                modifiers: ModMask::M1 | ModMask::CONTROL,
119
-                keysym: XK_LEFT,
120
-                action: Action::Resize(Direction::Left),
121
-            },
122
-            Keybind {
123
-                modifiers: ModMask::M1 | ModMask::CONTROL,
124
-                keysym: XK_RIGHT,
125
-                action: Action::Resize(Direction::Right),
126
-            },
127
-            Keybind {
128
-                modifiers: ModMask::M1 | ModMask::CONTROL,
129
-                keysym: XK_UP,
130
-                action: Action::Resize(Direction::Up),
131
-            },
132
-            Keybind {
133
-                modifiers: ModMask::M1 | ModMask::CONTROL,
134
-                keysym: XK_DOWN,
135
-                action: Action::Resize(Direction::Down),
136
-            },
137
-            // Alt+1-9,0: switch workspace
138
-            Keybind { modifiers: ModMask::M1, keysym: XK_1, action: Action::SwitchWorkspace(0) },
139
-            Keybind { modifiers: ModMask::M1, keysym: XK_2, action: Action::SwitchWorkspace(1) },
140
-            Keybind { modifiers: ModMask::M1, keysym: XK_3, action: Action::SwitchWorkspace(2) },
141
-            Keybind { modifiers: ModMask::M1, keysym: XK_4, action: Action::SwitchWorkspace(3) },
142
-            Keybind { modifiers: ModMask::M1, keysym: XK_5, action: Action::SwitchWorkspace(4) },
143
-            Keybind { modifiers: ModMask::M1, keysym: XK_6, action: Action::SwitchWorkspace(5) },
144
-            Keybind { modifiers: ModMask::M1, keysym: XK_7, action: Action::SwitchWorkspace(6) },
145
-            Keybind { modifiers: ModMask::M1, keysym: XK_8, action: Action::SwitchWorkspace(7) },
146
-            Keybind { modifiers: ModMask::M1, keysym: XK_9, action: Action::SwitchWorkspace(8) },
147
-            Keybind { modifiers: ModMask::M1, keysym: XK_0, action: Action::SwitchWorkspace(9) },
148
-            // Alt+Shift+1-9,0: move window to workspace
149
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_1, action: Action::MoveToWorkspace(0) },
150
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_2, action: Action::MoveToWorkspace(1) },
151
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_3, action: Action::MoveToWorkspace(2) },
152
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_4, action: Action::MoveToWorkspace(3) },
153
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_5, action: Action::MoveToWorkspace(4) },
154
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_6, action: Action::MoveToWorkspace(5) },
155
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_7, action: Action::MoveToWorkspace(6) },
156
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_8, action: Action::MoveToWorkspace(7) },
157
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_9, action: Action::MoveToWorkspace(8) },
158
-            Keybind { modifiers: ModMask::M1 | ModMask::SHIFT, keysym: XK_0, action: Action::MoveToWorkspace(9) },
159
-        ]
160
-    }
161
-
162
-    /// Set up initial keybinds and grabs.
15
+    /// Set up initial keybinds and grabs from Lua config.
16316
     pub fn setup_grabs(&mut self) -> Result<()> {
164
-        for keybind in Self::keybinds() {
17
+        let state = self.lua_state.lock().unwrap();
18
+        for keybind in &state.keybinds {
16519
             if let Some(keycode) = self.conn.keycode_from_keysym(keybind.keysym) {
16620
                 self.conn.grab_key(keybind.modifiers, keycode)?;
16721
                 tracing::debug!(
@@ -174,7 +28,7 @@ impl WindowManager {
17428
         }
17529
 
17630
         self.conn.flush()?;
177
-        tracing::info!("Keybinds registered");
31
+        tracing::info!("{} keybinds registered", state.keybinds.len());
17832
         Ok(())
17933
     }
18034
 
@@ -334,14 +188,22 @@ impl WindowManager {
334188
                 as u16,
335189
         );
336190
 
337
-        // Find matching keybind
338
-        for keybind in Self::keybinds() {
339
-            let bind_keycode = self.conn.keycode_from_keysym(keybind.keysym);
340
-            if bind_keycode == Some(keycode) && keybind.modifiers == modifiers {
341
-                tracing::debug!("Executing action: {:?}", keybind.action);
342
-                self.execute_action(keybind.action)?;
343
-                return Ok(());
344
-            }
191
+        // Find matching keybind from Lua config
192
+        let action = {
193
+            let lua_state = self.lua_state.lock().unwrap();
194
+            lua_state
195
+                .keybinds
196
+                .iter()
197
+                .find(|kb| {
198
+                    let bind_keycode = self.conn.keycode_from_keysym(kb.keysym);
199
+                    bind_keycode == Some(keycode) && kb.modifiers == modifiers
200
+                })
201
+                .map(|kb| kb.action.clone())
202
+        };
203
+
204
+        if let Some(action) = action {
205
+            tracing::debug!("Executing action: {:?}", action);
206
+            self.execute_action(action)?;
345207
         }
346208
 
347209
         Ok(())
@@ -349,8 +211,9 @@ impl WindowManager {
349211
 
350212
     fn execute_action(&mut self, action: Action) -> Result<()> {
351213
         match action {
352
-            Action::SpawnTerminal => {
353
-                self.spawn_terminal();
214
+            Action::Exec(cmd) => {
215
+                tracing::info!("Exec: {}", cmd);
216
+                Command::new("sh").arg("-c").arg(&cmd).spawn().ok();
354217
             }
355218
             Action::CloseWindow => {
356219
                 if let Some(window) = self.focused_window {
@@ -358,42 +221,45 @@ impl WindowManager {
358221
                 }
359222
             }
360223
             Action::Focus(direction) => {
361
-                self.focus_direction(direction)?;
224
+                if let Some(dir) = parse_direction(&direction) {
225
+                    self.focus_direction(dir)?;
226
+                }
362227
             }
363228
             Action::Swap(direction) => {
364
-                self.swap_direction(direction)?;
229
+                if let Some(dir) = parse_direction(&direction) {
230
+                    self.swap_direction(dir)?;
231
+                }
365232
             }
366
-            Action::Resize(direction) => {
367
-                self.resize_direction(direction)?;
233
+            Action::Resize(direction, amount) => {
234
+                if let Some(dir) = parse_direction(&direction) {
235
+                    self.resize_direction(dir, amount)?;
236
+                }
368237
             }
369238
             Action::Equalize => {
370239
                 self.equalize()?;
371240
             }
372
-            Action::SwitchWorkspace(idx) => {
373
-                self.switch_workspace(idx)?;
241
+            Action::Workspace(idx) => {
242
+                // Lua uses 1-based indexing
243
+                self.switch_workspace(idx.saturating_sub(1))?;
374244
             }
375245
             Action::MoveToWorkspace(idx) => {
376
-                self.move_to_workspace(idx)?;
246
+                // Lua uses 1-based indexing
247
+                self.move_to_workspace(idx.saturating_sub(1))?;
377248
             }
378
-        }
379
-        Ok(())
380
-    }
381
-
382
-    fn spawn_terminal(&self) {
383
-        // Try common terminals in order of preference
384
-        let terminals = ["alacritty", "kitty", "foot", "xterm"];
385
-
386
-        for terminal in terminals {
387
-            match Command::new(terminal).spawn() {
388
-                Ok(_) => {
389
-                    tracing::info!("Spawned {}", terminal);
390
-                    return;
249
+            Action::Reload => {
250
+                self.reload_config()?;
251
+            }
252
+            Action::Exit => {
253
+                tracing::info!("Exit requested");
254
+                self.running = false;
255
+            }
256
+            Action::LuaCallback(index) => {
257
+                if let Err(e) = self.lua_config.execute_callback(index) {
258
+                    tracing::error!("Lua callback error: {}", e);
391259
                 }
392
-                Err(_) => continue,
393260
             }
394261
         }
395
-
396
-        tracing::warn!("No terminal emulator found");
262
+        Ok(())
397263
     }
398264
 
399265
     fn close_window(&mut self, window: u32) -> Result<()> {
@@ -451,18 +317,16 @@ impl WindowManager {
451317
         Ok(())
452318
     }
453319
 
454
-    fn resize_direction(&mut self, direction: Direction) -> Result<()> {
320
+    fn resize_direction(&mut self, direction: Direction, delta: f32) -> Result<()> {
455321
         let Some(focused) = self.focused_window else {
456322
             return Ok(());
457323
         };
458324
 
459
-        const RESIZE_DELTA: f32 = 0.05;
460
-
461325
         // Resize the split
462326
         if self
463327
             .current_workspace_mut()
464328
             .tree
465
-            .resize(focused, direction, RESIZE_DELTA)
329
+            .resize(focused, direction, delta)
466330
         {
467331
             // Re-apply layout
468332
             self.apply_layout()?;
@@ -503,7 +367,11 @@ impl WindowManager {
503367
         self.apply_layout()?;
504368
 
505369
         // Focus the workspace's focused window or first window
506
-        if let Some(window) = self.current_workspace().focused.or_else(|| self.current_workspace().tree.first_window()) {
370
+        if let Some(window) = self
371
+            .current_workspace()
372
+            .focused
373
+            .or_else(|| self.current_workspace().tree.first_window())
374
+        {
507375
             self.set_focus(window)?;
508376
             self.conn.ungrab_button(window)?;
509377
         } else {
@@ -543,7 +411,9 @@ impl WindowManager {
543411
         // Insert into target workspace
544412
         let target_focused = self.workspaces[idx].focused;
545413
         let screen = self.screen_rect();
546
-        self.workspaces[idx].tree.insert_with_rect(window, target_focused, screen);
414
+        self.workspaces[idx]
415
+            .tree
416
+            .insert_with_rect(window, target_focused, screen);
547417
 
548418
         // Re-apply layout on current workspace
549419
         self.apply_layout()?;
@@ -558,6 +428,35 @@ impl WindowManager {
558428
         Ok(())
559429
     }
560430
 
431
+    fn reload_config(&mut self) -> Result<()> {
432
+        tracing::info!("Reloading configuration");
433
+
434
+        // Ungrab all current keys
435
+        // (We'd need to track grabbed keys to ungrab them properly,
436
+        // for now we'll just regrab - X11 handles duplicates)
437
+
438
+        // Reload Lua config
439
+        if let Err(e) = self.lua_config.reload() {
440
+            tracing::error!("Config reload failed: {}", e);
441
+            return Ok(());
442
+        }
443
+
444
+        // Update config from Lua state
445
+        {
446
+            let state = self.lua_state.lock().unwrap();
447
+            self.config = state.config.clone();
448
+        }
449
+
450
+        // Re-register keybinds
451
+        self.setup_grabs()?;
452
+
453
+        // Re-apply layout with new settings
454
+        self.apply_layout()?;
455
+
456
+        tracing::info!("Configuration reloaded");
457
+        Ok(())
458
+    }
459
+
561460
     pub fn run(&mut self) -> Result<()> {
562461
         tracing::info!("Starting event loop");
563462
 
@@ -573,3 +472,13 @@ impl WindowManager {
573472
         Ok(())
574473
     }
575474
 }
475
+
476
+fn parse_direction(s: &str) -> Option<Direction> {
477
+    match s.to_lowercase().as_str() {
478
+        "left" => Some(Direction::Left),
479
+        "right" => Some(Direction::Right),
480
+        "up" => Some(Direction::Up),
481
+        "down" => Some(Direction::Down),
482
+        _ => None,
483
+    }
484
+}