From 720c40f5f2a56e85892d50b340d94b4967108d2b Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 26 Aug 2021 14:22:48 -0700 Subject: [PATCH] Initial implementation of StatusArea Just shows icons, and doesn't handle added/removed icons. --- src/mpris.rs | 2 +- src/status_area.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/mpris.rs b/src/mpris.rs index d1c75c07..e086e136 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -243,7 +243,7 @@ impl MprisControls { let artist = metadata .artist() .and_then(|x| x.get(0).cloned()) - .unwrap_or_else(|| String::new()); + .unwrap_or_default(); let _album = metadata.album(); // TODO diff --git a/src/status_area.rs b/src/status_area.rs index 2072eb35..a44c75db 100644 --- a/src/status_area.rs +++ b/src/status_area.rs @@ -1,6 +1,12 @@ +// TODO +// - Implement StatusNotifierWatcher if one is not running +// - Register with StatusNotiferWatcher +// - Handle signals for registered/unreigisted items + use cascade::cascade; use gtk4::{ - glib, + gio, + glib::{self, clone}, prelude::*, subclass::prelude::*, }; @@ -31,6 +37,21 @@ impl ObjectImpl for StatusAreaInner { }; self.box_.set(box_); + + glib::MainContext::default().spawn_local(clone!(@strong obj => async move { + let watcher = match StatusNotifierWatcher::new().await { + Ok(watcher) => watcher, + Err(err) => { + eprintln!("Failed to connect to 'org.kde.StatusNotifierWatcher': {}", err); + return; + } + }; + + for i in watcher.registered_status_notifier_items().await { + let image = gtk4::Image::from_icon_name(i.icon_name().as_deref()); + obj.inner().box_.append(&image); + } + })); } fn dispose(&self, _obj: &StatusArea) { @@ -54,3 +75,70 @@ impl StatusArea { StatusAreaInner::from_instance(self) } } + +struct StatusNotifierItem(gio::DBusProxy); + +impl StatusNotifierItem { + async fn new(dest: &str, path: &str) -> Result { + let proxy = gio::DBusProxy::for_bus_future( + gio::BusType::Session, + gio::DBusProxyFlags::NONE, + None, + dest, + path, + "org.kde.StatusNotifierItem", + ) + .await?; + Ok(Self(proxy)) + } + + fn property(&self, prop: &str) -> Option { + self.0.cached_property(prop)?.get() + } + + fn icon_name(&self) -> Option { + // TODO: IconThemePath? AttentionIconName? + self.property("IconName") + } + + fn menu(&self) -> Option { + // TODO: Return menu rather than just string + self.property("Menu") + } +} + +struct StatusNotifierWatcher(gio::DBusProxy); + +impl StatusNotifierWatcher { + async fn new() -> Result { + let proxy = gio::DBusProxy::for_bus_future( + gio::BusType::Session, + gio::DBusProxyFlags::NONE, + None, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + ) + .await?; + Ok(Self(proxy)) + } + + fn property(&self, prop: &str) -> Option { + self.0.cached_property(prop)?.get() + } + + async fn registered_status_notifier_items(&self) -> Vec { + let mut items = Vec::new(); + for i in self + .property::>("RegisteredStatusNotifierItems") + .unwrap_or_default() + { + let idx = i.find('/').unwrap(); + match StatusNotifierItem::new(&i[..idx], &i[idx..]).await { + Ok(item) => items.push(item), + Err(err) => eprintln!("Failed to connect to '{}': {}", i, err), + } + } + items + } +}