Skip to content

Commit

Permalink
fix: Load systray items that are registered without a path (#1230)
Browse files Browse the repository at this point in the history
Co-authored-by: ElKowar <[email protected]>
  • Loading branch information
Kage-Yami and elkowar authored Feb 5, 2025
1 parent c6decc8 commit 49b3066
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ All notable changes to eww will be listed here, starting at changes since versio
- Fix values in the `EWW_NET` variable (By: mario-kr)
- Fix the gtk `expander` widget (By: ovalkonia)
- Fix wayland monitor names support (By: dragonnn)
- Load systray items that are registered without a path (By: Kage-Yami)
- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)

### Features
Expand Down
76 changes: 44 additions & 32 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/notifier_host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ homepage = "https://github.com/elkowar/eww"

[dependencies]
dbusmenu-gtk3 = "0.1.0"
quick-xml = { version = "0.37.1", features = ["serialize"] }
serde = "1.0.215"

gtk.workspace = true
log.workspace = true
Expand Down
65 changes: 64 additions & 1 deletion crates/notifier_host/src/item.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::*;

use gtk::{self, prelude::*};
use serde::Deserialize;
use zbus::fdo::IntrospectableProxy;

/// Recognised values of [`org.freedesktop.StatusNotifierItem.Status`].
///
Expand Down Expand Up @@ -61,7 +63,12 @@ impl Item {
if let Some((addr, path)) = service.split_once('/') {
(addr.to_owned(), format!("/{}", path))
} else if service.starts_with(':') {
(service[0..6].to_owned(), names::ITEM_OBJECT.to_owned())
(
service.to_owned(),
resolve_pathless_address(con, service, "/".to_owned())
.await?
.ok_or_else(|| zbus::Error::Failure(format!("no StatusNotifierItem found for {service}")))?,
)
} else {
return Err(zbus::Error::Address(service.to_owned()));
}
Expand Down Expand Up @@ -105,3 +112,59 @@ impl Item {
load_icon_from_sni(&self.sni, size, scale).await
}
}

#[derive(Deserialize)]
struct DBusNode {
#[serde(default)]
interface: Vec<DBusInterface>,

#[serde(default)]
node: Vec<DBusNode>,

#[serde(rename = "@name")]
name: Option<String>,
}

#[derive(Deserialize)]
struct DBusInterface {
#[serde(rename = "@name")]
name: String,
}

async fn resolve_pathless_address(con: &zbus::Connection, service: &str, path: String) -> zbus::Result<Option<String>> {
let introspection_xml =
IntrospectableProxy::builder(con).destination(service)?.path(path.as_str())?.build().await?.introspect().await?;

let dbus_node =
quick_xml::de::from_str::<DBusNode>(&introspection_xml).map_err(|err| zbus::Error::Failure(err.to_string()))?;

if dbus_node.interface.iter().any(|interface| interface.name == "org.kde.StatusNotifierItem") {
// This item implements the desired interface, so bubble it back up
Ok(Some(path))
} else {
for node in dbus_node.node {
if let Some(name) = node.name {
if name == "StatusNotifierItem" {
// If this exists, then there's a good chance DBus may not think anything
// implements the desired interface, so just bubble this up instead.
return Ok(Some(join_to_path(&path, name)));
}

let path = Box::pin(resolve_pathless_address(con, service, join_to_path(&path, name))).await?;

if path.is_some() {
// Return the first item found from a child
return Ok(path);
}
}
}

// No children had the item we want...
Ok(None)
}
}

fn join_to_path(path: &str, name: String) -> String {
// Make sure we don't double-up on the leading slash
format!("{path}/{name}", path = if path == "/" { "" } else { path })
}

0 comments on commit 49b3066

Please sign in to comment.