git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commitbe5771a22e70e8313bd4ce9aff40ebbbae1874e6
parent4353b94308
authorLucas Galante <[email protected]>
date2026-07-13 22:55
refactor: remove monolithic mode and app-side super+drag reordering

--monolithic ran all nine modules in one window and was the only mode
where super+drag reordering (check_drag_swap, dragged_module,
super_pressed, the ModifiersUpdated event and the modifiers feed
subscription) did anything — a Wayland client can't reposition its own
surface, so per-module segments never benefited. Segment moving is now
compositor-owned: super+left-drag starts the same drag as
adjust-position mode (cce a110048). The launcher-daemon and --module
process model is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>

 CLAUDE.md        |  21 ++++---
 src/listeners.rs |   1 -
 src/main.rs      | 179 +++++++------------------------------------------------
 3 files changed, 35 insertions(+), 166 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 9bf015e..25560ee 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -24,22 +24,23 @@ sockets); there is no meaningful headless mode.
 
 ## Process model (the most important thing to know)
 
-One binary, four modes, selected by CLI args in `main()`:
+One binary, three modes, selected by CLI args in `main()`:
 
 - **No args — launcher daemon.** Spawns one child process per module
   (`--module window`, `--module clock`, …), polls every 500ms and restarts crashed
-  children. This is the normal production mode: each module is its own process and its
-  own Wayland surface.
+  children with exponential backoff (500ms doubling to 30s; 30s of healthy uptime
+  resets it). This is the normal production mode: each module is its own process and
+  its own Wayland surface.
 - **`--module <name>`** — a single-module bar segment. Valid names: `window`, `tray`,
   `cpu`, `memory`, `brightness`, `volume`, `battery`, `clock`, `light_source`.
-- **`--monolithic`** — all modules in one window (window on the left, the rest on the
-  right). Useful for debugging layout without nine processes.
 - **`--trigger-switcher`** — one-shot: writes `trigger` to the switcher socket of the
   running instance and exits (used as a keybinding target).
 
+(The old `--monolithic` all-modules-in-one-window mode is gone, along with the
+app-side super+drag module reordering that only made sense there.)
+
 The compositor places each segment by its Wayland `app_id`, computed in
-`StatusApp::get_app_id()`: `cce-status-{side}-{name}` (e.g. `cce-status-left-window`),
-or plain `cce-status` for the monolithic bar. If
+`StatusApp::get_app_id()`: `cce-status-{side}-{name}` (e.g. `cce-status-left-window`). If
 `/tmp/cce-status-interface-{WAYLAND_DISPLAY}.sock` exists, the `cce-status-interface-`
 prefix is used instead — keep both spellings in mind when matching app_ids. A module's
 side comes from the config (`get_module_side`, which also maps snap positions like
@@ -121,8 +122,10 @@ mtime in `tick()`, so there is no reload event to wire up.
 
 ## Interactions worth knowing before touching input code
 
-- **Super + left-drag** moves a module along the bar (`dragged_module`,
-  `ModifiersUpdated` tracks the super key from the compositor feed).
+- **Super + left-drag on a segment is handled by the compositor**, not this app: it
+  starts the same segment drag as adjust-position mode (snap to an edge on release,
+  persisted to `layout.status_bar.<module>` in config.kdl). This app never sees those
+  clicks and no longer tracks the super key.
 - Viewport tabs in the window module are clickable (`viewport_bounds` → `ccectl view`);
   the layout indicator opens the layout-mode menu; tray icons left-click activate /
   right-click open their DBusMenu.
diff --git a/src/listeners.rs b/src/listeners.rs
index 97c69d5..01a0d3b 100644
--- a/src/listeners.rs
+++ b/src/listeners.rs
@@ -38,7 +38,6 @@ pub(crate) async fn spawn_status_listener(sub: &'static str, sender: calloop::ch
                             "viewport" => CustomEvent::ViewportUpdated(val.clone()),
                             "layout" => CustomEvent::LayoutUpdated(val.clone()),
                             "title" => CustomEvent::TitleUpdated(val.clone()),
-                            "modifiers" => CustomEvent::ModifiersUpdated(val.clone()),
                             _ => unreachable!(),
                         };
                         let _ = sender.send(ev);
diff --git a/src/main.rs b/src/main.rs
index fe7146a..e3e7a1b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -89,7 +89,6 @@ pub(crate) enum CustomEvent {
     ViewportUpdated(String),
     LayoutUpdated(String),
     TitleUpdated(String),
-    ModifiersUpdated(String),
     SystemStatsUpdated(SystemStats),
     TrayUpdated(TrayItem),
     TrayRemoved(String),
@@ -222,8 +221,6 @@ struct StatusApp {
     needs_rebuild: bool,
     current_bg_color: [f32; 4],
     input_regions: Vec<(i32, i32, i32, i32)>,
-    super_pressed: bool,
-    dragged_module: Option<(String, Side, f32)>,
     module_bounds: Vec<ModuleBounds>,
     left_modules: Vec<Box<dyn StatusModule>>,
     right_modules: Vec<Box<dyn StatusModule>>,
@@ -647,72 +644,6 @@ impl StatusApp {
         self.needs_rebuild = false;
     }
 
-    fn check_drag_swap(&mut self, mouse_x: f32) -> bool {
-        if let Some((ref dragged_name, side, _)) = self.dragged_module {
-            match side {
-                Side::Left => {
-                    if let Some(curr_idx) = self.left_modules.iter().position(|m| m.name() == dragged_name) {
-                        let curr_bounds = self.module_bounds.iter().find(|mb| mb.name == *dragged_name && mb.side == Side::Left);
-                        if curr_bounds.is_some() {
-                            // Check left neighbor
-                            if curr_idx > 0 {
-                                let prev_name = self.left_modules[curr_idx - 1].name();
-                                if let Some(prev) = self.module_bounds.iter().find(|mb| mb.name == prev_name && mb.side == Side::Left) {
-                                    let prev_center = prev.x + prev.w / 2.0;
-                                    if mouse_x < prev_center {
-                                        self.left_modules.swap(curr_idx, curr_idx - 1);
-                                        return true;
-                                    }
-                                }
-                            }
-                            // Check right neighbor
-                            if curr_idx < self.left_modules.len() - 1 {
-                                let next_name = self.left_modules[curr_idx + 1].name();
-                                if let Some(next) = self.module_bounds.iter().find(|mb| mb.name == next_name && mb.side == Side::Left) {
-                                    let next_center = next.x + next.w / 2.0;
-                                    if mouse_x > next_center {
-                                        self.left_modules.swap(curr_idx, curr_idx + 1);
-                                        return true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                Side::Right => {
-                    if let Some(curr_idx) = self.right_modules.iter().position(|m| m.name() == dragged_name) {
-                        let curr_bounds = self.module_bounds.iter().find(|mb| mb.name == *dragged_name && mb.side == Side::Right);
-                        if curr_bounds.is_some() {
-                            // Check left neighbor
-                            if curr_idx > 0 {
-                                let prev_name = self.right_modules[curr_idx - 1].name();
-                                if let Some(prev) = self.module_bounds.iter().find(|mb| mb.name == prev_name && mb.side == Side::Right) {
-                                    let prev_center = prev.x + prev.w / 2.0;
-                                    if mouse_x < prev_center {
-                                        self.right_modules.swap(curr_idx, curr_idx - 1);
-                                        return true;
-                                    }
-                                }
-                            }
-                            // Check right neighbor
-                            if curr_idx < self.right_modules.len() - 1 {
-                                let next_name = self.right_modules[curr_idx + 1].name();
-                                if let Some(next) = self.module_bounds.iter().find(|mb| mb.name == next_name && mb.side == Side::Right) {
-                                    let next_center = next.x + next.w / 2.0;
-                                    if mouse_x > next_center {
-                                        self.right_modules.swap(curr_idx, curr_idx + 1);
-                                        return true;
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        false
-    }
-
     fn trigger_switcher(&mut self, is_switcher_mode: bool) {
         // Keyboard alt-tab switching is implemented natively by the compositor
         // (bound to super+tab via the window_manager.window_switcher config key).
@@ -966,48 +897,35 @@ impl cce_ui::engine::Application for StatusApp {
         let selected_module = parse_selected_module_from_args();
 
         let mut left_modules: Vec<Box<dyn StatusModule>> = Vec::new();
-        let mut right_modules: Vec<Box<dyn StatusModule>> = Vec::new();
+        let right_modules: Vec<Box<dyn StatusModule>> = Vec::new();
 
         let mut has_window = false;
 
-        if let Some((ref name, _)) = selected_module {
-            let module: Box<dyn StatusModule> = match name.as_str() {
-                "window" => {
-                    has_window = true;
-                    Box::new(WindowModule)
-                }
-                "tray" => Box::new(TrayModule),
-                "cpu" => Box::new(CpuModule),
-                "memory" => Box::new(MemoryModule),
-                "brightness" => Box::new(BrightnessModule),
-                "volume" => Box::new(VolumeModule),
-                "battery" => Box::new(BatteryModule),
-                "clock" => Box::new(ClockModule),
-                "light_source" => Box::new(LightSourceModule),
-                _ => panic!("Unknown module: {}", name),
-            };
-            left_modules.push(module);
-        } else {
-            left_modules.push(Box::new(WindowModule));
-            right_modules.push(Box::new(TrayModule));
-            right_modules.push(Box::new(CpuModule));
-            right_modules.push(Box::new(MemoryModule));
-            right_modules.push(Box::new(BrightnessModule));
-            right_modules.push(Box::new(VolumeModule));
-            right_modules.push(Box::new(BatteryModule));
-            right_modules.push(Box::new(ClockModule));
-            right_modules.push(Box::new(LightSourceModule));
-            has_window = true;
-        }
+        let (ref name, _) = selected_module
+            .as_ref()
+            .expect("StatusApp requires --module <name>; the no-arg form runs the launcher daemon");
+        let module: Box<dyn StatusModule> = match name.as_str() {
+            "window" => {
+                has_window = true;
+                Box::new(WindowModule)
+            }
+            "tray" => Box::new(TrayModule),
+            "cpu" => Box::new(CpuModule),
+            "memory" => Box::new(MemoryModule),
+            "brightness" => Box::new(BrightnessModule),
+            "volume" => Box::new(VolumeModule),
+            "battery" => Box::new(BatteryModule),
+            "clock" => Box::new(ClockModule),
+            "light_source" => Box::new(LightSourceModule),
+            _ => panic!("Unknown module: {}", name),
+        };
+        left_modules.push(module);
 
         if has_window {
             tokio::spawn(spawn_status_listener("viewport", sender.clone()));
             tokio::spawn(spawn_status_listener("layout", sender.clone()));
             tokio::spawn(spawn_status_listener("title", sender.clone()));
         }
-        if selected_module.is_none() {
-            tokio::spawn(spawn_status_listener("modifiers", sender.clone()));
-        }
         let is_primary_for_switcher = selected_module.as_ref().map_or(true, |(name, _)| name == "window");
         if is_primary_for_switcher {
             tokio::spawn(spawn_switcher_listener(sender.clone()));
@@ -1052,8 +970,6 @@ impl cce_ui::engine::Application for StatusApp {
             needs_rebuild: true,
             current_bg_color: color::STATUS_BG,
             input_regions: Vec::new(),
-            super_pressed: false,
-            dragged_module: None,
             module_bounds: Vec::new(),
             left_modules,
             right_modules,
@@ -1102,9 +1018,6 @@ impl cce_ui::engine::Application for StatusApp {
             CustomEvent::TitleUpdated(t) => {
                 self.title = t;
             }
-            CustomEvent::ModifiersUpdated(m) => {
-                self.super_pressed = m.contains("super");
-            }
             CustomEvent::SystemStatsUpdated(s) => {
                 log::debug!("[module-{}] stats updated, current width={}", self.selected_module_name.as_deref().unwrap_or("none"), self.width);
                 self.stats = Some(s);
@@ -1249,16 +1162,6 @@ impl cce_ui::engine::Application for StatusApp {
     fn handle_pointer_move(&mut self, pos: cce_ui::engine::LogicalPosition, needs_rebuild: &mut bool) {
         let (lx, ly) = (pos.x, pos.y);
         self.cursor_pos = (lx as f64, ly as f64);
-        let is_vertical = self.is_vertical();
-        let coord = if is_vertical { ly } else { lx };
-
-        if self.dragged_module.is_some() {
-            if self.check_drag_swap(coord) {
-                *needs_rebuild = true;
-                self.needs_rebuild = true;
-            }
-            return;
-        }
 
         let mut newly_hovered = None;
         for bound in &self.tray_item_bounds {
@@ -1275,40 +1178,13 @@ impl cce_ui::engine::Application for StatusApp {
         }
     }
 
-    fn handle_mouse_input(&mut self, button: MouseButton, state: ElementState, pos: cce_ui::engine::LogicalPosition, needs_rebuild: &mut bool) -> Option<Self::Message> {
+    fn handle_mouse_input(&mut self, button: MouseButton, state: ElementState, pos: cce_ui::engine::LogicalPosition, _needs_rebuild: &mut bool) -> Option<Self::Message> {
         let (lx, ly) = (pos.x, pos.y);
         let is_vertical = self.is_vertical();
         let coord = if is_vertical { ly } else { lx };
         let cx = lx as f64;
         let cy = ly as f64;
 
-        if button == MouseButton::Left {
-            if state == ElementState::Pressed {
-                if self.super_pressed {
-                    let mut clicked_module = None;
-                    for mb in &self.module_bounds {
-                        if lx >= mb.x && lx <= (mb.x + mb.w) {
-                            clicked_module = Some((mb.name.clone(), mb.side));
-                            break;
-                        }
-                    }
-                    if let Some((name, side)) = clicked_module {
-                        self.dragged_module = Some((name, side, lx));
-                        *needs_rebuild = true;
-                        self.needs_rebuild = true;
-                        return None;
-                    }
-                }
-            } else {
-                if self.dragged_module.is_some() {
-                    self.dragged_module = None;
-                    *needs_rebuild = true;
-                    self.needs_rebuild = true;
-                    return None;
-                }
-            }
-        }
-
         if state == ElementState::Pressed {
             // Check if tray icon was clicked
             let mut clicked_tray = None;
@@ -1654,18 +1530,9 @@ fn main() {
         return;
     }
 
-    let mut has_module = false;
-    let mut monolithic = false;
-    for i in 0..args.len() {
-        if args[i] == "--module" {
-            has_module = true;
-        }
-        if args[i] == "--monolithic" {
-            monolithic = true;
-        }
-    }
+    let has_module = args.iter().any(|arg| arg == "--module");
 
-    if !has_module && !monolithic {
+    if !has_module {
         log::info!("Starting cce-status-interface launcher daemon...");
         let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
         rt.block_on(async {