Iced port of status area applet

This is based on the GTK version of the status area applet that was
previously in this repository.

This exposes app indicators found over dbus. As used in applications
like nm-applet and steam.
This commit is contained in:
Ian Douglas Scott 2023-01-03 14:36:56 -08:00
parent 29a2dea760
commit 6a64486163
16 changed files with 1313 additions and 1 deletions

View file

@ -0,0 +1,207 @@
use cosmic::iced;
use futures::{FutureExt, StreamExt};
use zbus::zvariant::{self, OwnedValue};
#[derive(Clone, Debug)]
pub struct StatusNotifierItem {
name: String,
icon_name: String,
_item_proxy: StatusNotifierItemProxy<'static>,
menu_proxy: DBusMenuProxy<'static>,
}
impl StatusNotifierItem {
pub async fn new(connection: &zbus::Connection, name: String) -> zbus::Result<Self> {
let (dest, path) = if let Some(idx) = name.find('/') {
(&name[..idx], &name[idx..])
} else {
(name.as_str(), "/StatusNotifierItem")
};
let item_proxy = StatusNotifierItemProxy::builder(&connection)
.destination(dest.to_string())?
.path(path.to_string())?
.build()
.await?;
let icon_name = item_proxy.icon_name().await?;
let menu_path = item_proxy.menu().await?;
let menu_proxy = DBusMenuProxy::builder(&connection)
.destination(dest.to_string())?
.path(menu_path)?
.build()
.await?;
Ok(Self {
name,
icon_name,
_item_proxy: item_proxy,
menu_proxy,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn icon_name(&self) -> &str {
&self.icon_name
}
// TODO: Only fetch changed part of layout, if that's any faster
pub fn layout_subscription(&self) -> iced::Subscription<Result<Layout, String>> {
let menu_proxy = self.menu_proxy.clone();
iced::subscription::run_with_id(
format!("status-notifier-item-{}", &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();
let updates = layout_updated_stream.then(move |_| get_layout(menu_proxy.clone()));
initial.chain(updates)
}
.flatten_stream(),
)
}
pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> {
&self.menu_proxy
}
}
async fn get_layout(menu_proxy: DBusMenuProxy<'static>) -> Result<Layout, String> {
match menu_proxy.get_layout(0, -1, &[]).await {
Ok((_, layout)) => Ok(layout),
Err(err) => Err(err.to_string()),
}
}
#[zbus::dbus_proxy(interface = "org.kde.StatusNotifierItem")]
trait StatusNotifierItem {
#[dbus_proxy(property)]
fn icon_name(&self) -> zbus::Result<String>;
#[dbus_proxy(property)]
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
}
#[derive(Clone, Debug)]
pub struct Layout(i32, LayoutProps, Vec<Layout>);
impl<'a> serde::Deserialize<'a> for Layout {
fn deserialize<D: serde::Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
let (id, props, children) =
<(i32, LayoutProps, Vec<(zvariant::Signature<'_>, Self)>)>::deserialize(deserializer)?;
Ok(Self(id, props, children.into_iter().map(|x| x.1).collect()))
}
}
impl zvariant::Type for Layout {
fn signature() -> zvariant::Signature<'static> {
zvariant::Signature::try_from("(ia{sv}av)").unwrap()
}
}
#[derive(Clone, Debug, zvariant::DeserializeDict)]
pub struct LayoutProps {
#[zvariant(rename = "accessible-desc")]
accessible_desc: Option<String>,
#[zvariant(rename = "children-display")]
children_display: Option<String>,
label: Option<String>,
enabled: Option<bool>,
visible: Option<bool>,
#[zvariant(rename = "type")]
type_: Option<String>,
#[zvariant(rename = "toggle-type")]
toggle_type: Option<String>,
#[zvariant(rename = "toggle-state")]
toggle_state: Option<i32>,
#[zvariant(rename = "icon-data")]
icon_data: Option<Vec<u8>>,
#[zvariant(rename = "icon-name")]
icon_name: Option<String>,
disposition: Option<String>,
shortcut: Option<String>,
}
impl zvariant::Type for LayoutProps {
fn signature() -> zvariant::Signature<'static> {
zvariant::Signature::try_from("a{sv}").unwrap()
}
}
#[allow(dead_code)]
impl Layout {
pub fn id(&self) -> i32 {
self.0
}
pub fn children(&self) -> &[Self] {
&self.2
}
pub fn accessible_desc(&self) -> Option<&str> {
self.1.accessible_desc.as_deref()
}
pub fn children_display(&self) -> Option<&str> {
self.1.children_display.as_deref()
}
pub fn label(&self) -> Option<&str> {
self.1.label.as_deref()
}
pub fn enabled(&self) -> bool {
self.1.enabled.unwrap_or(true)
}
pub fn visible(&self) -> bool {
self.1.visible.unwrap_or(true)
}
pub fn type_(&self) -> Option<&str> {
self.1.type_.as_deref()
}
pub fn toggle_type(&self) -> Option<&str> {
self.1.toggle_type.as_deref()
}
pub fn toggle_state(&self) -> Option<i32> {
self.1.toggle_state
}
pub fn icon_data(&self) -> Option<&[u8]> {
self.1.icon_data.as_deref()
}
pub fn icon_name(&self) -> Option<&str> {
self.1.icon_name.as_deref()
}
pub fn disposition(&self) -> Option<&str> {
self.1.disposition.as_deref()
}
pub fn shortcut(&self) -> Option<&str> {
self.1.shortcut.as_deref()
}
}
#[zbus::dbus_proxy(interface = "com.canonical.dbusmenu")]
trait DBusMenu {
fn get_layout(
&self,
parent_id: i32,
recursion_depth: i32,
property_names: &[&str],
) -> zbus::Result<(u32, Layout)>;
fn event(&self, id: i32, event_id: &str, data: &OwnedValue, timestamp: u32)
-> zbus::Result<()>;
#[dbus_proxy(signal)]
fn layout_updated(&self, revision: u32, parent: i32) -> zbus::Result<()>;
}