fix(status-area): better handle apps with no menus

This commit is contained in:
Ashley Wulber 2025-11-06 09:32:54 -05:00 committed by Ashley Wulber
parent bd98de8228
commit b026db6f7c
3 changed files with 54 additions and 12 deletions

View file

@ -460,7 +460,11 @@ impl cosmic::Application for App {
.icon_button_from_handle(icon.clone().symbolic(true)),
_ => self.core.applet.icon_button(menu.icon_name()),
}
.on_press_down(Msg::TogglePopup(*id)),
.on_press_down(if menu.item.menu_proxy().is_some() {
Msg::TogglePopup(*id)
} else {
Msg::StatusMenu((*id, status_menu::Msg::Click(0, true)))
}),
)
.on_enter(Msg::Hovered(*id))
.into()

View file

@ -20,7 +20,7 @@ pub enum Msg {
}
pub struct State {
item: StatusNotifierItem,
pub item: StatusNotifierItem,
layout: Option<Layout>,
expanded: Option<i32>,
icon_name: String,
@ -101,9 +101,19 @@ impl State {
let Some((id, is_submenu)) = self.click_event else {
return iced::Task::none();
};
let menu_proxy = self.item.menu_proxy().clone();
let item_proxy = self.item.item_proxy().clone();
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
tokio::spawn(async move {
let _ = item_proxy.provide_xdg_activation_token(token).await;
if let Err(err) = item_proxy.activate(0, 0).await {
tracing::error!(
"Error activating status notifier item without menu proxy: {err:?}"
);
}
});
return iced::Task::none();
};
tokio::spawn(async move {
let _ = item_proxy.provide_xdg_activation_token(token).await;
let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await;
@ -150,7 +160,9 @@ impl State {
}
pub fn opened(&self) {
let menu_proxy = self.item.menu_proxy().clone();
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
return;
};
tokio::spawn(async move {
let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await;
let _ = menu_proxy.about_to_show(0).await;
@ -158,7 +170,9 @@ impl State {
}
pub fn closed(&self) {
let menu_proxy = self.item.menu_proxy().clone();
let Some(menu_proxy) = self.item.menu_proxy().cloned() else {
return;
};
tokio::spawn(async move {
let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await;
});

View file

@ -10,7 +10,7 @@ use zbus::zvariant::{self, OwnedValue};
pub struct StatusNotifierItem {
name: String,
item_proxy: StatusNotifierItemProxy<'static>,
menu_proxy: DBusMenuProxy<'static>,
menu_proxy: Option<DBusMenuProxy<'static>>,
}
#[derive(Clone, Debug, zvariant::Value)]
@ -42,7 +42,22 @@ impl StatusNotifierItem {
.build()
.await?;
let menu_path = item_proxy.menu().await?;
// XX: some items will not implement this but have a menu anyway
let is_menu = item_proxy.item_is_menu().await;
let menu_path = item_proxy.menu().await;
// Why would an item say it has no menu but provide a menu path? Slack does this.
let mut is_menu = menu_path.is_ok() || is_menu.unwrap_or(false);
if !is_menu {
return Ok(Self {
name,
item_proxy,
menu_proxy: None,
});
}
let menu_path = menu_path?;
let menu_proxy = DBusMenuProxy::builder(connection)
.destination(dest.to_string())?
.path(menu_path)?
@ -52,7 +67,7 @@ impl StatusNotifierItem {
Ok(Self {
name,
item_proxy,
menu_proxy,
menu_proxy: Some(menu_proxy),
})
}
@ -62,7 +77,9 @@ impl StatusNotifierItem {
// TODO: Only fetch changed part of layout, if that's any faster
pub fn layout_subscription(&self) -> iced::Subscription<Result<Layout, String>> {
let menu_proxy = self.menu_proxy.clone();
let Some(menu_proxy) = self.menu_proxy.clone() else {
return Subscription::none();
};
Subscription::run_with_id(
format!("status-notifier-item-layout-{}", &self.name),
async move {
@ -107,8 +124,8 @@ impl StatusNotifierItem {
)
}
pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> {
&self.menu_proxy
pub fn menu_proxy(&self) -> Option<&DBusMenuProxy<'static>> {
self.menu_proxy.as_ref()
}
pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> {
@ -135,10 +152,17 @@ pub trait StatusNotifierItem {
#[zbus(property)]
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
#[zbus(property)]
fn item_is_menu(&self) -> zbus::Result<bool>;
#[zbus(signal)]
fn new_icon(&self) -> zbus::Result<()>;
fn provide_xdg_activation_token(&self, token: String) -> zbus::Result<()>;
fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;
}
#[derive(Clone, Debug)]