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

commit561026fa735d72c50990a117dea0e67357a0f411
parent05d60a1bea
authorLucas Galante <[email protected]>
date2026-06-15 12:53
feat(server): render desktop background using layout low_color config

 src/server/config.rs         | 33 +++++++++++++++++++++++++++++----
 src/server/output.rs         | 25 +++++++++++++++++++++++++
 src/server/output_manager.rs | 30 ++++++++++++++++++++++++++++++
 src/server/window_manager.rs | 13 +++++++++++++
 4 files changed, 97 insertions(+), 4 deletions(-)

diff --git a/src/server/config.rs b/src/server/config.rs
index 74456c1..1ba3c81 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -38,6 +38,7 @@ pub struct Layout {
     pub side_panel_border_gap: i32,
     pub side_panel_border_opacity: i32,
     pub status_normal_color: String,
+    pub low_color: String,
 }
 
 impl Default for Layout {
@@ -59,9 +60,9 @@ impl Default for Layout {
             border_g: 0x3E3E3E3Eu32,
             border_b: 0x3E3E3E3Eu32,
             border_a: 0xFFFFFFFFu32,
-            background_r: 0x0A0A0A0Au32,
-            background_g: 0x1A1A1A1Au32,
-            background_b: 0x0E0E0E0Eu32,
+            background_r: 0x1C1C1C1Cu32,
+            background_g: 0x20202020u32,
+            background_b: 0x20202020u32,
             background_a: 0xFFFFFFFFu32,
             border_font_size: 11,
             transition_duration: 300,
@@ -74,6 +75,7 @@ impl Default for Layout {
             side_panel_border_gap: 0,
             side_panel_border_opacity: 100,
             status_normal_color: "#ccccd8".to_string(),
+            low_color: "#1c2020".to_string(),
         }
     }
 }
@@ -243,6 +245,8 @@ pub struct LayoutConfig {
     pub border_color: String,
     #[serde(default = "default_background_color")]
     pub background_color: String,
+    #[serde(default)]
+    pub low_color: Option<String>,
     #[serde(default = "default_border_font_size")]
     pub border_font_size: i64,
     #[serde(default = "default_transition_duration")]
@@ -284,6 +288,7 @@ impl Default for LayoutConfig {
             floating_border_width: default_floating_border_width(),
             border_color: default_border_color(),
             background_color: default_background_color(),
+            low_color: None,
             border_font_size: default_border_font_size(),
             transition_duration: default_transition_duration(),
             grid_gap: default_grid_gap(),
@@ -597,7 +602,10 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
     state.layout.border_b = (border_color_val & 0xFF) * 0x01010101;
     state.layout.border_a = 0xFFFFFFFF;
 
-    let background_color_val = parse_hex_color(&config.layout.background_color);
+    let low_color_str = config.layout.low_color.clone().unwrap_or_else(|| config.layout.background_color.clone());
+    state.layout.low_color = low_color_str.clone();
+
+    let background_color_val = parse_hex_color(&low_color_str);
     state.layout.background_r = ((background_color_val >> 16) & 0xFF) * 0x01010101;
     state.layout.background_g = ((background_color_val >> 8) & 0xFF) * 0x01010101;
     state.layout.background_b = (background_color_val & 0xFF) * 0x01010101;
@@ -710,6 +718,19 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
         state.startup.len()
     );
 
+    unsafe {
+        if !state.server.is_null() {
+            let outputs_head = &mut (*state.server).om.outputs as *mut crate::ffi::wl_list as *mut crate::server::WlList;
+            let mut curr = (*outputs_head).next;
+            while curr != outputs_head {
+                let next = (*curr).next;
+                let output = &mut *crate::container_of!(curr, crate::output::Output, link);
+                output.update_background_color();
+                curr = next;
+            }
+        }
+    }
+
     Ok(())
 }
 
@@ -733,6 +754,10 @@ mod tests {
             assert_eq!(server.wm.input_config.dwtp, Some(true));
             assert_eq!(server.wm.input_config.trackpoint_accel_speed, Some(0.6));
             assert_eq!(server.wm.input_config.trackpoint_accel_profile, Some("flat".to_string()));
+            assert_eq!(server.wm.layout.low_color, "#1c2020");
+            assert_eq!(server.wm.layout.background_r, 0x1C1C1C1Cu32);
+            assert_eq!(server.wm.layout.background_g, 0x20202020u32);
+            assert_eq!(server.wm.layout.background_b, 0x20202020u32);
         }
     }
 }
diff --git a/src/server/output.rs b/src/server/output.rs
index 4e642cc..402732e 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -140,6 +140,7 @@ pub struct Output {
     pub server: *mut Server,
     pub wlr_output: *mut ffi::wlr_output,
     pub scene_output: *mut ffi::wlr_scene_output,
+    pub background_rect: *mut ffi::wlr_scene_rect,
     pub object: *mut ffi::wl_resource, // river_output_v1 resource
     pub layer_shell: LayerShellOutput,
     pub lock_render_state: LockRenderState,
@@ -301,6 +302,11 @@ impl Output {
                 if self.scheduled.state == OutputStateValue::Destroying {
                     assert!(self.wlr_output.is_null());
                     
+                    if !self.background_rect.is_null() {
+                        ffi::wlr_scene_node_destroy(self.background_rect as *mut ffi::wlr_scene_node);
+                        self.background_rect = 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 {
@@ -357,6 +363,7 @@ impl Output {
             server,
             wlr_output,
             scene_output,
+            background_rect: std::ptr::null_mut(),
             object: std::ptr::null_mut(),
             layer_shell: LayerShellOutput::default(),
             lock_render_state: LockRenderState::Blanked,
@@ -470,6 +477,19 @@ impl Output {
 
         Ok(())
     }
+
+    pub unsafe fn update_background_color(&mut self) {
+        if !self.background_rect.is_null() {
+            let wm = &(*self.server).wm;
+            let color: [f32; 4] = [
+                (wm.layout.background_r as f64 / u32::MAX as f64) as f32,
+                (wm.layout.background_g as f64 / u32::MAX as f64) as f32,
+                (wm.layout.background_b as f64 / u32::MAX as f64) as f32,
+                (wm.layout.background_a as f64 / u32::MAX as f64) as f32,
+            ];
+            ffi::wlr_scene_rect_set_color(self.background_rect, color.as_ptr());
+        }
+    }
 }
 
 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
@@ -483,6 +503,11 @@ unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut
     wl_listener_remove(&mut (*output).frame);
     wl_listener_remove(&mut (*output).present);
 
+    if !(*output).background_rect.is_null() {
+        ffi::wlr_scene_node_destroy((*output).background_rect as *mut ffi::wlr_scene_node);
+        (*output).background_rect = 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 4ac5ac3..043d76f 100644
--- a/src/server/output_manager.rs
+++ b/src/server/output_manager.rs
@@ -199,9 +199,39 @@ impl OutputManager {
                                 output.sent.y,
                             );
                         }
+
+                        let (width, height) = output.sent.dimensions();
+                        let color: [f32; 4] = [
+                            ((*server).wm.layout.background_r as f64 / u32::MAX as f64) as f32,
+                            ((*server).wm.layout.background_g as f64 / u32::MAX as f64) as f32,
+                            ((*server).wm.layout.background_b as f64 / u32::MAX as f64) as f32,
+                            ((*server).wm.layout.background_a as f64 / u32::MAX as f64) as f32,
+                        ];
+                        if output.background_rect.is_null() {
+                            output.background_rect = ffi::wlr_scene_rect_create(
+                                (*server).scene.layers.background,
+                                width,
+                                height,
+                                color.as_ptr(),
+                            );
+                        } else {
+                            ffi::wlr_scene_rect_set_size(output.background_rect, width, height);
+                            ffi::wlr_scene_rect_set_color(output.background_rect, color.as_ptr());
+                        }
+                        if !output.background_rect.is_null() {
+                            ffi::wlr_scene_node_set_position(
+                                output.background_rect as *mut ffi::wlr_scene_node,
+                                output.sent.x,
+                                output.sent.y,
+                            );
+                        }
                     }
                     OutputStateValue::DisabledHard => {
                         ffi::wlr_output_layout_remove(self.output_layout, wlr_output);
+                        if !output.background_rect.is_null() {
+                            ffi::wlr_scene_node_destroy(output.background_rect as *mut ffi::wlr_scene_node);
+                            output.background_rect = std::ptr::null_mut();
+                        }
                     }
                     OutputStateValue::Destroying => unreachable!(),
                 }
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index c705c96..44fff9a 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -1493,6 +1493,19 @@ impl WindowManager {
                 self.execute_action(&crate::config::Action::Exit, None);
                 "ok\n".to_string()
             }
+            "reload" => {
+                if let Some(path) = crate::config::default_config_path() {
+                    match crate::config::parse_config(&path, self) {
+                        Ok(()) => {
+                            self.dirty_windowing();
+                            "ok\n".to_string()
+                        }
+                        Err(e) => format!("error: failed to reload config: {}\n", e),
+                    }
+                } else {
+                    "error: no config file found\n".to_string()
+                }
+            }
             "retile" => {
                 self.dirty_windowing();
                 "ok\n".to_string()