diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index 9f582126..0f41c149 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -7,11 +7,12 @@ use cosmic::{ widget::icon, }; -use crate::subscriptions::status_notifier_item::{Layout, StatusNotifierItem}; +use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem}; #[derive(Clone, Debug)] pub enum Msg { Layout(Result), + Icon(IconUpdate), Click(i32, bool), } @@ -19,6 +20,9 @@ pub struct State { item: StatusNotifierItem, layout: Option, expanded: Option, + icon_name: String, + // TODO handle icon with multiple sizes? + icon_pixmap: Option, } impl State { @@ -28,6 +32,8 @@ impl State { item, layout: None, expanded: None, + icon_name: String::new(), + icon_pixmap: None, }, iced::Task::none(), ) @@ -44,6 +50,27 @@ impl State { } iced::Task::none() } + Msg::Icon(update) => { + match update { + IconUpdate::Name(name) => { + self.icon_name = name; + } + IconUpdate::Pixmap(icons) => { + self.icon_pixmap = icons + .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) + }); + } + } + + iced::Task::none() + } Msg::Click(id, is_submenu) => { let menu_proxy = self.item.menu_proxy().clone(); tokio::spawn(async move { @@ -68,11 +95,11 @@ impl State { } pub fn icon_name(&self) -> &str { - self.item.icon_name() + &self.icon_name } pub fn icon_pixmap(&self) -> Option<&icon::Handle> { - self.item.icon_pixmap() + self.icon_pixmap.as_ref() } pub fn popup_view(&self) -> cosmic::Element { @@ -84,7 +111,10 @@ impl State { } pub fn subscription(&self) -> iced::Subscription { - self.item.layout_subscription().map(Msg::Layout) + iced::Subscription::batch([ + self.item.layout_subscription().map(Msg::Layout), + self.item.icon_subscription().map(Msg::Icon), + ]) } 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..79686baf 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs @@ -11,18 +11,21 @@ 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, - _item_proxy: StatusNotifierItemProxy<'static>, + item_proxy: StatusNotifierItemProxy<'static>, menu_proxy: DBusMenuProxy<'static>, } #[derive(Clone, Debug, zvariant::Value)] pub struct Icon { - width: i32, - height: i32, - bytes: Vec, + pub width: i32, + pub height: i32, + pub bytes: Vec, +} + +#[derive(Clone, Debug)] +pub enum IconUpdate { + Name(String), + Pixmap(Vec), } impl StatusNotifierItem { @@ -34,26 +37,13 @@ impl StatusNotifierItem { }; let item_proxy = StatusNotifierItemProxy::builder(connection) + // Status icons don't seem to report property changes the normal way... + .cache_properties(zbus::proxy::CacheProperties::No) .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,9 +53,7 @@ impl StatusNotifierItem { Ok(Self { name, - icon_name, - icon_pixmap, - _item_proxy: item_proxy, + item_proxy, menu_proxy, }) } @@ -74,19 +62,11 @@ 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() - } - // TODO: Only fetch changed part of layout, if that's any faster pub fn layout_subscription(&self) -> iced::Subscription> { let menu_proxy = self.menu_proxy.clone(); Subscription::run_with_id( - format!("status-notifier-item-{}", &self.name), + format!("status-notifier-item-layout-{}", &self.name), async move { let initial = futures::stream::once(get_layout(menu_proxy.clone())); let layout_updated_stream = menu_proxy.receive_layout_updated().await.unwrap(); @@ -97,6 +77,38 @@ impl StatusNotifierItem { ) } + pub fn icon_subscription(&self) -> iced::Subscription { + fn icon_events<'a>( + item_proxy: StatusNotifierItemProxy<'static>, + ) -> impl futures::Stream + 'static { + async move { + let icon_name = item_proxy.icon_name().await; + let icon_pixmap = item_proxy.icon_pixmap().await; + futures::stream::iter( + [ + icon_name.map(IconUpdate::Name), + icon_pixmap.map(IconUpdate::Pixmap), + ] + .into_iter() + .filter_map(Result::ok), + ) + } + .flatten_stream() + } + + let item_proxy = self.item_proxy.clone(); + Subscription::run_with_id( + format!("status-notifier-item-icon-{}", &self.name), + async move { + let new_icon_stream = item_proxy.receive_new_icon().await.unwrap(); + futures::stream::once(async { () }) + .chain(new_icon_stream.map(|_| ())) + .flat_map(move |()| icon_events(item_proxy.clone())) + } + .flatten_stream(), + ) + } + pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> { &self.menu_proxy } @@ -120,6 +132,9 @@ trait StatusNotifierItem { #[zbus(property)] fn menu(&self) -> zbus::Result; + + #[zbus(signal)] + fn new_icon(&self) -> zbus::Result<()>; } #[derive(Clone, Debug)]