diff --git a/cosmic-applet-status-area/src/components/app.rs b/cosmic-applet-status-area/src/components/app.rs index 615b456a..0a72effa 100644 --- a/cosmic-applet-status-area/src/components/app.rs +++ b/cosmic-applet-status-area/src/components/app.rs @@ -244,14 +244,10 @@ impl cosmic::Application for App { fn view(&self) -> cosmic::Element<'_, Msg> { let children = self.menus.iter().map(|(id, menu)| { mouse_area( - match menu.icon_pixmap() { - Some(icon) if menu.icon_name() == "" => self - .core - .applet - .icon_button_from_handle(icon.clone().symbolic(true)), - _ => self.core.applet.icon_button(menu.icon_name()), - } - .on_press_down(Msg::TogglePopup(*id)), + self.core + .applet + .icon_button_from_handle(menu.icon_handle().into()) + .on_press_down(Msg::TogglePopup(*id)), ) .on_enter(Msg::Hovered(*id)) .into() diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index ffb5bf73..eb0f2070 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -3,17 +3,20 @@ use cosmic::{applet::menu_button, iced, widget::icon}; -use crate::subscriptions::status_notifier_item::{Layout, StatusNotifierItem}; +use crate::subscriptions::status_notifier_item::{IconNameOrPixmap, Layout, StatusNotifierItem}; #[derive(Clone, Debug)] pub enum Msg { + Icon(Option), Layout(Result), Click(i32, bool), } +#[derive(Debug)] pub struct State { item: StatusNotifierItem, layout: Option, + icon: Option, expanded: Option, } @@ -24,6 +27,7 @@ impl State { item, layout: None, expanded: None, + icon: None, }, iced::Task::none(), ) @@ -31,6 +35,10 @@ impl State { pub fn update(&mut self, message: Msg) -> iced::Task { match message { + Msg::Icon(icon) => { + self.icon = icon; + iced::Command::none() + } Msg::Layout(layout) => { match layout { Ok(layout) => { @@ -63,12 +71,10 @@ impl State { self.item.name() } - pub fn icon_name(&self) -> &str { - self.item.icon_name() - } - - pub fn icon_pixmap(&self) -> Option<&icon::Handle> { - self.item.icon_pixmap() + pub fn icon_handle(&self) -> icon::Handle { + self.icon.as_ref() + .map(|i| i.clone().into()) + .unwrap_or_else(|| icon::from_raster_bytes(&[])) } pub fn popup_view(&self) -> cosmic::Element { @@ -80,7 +86,12 @@ impl State { } pub fn subscription(&self) -> iced::Subscription { - self.item.layout_subscription().map(Msg::Layout) + let subs = vec![ + self.item.icon_subscription().map(Msg::Icon), + self.item.layout_subscription().map(Msg::Layout), + ]; + + iced::Subscription::batch(subs) } pub fn opened(&self) { diff --git a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs index 43593fe1..6ae744bd 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs @@ -11,9 +11,8 @@ use zbus::zvariant::{self, OwnedValue}; #[derive(Clone, Debug)] pub struct StatusNotifierItem { name: String, - icon_name: String, - // TODO Handle icon with multiple sizes? - icon_pixmap: Option, + // icon_name: String, + // icon_pixmap: Option, _item_proxy: StatusNotifierItemProxy<'static>, menu_proxy: DBusMenuProxy<'static>, } @@ -25,6 +24,28 @@ pub struct Icon { bytes: Vec, } +#[derive(Clone, Debug)] +pub enum IconNameOrPixmap { + Name(String), + Pixmap(Icon), +} + +impl From for icon::Handle { + fn from(value: IconNameOrPixmap) -> Self { + match value { + IconNameOrPixmap::Name(name) => icon::from_name(name).symbolic(true).into(), + IconNameOrPixmap::Pixmap(i) => { + let mut i = i.clone(); + // Convert ARGB to RGBA + for pixel in i.bytes.chunks_exact_mut(4) { + pixel.rotate_left(1); + } + icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes).symbolic(true) + } + } + } +} + impl StatusNotifierItem { pub async fn new(connection: &zbus::Connection, name: String) -> zbus::Result { let (dest, path) = if let Some(idx) = name.find('/') { @@ -34,26 +55,12 @@ impl StatusNotifierItem { }; let item_proxy = StatusNotifierItemProxy::builder(connection) + .cache_properties(zbus::proxy::CacheProperties::Yes) .destination(dest.to_string())? .path(path.to_string())? .build() .await?; - let icon_name = item_proxy.icon_name().await.unwrap_or_default(); - let icon_pixmap = item_proxy - .icon_pixmap() - .await - .unwrap_or_default() - .into_iter() - .max_by_key(|i| (i.width, i.height)) - .map(|mut i| { - // Convert ARGB to RGBA - for pixel in i.bytes.chunks_exact_mut(4) { - pixel.rotate_left(1); - } - icon::from_raster_pixels(i.width as u32, i.height as u32, i.bytes) - }); - let menu_path = item_proxy.menu().await?; let menu_proxy = DBusMenuProxy::builder(connection) .destination(dest.to_string())? @@ -63,8 +70,6 @@ impl StatusNotifierItem { Ok(Self { name, - icon_name, - icon_pixmap, _item_proxy: item_proxy, menu_proxy, }) @@ -74,12 +79,21 @@ impl StatusNotifierItem { &self.name } - pub fn icon_name(&self) -> &str { - &self.icon_name - } - - pub fn icon_pixmap(&self) -> Option<&icon::Handle> { - self.icon_pixmap.as_ref() + pub fn icon_subscription(&self) -> iced::Subscription> { + let item_proxy = self._item_proxy.clone(); + iced::subscription::run_with_id( + format!("status-notifier-newicon-{}", &self.name), + async move { + let initial = futures::stream::once(get_icon(item_proxy.clone())); + let updates = item_proxy + .receive_new_icon() + .await + .unwrap() + .then(move |_| get_icon(item_proxy.clone())); + initial.chain(updates) + } + .flatten_stream(), + ) } // TODO: Only fetch changed part of layout, if that's any faster @@ -109,6 +123,23 @@ async fn get_layout(menu_proxy: DBusMenuProxy<'static>) -> Result) -> Option { + if let Ok(pixmaps) = item_proxy.icon_pixmap().await { + // TODO Handle icon with multiple sizes? + return Some(IconNameOrPixmap::Pixmap( + pixmaps.into_iter().max_by_key(|i| (i.width, i.height))?, + )); + } + + if let Ok(icon_name) = item_proxy.icon_name().await { + if icon_name != "" { + return Some(IconNameOrPixmap::Name(icon_name)); + } + } + + None +} + #[zbus::proxy(interface = "org.kde.StatusNotifierItem")] trait StatusNotifierItem { #[zbus(property)] @@ -120,6 +151,9 @@ trait StatusNotifierItem { #[zbus(property)] fn menu(&self) -> zbus::Result; + + #[zbus(signal)] + fn new_icon(&self) -> zbus::Result<()>; } #[derive(Clone, Debug)]