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

commitc547f252b36bb5a5ad58bf72a576a9dd78c551e3
parent7346c32704
authorLucas Galante <[email protected]>
date2026-07-03 10:52
Implement status hide mode with configurable preview height

 src/server/config.rs         |  9 ++++++++
 src/server/layer_shell.rs    | 49 ++++++++++++++++++++++++++++++--------------
 src/server/window_manager.rs | 33 ++++++++++++++++++++++++-----
 3 files changed, 71 insertions(+), 20 deletions(-)

diff --git a/src/server/config.rs b/src/server/config.rs
index 1e7acc6..75ec41c 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -53,6 +53,7 @@ pub struct Layout {
     pub scenefx_optimized_blur: bool,
     pub status_backdrop_blur_ignore_transparent: bool,
     pub window_backdrop_blur_ignore_transparent: bool,
+    pub status_module_hide_mode_preview: i64,
 }
 
 impl Default for Layout {
@@ -104,6 +105,7 @@ impl Default for Layout {
             scenefx_optimized_blur: true,
             status_backdrop_blur_ignore_transparent: true,
             window_backdrop_blur_ignore_transparent: true,
+            status_module_hide_mode_preview: 4,
         }
     }
 }
@@ -441,6 +443,8 @@ pub struct LayoutConfig {
     pub status_backdrop_blur_ignore_transparent: bool,
     #[serde(default = "default_window_backdrop_blur_ignore_transparent")]
     pub window_backdrop_blur_ignore_transparent: bool,
+    #[serde(default = "default_status_module_hide_mode_preview")]
+    pub status_module_hide_mode_preview: i64,
 }
 
 impl Default for LayoutConfig {
@@ -465,6 +469,7 @@ impl Default for LayoutConfig {
             window_opacity: default_window_opacity(),
             status_backdrop_blur_ignore_transparent: default_status_backdrop_blur_ignore_transparent(),
             window_backdrop_blur_ignore_transparent: default_window_backdrop_blur_ignore_transparent(),
+            status_module_hide_mode_preview: default_status_module_hide_mode_preview(),
         }
     }
 }
@@ -487,6 +492,8 @@ fn default_status_normal_color() -> String { "#ccccd8".to_string() }
 fn default_status_background_blur() -> f64 { 0.8 }
 fn default_window_opacity() -> bool { true }
 fn default_status_backdrop_blur_ignore_transparent() -> bool { true }
+
+fn default_status_module_hide_mode_preview() -> i64 { 4 }
 fn default_window_backdrop_blur_ignore_transparent() -> bool { true }
 
 #[derive(Debug, Deserialize)]
@@ -1022,6 +1029,7 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
         layout.status_normal_color = get_nested_prop_string(node, "status", "normal_color", &default_status_normal_color());
         layout.status_background_blur = get_nested_prop_f64(node, "status", "background_blur", default_status_background_blur());
         layout.status_backdrop_blur_ignore_transparent = get_nested_prop_bool(node, "status", "backdrop_blur_ignore_transparent", default_status_backdrop_blur_ignore_transparent());
+        layout.status_module_hide_mode_preview = get_nested_prop_i64(node, "status", "module_hide_mode_preview", default_status_module_hide_mode_preview());
     }
 
     // 2. env
@@ -1446,6 +1454,7 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
     state.layout.scenefx_optimized_blur = config.output.as_ref().map(|o| o.scenefx_optimized_blur).unwrap_or(true);
     state.layout.status_backdrop_blur_ignore_transparent = config.layout.status_backdrop_blur_ignore_transparent;
     state.layout.window_backdrop_blur_ignore_transparent = config.layout.window_backdrop_blur_ignore_transparent;
+    state.layout.status_module_hide_mode_preview = config.layout.status_module_hide_mode_preview;
 
     for (key, val) in &config.env {
         let expanded = expand_env_vars(val);
diff --git a/src/server/layer_shell.rs b/src/server/layer_shell.rs
index da7c5d3..e665589 100644
--- a/src/server/layer_shell.rs
+++ b/src/server/layer_shell.rs
@@ -648,6 +648,7 @@ unsafe extern "C" fn handle_layer_surface_commit(listener: *mut ffi::wl_listener
                 let mut parent_x = None;
                 let mut parent_y = None;
                 let mut parent_w = 0;
+
                 for &win_ptr in (*server).wm.windows.iter() {
                     if win_ptr.is_null() || (*win_ptr).closed {
                         continue;
@@ -663,25 +664,43 @@ unsafe extern "C" fn handle_layer_surface_commit(listener: *mut ffi::wl_listener
                 }
 
                 if let (Some(px), Some(py)) = (parent_x, parent_y) {
-                    let wlr_box = (*output).sent.box_layout();
-                    let output_x = wlr_box.x;
-                    let output_y = wlr_box.y;
-                    let output_w = wlr_box.width;
+                    let wlr_output = (*wlr_layer_surface).output;
+                    if !wlr_output.is_null() {
+                        let mut output_x = 0;
+                        let mut output_y = 0;
+                        let mut output_w = 0;
+                        let outputs_list = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+                        let mut curr_out = (*outputs_list).next;
+                        while curr_out != outputs_list {
+                            let output = crate::container_of!(curr_out, crate::output::Output, link);
+                            if (*output).wlr_output == wlr_output {
+                                let wlr_box = (*output).sent.box_layout();
+                                output_x = wlr_box.x;
+                                output_y = wlr_box.y;
+                                output_w = wlr_box.width;
+                                break;
+                            }
+                            curr_out = (*curr_out).next;
+                        }
 
-                    let relative_parent_x = px - output_x;
-                    let relative_parent_y = py - output_y;
+                        let relative_parent_x = px - output_x;
+                        let relative_parent_y = py - output_y;
 
-                    let anchor = (*wlr_layer_surface).current.anchor;
-                    let is_align_right = (anchor & ffi::zwlr_layer_surface_v1_anchor_ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) != 0;
+                        let anchor = (*wlr_layer_surface).current.anchor;
+                        let is_align_right = (anchor & ffi::zwlr_layer_surface_v1_anchor_ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) != 0;
 
-                    if is_align_right {
-                        (*wlr_layer_surface).current.margin.right = (output_w - relative_parent_x - parent_w) + (*wlr_layer_surface).current.margin.right;
-                    } else {
-                        (*wlr_layer_surface).current.margin.left = relative_parent_x + (*wlr_layer_surface).current.margin.left;
-                    }
-                    (*wlr_layer_surface).current.margin.top = relative_parent_y + (*wlr_layer_surface).current.margin.top;
+                        if is_align_right {
+                            (*wlr_layer_surface).pending.margin.right = (output_w - relative_parent_x - parent_w) + (*wlr_layer_surface).pending.margin.right;
+                            (*wlr_layer_surface).current.margin.right = (*wlr_layer_surface).pending.margin.right;
+                        } else {
+                            (*wlr_layer_surface).pending.margin.left = relative_parent_x + (*wlr_layer_surface).pending.margin.left;
+                            (*wlr_layer_surface).current.margin.left = (*wlr_layer_surface).pending.margin.left;
+                        }
+                        (*wlr_layer_surface).pending.margin.top = relative_parent_y + (*wlr_layer_surface).pending.margin.top;
+                        (*wlr_layer_surface).current.margin.top = (*wlr_layer_surface).pending.margin.top;
 
-                    (*layer_surface).parent_offset_applied = true;
+                        (*layer_surface).parent_offset_applied = true;
+                    }
                 }
             }
         }
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index c5bd076..29eb74b 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -108,6 +108,7 @@ pub struct WindowManager {
     pub input_rules: Vec<crate::config::InputDeviceConfigRule>,
     pub input_config: crate::config::InputConfig,
     pub last_status_update: std::cell::RefCell<Option<crate::status_server::StatusUpdate>>,
+    pub status_hide_mode: bool,
     pub restore_queue: Vec<SavedWindowState>,
     pub last_window_states: Vec<SavedWindowState>,
     pub shutting_down: bool,
@@ -193,6 +194,7 @@ impl WindowManager {
         self.input_rules = Vec::new();
         self.input_config = crate::config::InputConfig::default();
         self.last_status_update = std::cell::RefCell::new(None);
+        self.status_hide_mode = false;
 
         ffi::wl_list_init(&mut self.sent.outputs);
         ffi::wl_list_init(&mut self.sent.seats);
@@ -1591,14 +1593,21 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
             let spacing = 12;
             let margin = 12;
 
+            let status_y = if self.status_hide_mode {
+                let preview = self.layout.status_module_hide_mode_preview as i32;
+                wlr_box.y - (bar_h as i32 - preview)
+            } else {
+                wlr_box.y
+            };
+
             // Layout Left status windows
             let mut cur_left_x = wlr_box.x + margin;
             for win_ptr in left_status {
                 let w = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
                 let app_id = (*win_ptr).get_app_id_string().unwrap_or_default();
-                log::info!("[ArrangeStatus] Left module app_id={} x={} y={} w={}", app_id, cur_left_x, wlr_box.y, w);
+                log::info!("[ArrangeStatus] Left module app_id={} x={} y={} w={}", app_id, cur_left_x, status_y, w);
                 (*win_ptr).rendering_requested.x = cur_left_x;
-                (*win_ptr).rendering_requested.y = wlr_box.y;
+                (*win_ptr).rendering_requested.y = status_y;
                 (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
                     width: w,
                     height: bar_h,
@@ -1617,9 +1626,9 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
                 let w = if actual_w > 0 { actual_w as u32 } else { 100 };
                 let x = cur_right_x - w as i32;
                 let app_id = (*win_ptr).get_app_id_string().unwrap_or_default();
-                log::info!("[ArrangeStatus] Right module app_id={} actual_box_w={} x={} y={} w={}", app_id, actual_w, x, wlr_box.y, w);
+                log::info!("[ArrangeStatus] Right module app_id={} actual_box_w={} x={} y={} w={}", app_id, actual_w, x, status_y, w);
                 (*win_ptr).rendering_requested.x = x;
-                (*win_ptr).rendering_requested.y = wlr_box.y;
+                (*win_ptr).rendering_requested.y = status_y;
                 (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
                     width: w,
                     height: bar_h,
@@ -1634,7 +1643,7 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
             // Layout Full/Legacy status windows
             for win_ptr in full_status {
                 (*win_ptr).rendering_requested.x = wlr_box.x;
-                (*win_ptr).rendering_requested.y = wlr_box.y;
+                (*win_ptr).rendering_requested.y = status_y;
                 (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
                     width: wlr_box.width as u32,
                     height: bar_h,
@@ -2369,6 +2378,20 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
             self.stop_panning_animation();
         }
         match action {
+            "status-hide-mode" => {
+                let enable = if parts.len() >= 2 {
+                    match parts[1] {
+                        "true" | "on" | "enable" | "1" => true,
+                        "false" | "off" | "disable" | "0" => false,
+                        _ => !self.status_hide_mode,
+                    }
+                } else {
+                    !self.status_hide_mode
+                };
+                self.status_hide_mode = enable;
+                self.dirty_windowing();
+                return format!("ok {}\n", enable);
+            }
             "view" => {
                 if parts.len() < 2 { return "error: missing tag\n".to_string(); }
                 if let Ok(tag) = parts[1].parse::<i32>() {