From 9fca893fe1490700ea013e23cdd48c8df0d1ea25 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 26 Aug 2021 15:04:01 -0700 Subject: [PATCH] Detect registered/unregisterered in StatusArea --- src/status_area.rs | 87 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/src/status_area.rs b/src/status_area.rs index a44c75db..39ea8bca 100644 --- a/src/status_area.rs +++ b/src/status_area.rs @@ -10,12 +10,16 @@ use gtk4::{ prelude::*, subclass::prelude::*, }; +use once_cell::unsync::OnceCell; +use std::{cell::RefCell, collections::HashMap}; use crate::deref_cell::DerefCell; #[derive(Default)] pub struct StatusAreaInner { box_: DerefCell, + watcher: OnceCell, + icons: RefCell>, } #[glib::object_subclass] @@ -47,10 +51,23 @@ impl ObjectImpl for StatusAreaInner { } }; - 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); + for name in watcher.registered_items().await { + glib::MainContext::default().spawn_local(clone!(@strong obj => async move { + obj.item_registered(&name).await; + })); } + + watcher.connect_item_registered_unregistered(clone!(@strong obj => move |name, registered| { + if registered { + glib::MainContext::default().spawn_local(clone!(@strong obj => async move { + obj.item_registered(&name).await; + })); + } else { + obj.item_unregistered(&name); + } + })); + + let _ = obj.inner().watcher.set(watcher); })); } @@ -74,18 +91,41 @@ impl StatusArea { fn inner(&self) -> &StatusAreaInner { StatusAreaInner::from_instance(self) } + + async fn item_registered(&self, name: &str) { + match StatusNotifierItem::new(&name).await { + Ok(item) => { + let image = gtk4::Image::from_icon_name(item.icon_name().as_deref()); + self.inner().box_.append(&image); + + self.item_unregistered(name); + self.inner() + .icons + .borrow_mut() + .insert(name.to_owned(), image); + } + Err(err) => eprintln!("Failed to connect to '{}': {}", name, err), + } + } + + fn item_unregistered(&self, name: &str) { + if let Some(icon) = self.inner().icons.borrow_mut().remove(name) { + self.inner().box_.remove(&icon); + } + } } struct StatusNotifierItem(gio::DBusProxy); impl StatusNotifierItem { - async fn new(dest: &str, path: &str) -> Result { + async fn new(name: &str) -> Result { + let idx = name.find('/').unwrap(); let proxy = gio::DBusProxy::for_bus_future( gio::BusType::Session, gio::DBusProxyFlags::NONE, None, - dest, - path, + &name[..idx], + &name[idx..], "org.kde.StatusNotifierItem", ) .await?; @@ -127,18 +167,29 @@ impl StatusNotifierWatcher { 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") + async fn registered_items(&self) -> Vec { + 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 + } + + fn connect_item_registered_unregistered( + &self, + f: F, + ) -> glib::SignalHandlerId { + self.0 + .connect_local("g-signal", false, move |args| { + let signal_args = args[3].get::().unwrap(); + match args[2].get::().unwrap().as_str() { + "StatusNotifierItemRegistered" => { + f(signal_args.get::<(String,)>().unwrap().0, true); + } + "StatusNotifierItemUnregistered" => { + f(signal_args.get::<(String,)>().unwrap().0, false); + } + _ => {} + } + None + }) + .unwrap() } }