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)), .icon_button_from_handle(icon.clone().symbolic(true)),
_ => self.core.applet.icon_button(menu.icon_name()), _ => 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)) .on_enter(Msg::Hovered(*id))
.into() .into()

View file

@ -20,7 +20,7 @@ pub enum Msg {
} }
pub struct State { pub struct State {
item: StatusNotifierItem, pub item: StatusNotifierItem,
layout: Option<Layout>, layout: Option<Layout>,
expanded: Option<i32>, expanded: Option<i32>,
icon_name: String, icon_name: String,
@ -101,9 +101,19 @@ impl State {
let Some((id, is_submenu)) = self.click_event else { let Some((id, is_submenu)) = self.click_event else {
return iced::Task::none(); return iced::Task::none();
}; };
let menu_proxy = self.item.menu_proxy().clone();
let item_proxy = self.item.item_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 { tokio::spawn(async move {
let _ = item_proxy.provide_xdg_activation_token(token).await; let _ = item_proxy.provide_xdg_activation_token(token).await;
let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await; let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await;
@ -150,7 +160,9 @@ impl State {
} }
pub fn opened(&self) { 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 { tokio::spawn(async move {
let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await; let _ = menu_proxy.event(0, "opened", &0i32.into(), 0).await;
let _ = menu_proxy.about_to_show(0).await; let _ = menu_proxy.about_to_show(0).await;
@ -158,7 +170,9 @@ impl State {
} }
pub fn closed(&self) { 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 { tokio::spawn(async move {
let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await; let _ = menu_proxy.event(0, "closed", &0i32.into(), 0).await;
}); });

View file

@ -10,7 +10,7 @@ use zbus::zvariant::{self, OwnedValue};
pub struct StatusNotifierItem { pub struct StatusNotifierItem {
name: String, name: String,
item_proxy: StatusNotifierItemProxy<'static>, item_proxy: StatusNotifierItemProxy<'static>,
menu_proxy: DBusMenuProxy<'static>, menu_proxy: Option<DBusMenuProxy<'static>>,
} }
#[derive(Clone, Debug, zvariant::Value)] #[derive(Clone, Debug, zvariant::Value)]
@ -42,7 +42,22 @@ impl StatusNotifierItem {
.build() .build()
.await?; .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) let menu_proxy = DBusMenuProxy::builder(connection)
.destination(dest.to_string())? .destination(dest.to_string())?
.path(menu_path)? .path(menu_path)?
@ -52,7 +67,7 @@ impl StatusNotifierItem {
Ok(Self { Ok(Self {
name, name,
item_proxy, 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 // TODO: Only fetch changed part of layout, if that's any faster
pub fn layout_subscription(&self) -> iced::Subscription<Result<Layout, String>> { 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( Subscription::run_with_id(
format!("status-notifier-item-layout-{}", &self.name), format!("status-notifier-item-layout-{}", &self.name),
async move { async move {
@ -107,8 +124,8 @@ impl StatusNotifierItem {
) )
} }
pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> { pub fn menu_proxy(&self) -> Option<&DBusMenuProxy<'static>> {
&self.menu_proxy self.menu_proxy.as_ref()
} }
pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> { pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> {
@ -135,10 +152,17 @@ pub trait StatusNotifierItem {
#[zbus(property)] #[zbus(property)]
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>; fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
#[zbus(property)]
fn item_is_menu(&self) -> zbus::Result<bool>;
#[zbus(signal)] #[zbus(signal)]
fn new_icon(&self) -> zbus::Result<()>; fn new_icon(&self) -> zbus::Result<()>;
fn provide_xdg_activation_token(&self, token: String) -> 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)] #[derive(Clone, Debug)]