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

commit4e7e60b1a10592b2587784cdc9809729f4fc633f
parent732bd2fd5d
authorLucas Galante <[email protected]>
date2026-08-10 10:39
refactor: remove the layout-mode menu and viewport plumbing

The menu's viewport-layout / apply-mode-sharing commands were already
no-op stubs compositor-side; with viewport tags removed it loses its
target vocabulary entirely. Gone: the layout-indicator click menu
(SetMode/ToggleApplyAll rows, apply_all/active_viewport menu state),
LayoutBounds plumbing through every StatusModule signature, the
viewport status subscription and field, and the
get_closest_viewport / get_active_viewport_from_camera helpers. The
mode text stays visible next to the focused title; modes remain
changeable via mode-next and ccectl mode.

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

 CLAUDE.md        |  22 +++----
 src/cloud.rs     |   5 --
 src/listeners.rs |   1 -
 src/main.rs      | 179 ++++---------------------------------------------------
 src/modules.rs   |  19 +-----
 5 files changed, 22 insertions(+), 204 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 8621654..db80cd4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -73,11 +73,9 @@ in `new()` — which tasks run depends on the selected module, so a clock proces
 listen to tray D-Bus, etc.:
 
 - **Compositor status feed** (`spawn_status_listener`): connects to
-  `/tmp/cce-status[-interface]-{WAYLAND_DISPLAY}.sock`, subscribes to `viewport`,
-  `layout`, `title`, `modifiers` (line-oriented, auto-reconnects every 1s). The
-  `viewport` payload is JSON carrying only the active viewport number
-  (`{"active": N}`), read at menu-open time for the layout menu's target —
-  nothing renders it (the old viewport tabs are gone).
+  `/tmp/cce-status[-interface]-{WAYLAND_DISPLAY}.sock`, subscribes to `layout`,
+  `title`, `modifiers`, `dismiss` (line-oriented, auto-reconnects every 1s).
+  (The old `viewport` topic is gone with the viewport-tag feature.)
 - **System stats** (`spawn_system_stats`): `/proc/stat`, `/proc/meminfo`,
   `/sys/class/power_supply/BAT*`, `/sys/class/backlight`, and `pactl` for volume/mute.
 - **Tray** (`spawn_status_tray`): a full StatusNotifierItem/Watcher host over `zbus`,
@@ -87,9 +85,8 @@ listen to tray D-Bus, etc.:
   `/tmp/cce-status-interface-switcher-{WAYLAND_DISPLAY}.sock`; a line on it fires
   `SwitcherTriggered`.
 
-Outbound actions shell out to `ccectl` (`view <viewport>`, `windows --json`,
-`focus-window`, `viewport-layout`, `window-switcher`, `status-hide-mode`,
-`adjust-position-mode`), resolved from `~/.local/bin` first (`get_ccectl_cmd`).
+Outbound actions shell out to `ccectl` (`windows --json`, `focus-window`,
+`window-switcher`, `status-hide-mode`, `adjust-position-mode`), resolved from `~/.local/bin` first (`get_ccectl_cmd`).
 `ccectl windows --json` returns one JSON object per line; the text format is kept only
 as a parse fallback for older compositors (`parse_ccectl_window_any_line` handles
 both). Keyboard alt-tab switching is delegated to the compositor
@@ -109,9 +106,8 @@ segment thicker than the bar as expanded: frozen slot, no size enforcement,
 raised above overlapped windows; the bar must reset its own height on close.
 
 **No cce-cloud popups remain in this app**: the window picker (window-module
-title click → `MenuReady` rows of `Ccectl(["focus-window", id])`) and the
-layout-mode menu (layout-indicator click → `SetMode` rows + the in-place
-`ToggleApplyAll` checkbox row) are in-surface menus too. Menu width sizes to
+click → `MenuReady` rows of `Ccectl(["focus-window", id])`) is an in-surface
+menu too. Menu width sizes to
 the longest row label. Expanded segments stack in the compositor's popups
 layer (cce-fx@74a0f75) so click-away-close works across the whole surface,
 including the strip band over neighboring segments.
@@ -159,8 +155,8 @@ mtime in `tick()`, so there is no reload event to wire up.
   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.
-- The layout indicator opens the layout-mode menu; tray icons left-click activate /
-  right-click open their DBusMenu.
+- Tray icons left-click activate / right-click open their DBusMenu. (The old
+  layout-mode menu and viewport tabs are gone with the viewport-tag feature.)
 - `ToggleHideModules` / `ToggleAdjustPositionMode` mirror their state to the compositor
   via `ccectl status-hide-mode|adjust-position-mode true|false`; the adjust-mode state
   is read back with `ccectl adjust-position-mode query` (the compositor is the single
diff --git a/src/cloud.rs b/src/cloud.rs
index f7f2766..5e43dd7 100644
--- a/src/cloud.rs
+++ b/src/cloud.rs
@@ -113,11 +113,6 @@ pub(crate) enum MenuRowAction {
     Dispatch(CustomEvent),
     /// Run ccectl with these args, detached (window picker rows).
     Ccectl(Vec<String>),
-    /// Apply a window mode (the layout menu): honors the menu's live
-    /// apply-to-all toggle and captured viewport at click time.
-    SetMode(String),
-    /// Flip the layout menu's apply-to-all toggle in place (stays open).
-    ToggleApplyAll,
     /// Non-interactive (separators).
     Inert,
 }
diff --git a/src/listeners.rs b/src/listeners.rs
index 33921fe..bd9baa8 100644
--- a/src/listeners.rs
+++ b/src/listeners.rs
@@ -35,7 +35,6 @@ pub(crate) async fn spawn_status_listener(sub: &'static str, sender: calloop::ch
                     log::debug!("[status-listener] received '{}' update: '{}'", sub, val);
                     if !val.is_empty() {
                         let ev = match sub {
-                            "viewport" => CustomEvent::ViewportUpdated(val.clone()),
                             "layout" => CustomEvent::LayoutUpdated(val.clone()),
                             "title" => CustomEvent::TitleUpdated(val.clone()),
                             // Click-away-close: the payload is the app_id of
diff --git a/src/main.rs b/src/main.rs
index f642c07..a5b6df6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -54,14 +54,6 @@ pub struct TrayIconBounds {
     pub dbus_id: Option<String>,
 }
 
-#[derive(Debug, Clone)]
-pub struct LayoutBounds {
-    pub x: f32,
-    pub y: f32,
-    pub w: f32,
-    pub h: f32,
-}
-
 #[derive(Debug, Clone)]
 pub struct SystemStats {
     pub clock: String,
@@ -101,7 +93,6 @@ fn stats_signature(module: Option<&str>, s: &SystemStats) -> Option<String> {
 
 #[derive(Debug, Clone)]
 pub(crate) enum CustomEvent {
-    ViewportUpdated(String),
     LayoutUpdated(String),
     TitleUpdated(String),
     SystemStatsUpdated(SystemStats),
@@ -133,10 +124,6 @@ struct ModuleContextMenu {
     /// to; None for the bar's own module menu.
     tray_target: Option<(String, String)>,
     min_w: f32,
-    /// The layout menu's "apply to all sharing mode" toggle.
-    apply_all: bool,
-    /// Viewport captured when the layout menu opened (SetMode target).
-    active_viewport: i32,
     hovered: Option<usize>,
     /// Menu box in surface-local logical coords, set by `rebuild_layout`.
     rect: (f32, f32, f32, f32),
@@ -254,7 +241,6 @@ pub struct ModuleBounds {
 
 struct StatusApp {
     // Status State
-    viewport: String,
     layout: String,
     title: String,
     stats: Option<SystemStats>,
@@ -262,7 +248,6 @@ struct StatusApp {
     cursor_pos: (f64, f64),
     hovered_tray_item: Option<String>,
     tray_item_bounds: Vec<TrayIconBounds>,
-    layout_bounds: Option<LayoutBounds>,
 
     font_system: FontSystem,
     status_bar: cce_ui::widget::Adapted<cce_ui::widget::StatusBar>,
@@ -389,7 +374,6 @@ impl StatusApp {
             self.status_bar.set_bg_color(self.current_bg_color);
         }
 
-        self.layout_bounds = None;
 
         let is_single = self.selected_module_name.is_some();
         let margin_padding = if is_single { 6.0 } else { 12.0 };
@@ -446,7 +430,6 @@ impl StatusApp {
                     &mut self.text_prims,
                     &mut self.rects,
                     &mut self.overlay_rects,
-                    &mut self.layout_bounds,
                     &self.tray_items,
                     &mut self.tray_item_bounds,
                     box_bg_color,
@@ -527,7 +510,6 @@ impl StatusApp {
                     &mut self.text_prims,
                     &mut self.rects,
                     &mut self.overlay_rects,
-                    &mut self.layout_bounds,
                     &self.tray_items,
                     &mut self.tray_item_bounds,
                     box_bg_color,
@@ -696,7 +678,7 @@ impl StatusApp {
                     // ONE continuous box in the module's own fill, spanning
                     // the strip band and the menu — the module box literally
                     // grows into the menu. Inserted at the front so the
-                    // module's strip content (tray icons, viewport tabs)
+                    // module's strip content (tray icons, labels)
                     // renders on top of its band.
                     self.rounded_boxes.insert(0, RoundedBox {
                         x: plate_x,
@@ -887,50 +869,6 @@ impl StatusApp {
     }
 }
 
-fn get_closest_viewport(x: f64, y: f64) -> i32 {
-    let centers = [(0.0, 0.0), (2000.0, 0.0), (0.0, 2000.0), (2000.0, 2000.0)];
-    let mut min_dist = f64::MAX;
-    let mut best_tag = 1;
-    for (i, &(cx, cy)) in centers.iter().enumerate() {
-        let dx = x - cx;
-        let dy = y - cy;
-        let dist = dx * dx + dy * dy;
-        if dist < min_dist {
-            min_dist = dist;
-            best_tag = (i + 1) as i32;
-        }
-    }
-    best_tag
-}
-
-fn get_active_viewport_from_camera(viewport_json: &str) -> u32 {
-    let mut text = viewport_json.to_string();
-    if let Ok(val) = serde_json::from_str::<serde_json::Value>(viewport_json) {
-        // Current compositors send the active viewport explicitly alongside
-        // the tab markup; the Pan-text parse below is the fallback for the
-        // old camera-state payload.
-        if let Some(a) = val.get("active").and_then(|v| v.as_u64()) {
-            if (1..=4).contains(&a) {
-                return a as u32;
-            }
-        }
-        if let Some(t) = val.get("text").and_then(|v| v.as_str()) {
-            text = t.to_string();
-        }
-    }
-    if let Some(pan_idx) = text.find("Pan: (") {
-        let coords_str = &text[pan_idx + "Pan: (".len()..];
-        if let Some(end_idx) = coords_str.find(")") {
-            let parts: Vec<&str> = coords_str[..end_idx].split(',').collect();
-            if parts.len() == 2 {
-                let pan_x = parts[0].trim().parse::<f64>().unwrap_or(0.0);
-                let pan_y = parts[1].trim().parse::<f64>().unwrap_or(0.0);
-                return get_closest_viewport(pan_x, pan_y) as u32;
-            }
-        }
-    }
-    1
-}
 
 fn get_module_side(name: &str) -> Side {
     let content = std::fs::read_to_string(cce_ui::config::get_config_path()).unwrap_or_default();
@@ -1133,7 +1071,6 @@ impl cce_ui::engine::Application for StatusApp {
         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()));
         }
@@ -1159,7 +1096,6 @@ impl cce_ui::engine::Application for StatusApp {
         let font_system = cce_ui::create_font_system();
 
         let mut app = Self {
-            viewport: String::new(),
             layout: String::new(),
             title: String::new(),
             stats: if has_stats { Some(get_initial_stats()) } else { None },
@@ -1167,7 +1103,6 @@ impl cce_ui::engine::Application for StatusApp {
             cursor_pos: (0.0, 0.0),
             hovered_tray_item: None,
             tray_item_bounds: Vec::new(),
-            layout_bounds: None,
             font_system,
             status_bar: cce_ui::widget::StatusBar::new(),
             rects: Vec::new(),
@@ -1225,17 +1160,10 @@ impl cce_ui::engine::Application for StatusApp {
     fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, _exit: &mut bool) {
         // Default to redrawing; the high-frequency push events below clear this when
         // their value is unchanged, so a once-a-second stats poll (or a repeated
-        // viewport/title push) no longer forces a redraw — and the compositor's
+        // title push) no longer forces a redraw — and the compositor's
         // whole-backdrop blur re-bake — every time.
         let mut changed = true;
         match msg {
-            CustomEvent::ViewportUpdated(t) => {
-                // Nothing renders the viewport payload (the tabs are gone);
-                // it is only read at menu-open time for the layout menu's
-                // active viewport, so a push never redraws.
-                changed = false;
-                self.viewport = t;
-            }
             CustomEvent::LayoutUpdated(l) => {
                 changed = self.layout != l;
                 self.layout = l;
@@ -1275,8 +1203,6 @@ impl cce_ui::engine::Application for StatusApp {
                         page: 0,
                         tray_target: None,
                         min_w,
-                        apply_all: false,
-                        active_viewport: 0,
                         hovered: None,
                         rect: (0.0, 0.0, 0.0, 0.0),
                         row_bounds: Vec::new(),
@@ -1296,8 +1222,6 @@ impl cce_ui::engine::Application for StatusApp {
                         page: 0,
                         tray_target: Some((destination, menu_path)),
                         min_w: 260.0,
-                        apply_all: false,
-                        active_viewport: 0,
                         hovered: None,
                         rect: (0.0, 0.0, 0.0, 0.0),
                         row_bounds: Vec::new(),
@@ -1575,32 +1499,6 @@ impl cce_ui::engine::Application for StatusApp {
                             });
                             self.menu_closing = true;
                         }
-                        Some(MenuRowAction::SetMode(mode)) => {
-                            let args = if menu.apply_all {
-                                vec!["apply-mode-sharing".to_string(), mode]
-                            } else {
-                                vec![
-                                    "viewport-layout".to_string(),
-                                    menu.active_viewport.to_string(),
-                                    mode,
-                                ]
-                            };
-                            std::thread::spawn(move || {
-                                let _ = std::process::Command::new(get_ccectl_cmd())
-                                    .args(&args)
-                                    .spawn();
-                            });
-                            self.menu_closing = true;
-                        }
-                        Some(MenuRowAction::ToggleApplyAll) => {
-                            menu.apply_all = !menu.apply_all;
-                            let mark = if menu.apply_all { "[x]" } else { "[ ]" };
-                            if let Some(page) = menu.pages.get_mut(menu.page) {
-                                if let Some(row) = page.rows.get_mut(i) {
-                                    row.label = format!("{} Apply to all sharing mode", mark);
-                                }
-                            }
-                        }
                         _ => {}
                     }
                 }
@@ -1763,8 +1661,6 @@ impl cce_ui::engine::Application for StatusApp {
                         page: 0,
                         tray_target: None,
                         min_w: 190.0,
-                        apply_all: false,
-                        active_viewport: 0,
                         hovered: None,
                         rect: (0.0, 0.0, 0.0, 0.0),
                         row_bounds: Vec::new(),
@@ -1776,70 +1672,19 @@ impl cce_ui::engine::Application for StatusApp {
             }
 
             if button == MouseButton::Left {
-                log::debug!("[viewport-click] Mouse left click at logical: ({}, {})", cx, cy);
-                
-                // Check if layout mode was clicked
-                let mut clicked_layout = false;
-                if let Some(ref bounds) = self.layout_bounds {
-                    if cx >= bounds.x as f64 && cx <= (bounds.x + bounds.w) as f64
-                        && cy >= bounds.y as f64 && cy <= (bounds.y + bounds.h) as f64 {
-                        clicked_layout = true;
-                    }
-                }
-
-                if clicked_layout {
-                    log::debug!("[layout-click] opening in-surface layout menu");
-                    if self.context_menu.is_none() {
-                        self.menu_anim = 0.0;
-                    }
-                    self.menu_closing = false;
-                    let mode_row = |label: &str| MenuRow {
-                        label: label.to_string(),
-                        enabled: true,
-                        separator: false,
-                        action: MenuRowAction::SetMode(label.to_lowercase()),
-                    };
-                    let rows = vec![
-                        MenuRow {
-                            label: "[ ] Apply to all sharing mode".to_string(),
-                            enabled: true,
-                            separator: false,
-                            action: MenuRowAction::ToggleApplyAll,
-                        },
-                        mode_row("Cascade"),
-                        mode_row("Grid"),
-                        mode_row("Fullscreen"),
-                        mode_row("Floating"),
-                        mode_row("Popup"),
-                    ];
-                    self.context_menu = Some(ModuleContextMenu {
-                        pages: vec![MenuPage { title: "Window Mode".to_string(), rows }],
-                        page: 0,
-                        tray_target: None,
-                        min_w: 240.0,
-                        apply_all: false,
-                        active_viewport: get_active_viewport_from_camera(&self.viewport) as i32,
-                        hovered: None,
-                        rect: (0.0, 0.0, 0.0, 0.0),
-                        row_bounds: Vec::new(),
-                    });
-                    self.needs_rebuild = true;
-                    *needs_rebuild = true;
-                } else {
-                    let mut clicked_window = false;
-                    for mb in &self.module_bounds {
-                        if mb.name == "window" {
-                            if coord >= mb.x && coord <= (mb.x + mb.w) {
-                                clicked_window = true;
-                                break;
-                            }
+                let mut clicked_window = false;
+                for mb in &self.module_bounds {
+                    if mb.name == "window" {
+                        if coord >= mb.x && coord <= (mb.x + mb.w) {
+                            clicked_window = true;
+                            break;
                         }
                     }
+                }
 
-                    if clicked_window {
-                        log::debug!("[window-click] Window module clicked, opening window picker");
-                        self.trigger_switcher(false);
-                    }
+                if clicked_window {
+                    log::debug!("[window-click] Window module clicked, opening window picker");
+                    self.trigger_switcher(false);
                 }
             }
         }
diff --git a/src/modules.rs b/src/modules.rs
index 874490b..4d6f1d4 100644
--- a/src/modules.rs
+++ b/src/modules.rs
@@ -4,7 +4,7 @@ use cce_ui::color;
 use cce_ui::widget::StyledLabel as Label;
 
 use crate::{
-    RectWidget, RoundedBox, LayoutBounds, SystemStats, TrayItem,
+    RectWidget, RoundedBox, SystemStats, TrayItem,
     TrayIconBounds, make_text_buffer,
 };
 
@@ -49,7 +49,6 @@ pub trait StatusModule {
         text_prims: &mut Vec<crate::TextPrim>,
         rects: &mut Vec<RectWidget>,
         overlay_rects: &mut Vec<RectWidget>,
-        layout_bounds: &mut Option<LayoutBounds>,
         tray_items: &HashMap<String, TrayItem>,
         tray_item_bounds: &mut Vec<TrayIconBounds>,
         box_bg_color: Option<[f32; 4]>,
@@ -162,7 +161,6 @@ impl StatusModule for WindowModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         box_bg_color: Option<[f32; 4]>,
@@ -196,14 +194,7 @@ impl StatusModule for WindowModule {
 
             if has_layout {
                 let layout_label = Label::new_with_family(font_system, layout, font_size, normal_color, font_family);
-                let w = layout_label.w;
                 crate::draw_label(text_prims, layout_label, cur_x, y_pos);
-                *layout_bounds = Some(LayoutBounds {
-                    x: cur_x - 2.0,
-                    y: 0.0,
-                    w: w + 4.0,
-                    h: bar_h,
-                });
             }
         } else if title == "(none)" {
             // Dim chip signalling that no window has keyboard focus — the
@@ -271,7 +262,6 @@ impl StatusModule for ClockModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -326,7 +316,6 @@ impl StatusModule for BatteryModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -388,7 +377,6 @@ impl StatusModule for VolumeModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -463,7 +451,6 @@ impl StatusModule for BrightnessModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -528,7 +515,6 @@ impl StatusModule for MemoryModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -583,7 +569,6 @@ impl StatusModule for CpuModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -638,7 +623,6 @@ impl StatusModule for TrayModule {
         text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         tray_items: &HashMap<String, TrayItem>,
         tray_item_bounds: &mut Vec<TrayIconBounds>,
         _box_bg_color: Option<[f32; 4]>,
@@ -849,7 +833,6 @@ impl StatusModule for LightSourceModule {
         _text_prims: &mut Vec<crate::TextPrim>,
         _rects: &mut Vec<RectWidget>,
         _overlay_rects: &mut Vec<RectWidget>,
-        _layout_bounds: &mut Option<LayoutBounds>,
         _tray_items: &HashMap<String, TrayItem>,
         _tray_item_bounds: &mut Vec<TrayIconBounds>,
         box_bg_color: Option<[f32; 4]>,