status bar
git clone https://git.lucas.co/cce-status-interface.git
feat: close in-surface menu on click-away (compositor dismiss push)
Subscribe every module process to the status socket's new one-shot
'dismiss' topic. The compositor pushes a line whenever a pointer press
lands anywhere other than an expanded status segment; the payload is
the pressed segment's app_id, so a segment ignores dismisses naming
itself (its own input handler already dealt with that press). The
menu closes through the normal contraction animation.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/listeners.rs | 3 +++
src/main.rs | 19 +++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/src/listeners.rs b/src/listeners.rs
index 01a0d3b..33921fe 100644
--- a/src/listeners.rs
+++ b/src/listeners.rs
@@ -38,6 +38,9 @@ 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()),
+ // Click-away-close: the payload is the app_id of
+ // the segment the press landed on ("-" for none).
+ "dismiss" => CustomEvent::MenuDismiss(val.clone()),
_ => unreachable!(),
};
let _ = sender.send(ev);
diff --git a/src/main.rs b/src/main.rs
index 3f5d1bd..7b92402 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -119,6 +119,10 @@ pub(crate) enum CustomEvent {
/// A bar-built in-surface menu (window picker), fetched off-thread.
MenuReady { title: String, pages: Vec<MenuPage>, min_w: f32 },
SwitcherTriggered,
+ /// Compositor click-away-close: a pointer press landed somewhere other
+ /// than this expanded segment. Payload = the pressed segment's app_id
+ /// ("-" for none); a segment ignores a dismiss naming itself.
+ MenuDismiss(String),
/// A tray icon's DBusMenu, fetched and flattened for the in-surface menu.
TrayMenuFetched { destination: String, menu_path: String, pages: Vec<MenuPage> },
ToggleHideModules,
@@ -1170,6 +1174,9 @@ impl cce_ui::engine::Application for StatusApp {
tokio::spawn(spawn_status_listener("layout", sender.clone()));
tokio::spawn(spawn_status_listener("title", sender.clone()));
}
+ // Every module can host an in-surface menu, so every process listens
+ // for the compositor's click-away dismiss pushes.
+ tokio::spawn(spawn_status_listener("dismiss", 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()));
@@ -1338,6 +1345,18 @@ impl cce_ui::engine::Application for StatusApp {
log::debug!("[switcher] SwitcherTriggered event received, calling trigger_switcher");
self.trigger_switcher(true);
}
+ CustomEvent::MenuDismiss(pressed_app_id) => {
+ // Close-on-click-away, unless the press was on THIS segment
+ // (then handle_mouse_input already decided what to do).
+ if self.context_menu.is_some()
+ && !self.menu_closing
+ && pressed_app_id != self.get_app_id()
+ {
+ self.menu_closing = true;
+ } else {
+ changed = false;
+ }
+ }
CustomEvent::ToggleHideModules => {
self.status_hide_mode = !self.status_hide_mode;
let cmd = if self.status_hide_mode { "true" } else { "false" };