git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit1ef6bd7a24b8890bd88002bfaed0462db053ba93
parentec0c959a6d
authorLucas Galante <[email protected]>
date2026-06-23 08:48
Update default zoom keybindings and implement dynamic desktop background grid

 src/server/config.rs         | 16 ++++-----
 src/server/output.rs         | 81 ++++++++++++++++++++++++++++++++++++++++++++
 src/server/output_manager.rs | 14 ++++++++
 3 files changed, 103 insertions(+), 8 deletions(-)

diff --git a/src/server/config.rs b/src/server/config.rs
index 2a5525a..50d2ecb 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -764,7 +764,6 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
     let down_sym = parse_keysym("Down");
     let equal_sym = parse_keysym("equal");
     let minus_sym = parse_keysym("minus");
-    let zero_sym = parse_keysym("0");
 
     if !state.keybinds.iter().any(|b| b.mods == super_ctrl_mod && b.keysym == up_sym) {
         state.keybinds.push(Keybind {
@@ -798,26 +797,27 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
             command: None,
         });
     }
-    if !state.keybinds.iter().any(|b| b.mods == super_mod && b.keysym == equal_sym) {
+    let super_ctrl_shift_mod = parse_modifiers("super+ctrl+shift");
+    if !state.keybinds.iter().any(|b| b.mods == super_ctrl_shift_mod && b.keysym == equal_sym) {
         state.keybinds.push(Keybind {
-            mods: super_mod,
+            mods: super_ctrl_shift_mod,
             keysym: equal_sym,
             action: Action::ZoomIn,
             command: None,
         });
     }
-    if !state.keybinds.iter().any(|b| b.mods == super_mod && b.keysym == minus_sym) {
+    if !state.keybinds.iter().any(|b| b.mods == super_ctrl_mod && b.keysym == minus_sym) {
         state.keybinds.push(Keybind {
-            mods: super_mod,
+            mods: super_ctrl_mod,
             keysym: minus_sym,
             action: Action::ZoomOut,
             command: None,
         });
     }
-    if !state.keybinds.iter().any(|b| b.mods == super_mod && b.keysym == zero_sym) {
+    if !state.keybinds.iter().any(|b| b.mods == super_ctrl_mod && b.keysym == equal_sym) {
         state.keybinds.push(Keybind {
-            mods: super_mod,
-            keysym: zero_sym,
+            mods: super_ctrl_mod,
+            keysym: equal_sym,
             action: Action::ZoomReset,
             command: None,
         });
diff --git a/src/server/output.rs b/src/server/output.rs
index d59b8d7..a99a15c 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -141,6 +141,7 @@ pub struct Output {
     pub wlr_output: *mut ffi::wlr_output,
     pub scene_output: *mut ffi::wlr_scene_output,
     pub background_rect: *mut ffi::wlr_scene_rect,
+    pub grid_tree: *mut ffi::wlr_scene_tree,
     pub object: *mut ffi::wl_resource, // zcce_output_v1 resource
     pub layer_shell: LayerShellOutput,
     pub lock_render_state: LockRenderState,
@@ -307,6 +308,11 @@ impl Output {
                         self.background_rect = std::ptr::null_mut();
                     }
 
+                    if !self.grid_tree.is_null() {
+                        ffi::wlr_scene_node_destroy(self.grid_tree as *mut ffi::wlr_scene_node);
+                        self.grid_tree = std::ptr::null_mut();
+                    }
+
                     // remove output from windows fullscreen hint
                     for &window in (*self.server).wm.windows.iter() {
                         if let crate::window::FullscreenRequest::Fullscreen(out) = (*window).wm_scheduled.fullscreen_requested {
@@ -364,6 +370,7 @@ impl Output {
             wlr_output,
             scene_output,
             background_rect: std::ptr::null_mut(),
+            grid_tree: std::ptr::null_mut(),
             object: std::ptr::null_mut(),
             layer_shell: LayerShellOutput::default(),
             lock_render_state: LockRenderState::Blanked,
@@ -432,6 +439,8 @@ impl Output {
             }
         }
 
+        self.draw_grid();
+
         let mut state = std::mem::zeroed();
         ffi::wlr_output_state_init(&mut state);
         
@@ -498,6 +507,73 @@ impl Output {
             ffi::wlr_scene_rect_set_color(self.background_rect, color.as_ptr());
         }
     }
+
+    pub unsafe fn draw_grid(&mut self) {
+        if self.grid_tree.is_null() {
+            return;
+        }
+
+        // Clear previous grid lines
+        ffi::river_scene_tree_clear_children(self.grid_tree);
+
+        let wm = &(*self.server).wm;
+        let (viewport_w, viewport_h) = self.current.dimensions();
+        let zoom = wm.desk_zoom;
+
+        // Dynamic spacing based on zoom to avoid rendering too many lines (LOD)
+        let mut grid_spacing = 100.0;
+        while grid_spacing * zoom < 40.0 {
+            grid_spacing *= 2.0;
+        }
+
+        let min_x = wm.desk_pan_x;
+        let max_x = wm.desk_pan_x + (viewport_w as f64) / zoom;
+        let min_y = wm.desk_pan_y;
+        let max_y = wm.desk_pan_y + (viewport_h as f64) / zoom;
+
+        // Subtle semi-transparent grid color (e.g. 5% white)
+        let grid_color: [f32; 4] = [1.0, 1.0, 1.0, 0.05];
+
+        // Draw vertical lines
+        let mut x_val = (min_x / grid_spacing).ceil() * grid_spacing;
+        while x_val <= max_x {
+            let rel_x = ((x_val - wm.desk_pan_x) * zoom) as i32;
+            let line_rect = ffi::wlr_scene_rect_create(
+                self.grid_tree,
+                1, // width of line
+                viewport_h,
+                grid_color.as_ptr(),
+            );
+            if !line_rect.is_null() {
+                ffi::wlr_scene_node_set_position(
+                    line_rect as *mut ffi::wlr_scene_node,
+                    rel_x,
+                    0,
+                );
+            }
+            x_val += grid_spacing;
+        }
+
+        // Draw horizontal lines
+        let mut y_val = (min_y / grid_spacing).ceil() * grid_spacing;
+        while y_val <= max_y {
+            let rel_y = ((y_val - wm.desk_pan_y) * zoom) as i32;
+            let line_rect = ffi::wlr_scene_rect_create(
+                self.grid_tree,
+                viewport_w,
+                1, // height of line
+                grid_color.as_ptr(),
+            );
+            if !line_rect.is_null() {
+                ffi::wlr_scene_node_set_position(
+                    line_rect as *mut ffi::wlr_scene_node,
+                    0,
+                    rel_y,
+                );
+            }
+            y_val += grid_spacing;
+        }
+    }
 }
 
 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
@@ -516,6 +592,11 @@ unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut
         (*output).background_rect = std::ptr::null_mut();
     }
 
+    if !(*output).grid_tree.is_null() {
+        ffi::wlr_scene_node_destroy((*output).grid_tree as *mut ffi::wlr_scene_node);
+        (*output).grid_tree = std::ptr::null_mut();
+    }
+
     if !(*output).wlr_output.is_null() {
         ffi::river_wlr_output_set_data((*output).wlr_output, std::ptr::null_mut());
     }
diff --git a/src/server/output_manager.rs b/src/server/output_manager.rs
index 8692b95..dd94da0 100644
--- a/src/server/output_manager.rs
+++ b/src/server/output_manager.rs
@@ -225,6 +225,16 @@ impl OutputManager {
                                 output.sent.y,
                             );
                         }
+                        if output.grid_tree.is_null() {
+                            output.grid_tree = ffi::wlr_scene_tree_create((*server).scene.layers.background);
+                        }
+                        if !output.grid_tree.is_null() {
+                            ffi::wlr_scene_node_set_position(
+                                output.grid_tree as *mut ffi::wlr_scene_node,
+                                output.sent.x,
+                                output.sent.y,
+                            );
+                        }
                     }
                     OutputStateValue::DisabledHard => {
                         ffi::wlr_output_layout_remove(self.output_layout, wlr_output);
@@ -232,6 +242,10 @@ impl OutputManager {
                             ffi::wlr_scene_node_destroy(output.background_rect as *mut ffi::wlr_scene_node);
                             output.background_rect = std::ptr::null_mut();
                         }
+                        if !output.grid_tree.is_null() {
+                            ffi::wlr_scene_node_destroy(output.grid_tree as *mut ffi::wlr_scene_node);
+                            output.grid_tree = std::ptr::null_mut();
+                        }
                     }
                     OutputStateValue::Destroying => unreachable!(),
                 }