status bar
git clone https://git.lucas.co/cce-status-interface.git
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 {