status bar
git clone https://git.lucas.co/cce-status-interface.git
Update system configuration and interface modules
Cargo.lock | 228 ++++++++++++++++++++++++++++++-
Cargo.toml | 3 +
src/bin/check_metrics.rs | 15 +++
src/bin/dbus_test.rs | 285 +++++++++++++++++++--------------------
src/main.rs | 340 ++++++++++++++++++++++++++++++++++++++++-------
5 files changed, 671 insertions(+), 200 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index ad8cf0c..4b729e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
[[package]]
name = "arrayvec"
version = "0.7.6"
@@ -186,6 +192,12 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -350,6 +362,7 @@ dependencies = [
"png",
"pollster",
"raw-window-handle",
+ "resvg",
"serde",
"serde_json",
"smithay-client-toolkit",
@@ -371,6 +384,7 @@ dependencies = [
"glyphon",
"pollster",
"raw-window-handle",
+ "resvg",
"smithay-client-toolkit",
"tokio",
"wayland-client",
@@ -388,6 +402,12 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@@ -436,7 +456,7 @@ dependencies = [
"rangemap",
"rayon",
"rustc-hash 1.1.0",
- "rustybuzz",
+ "rustybuzz 0.14.1",
"self_cell",
"swash",
"sys-locale",
@@ -506,6 +526,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
+[[package]]
+name = "data-url"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -660,6 +686,12 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+
[[package]]
name = "foldhash"
version = "0.1.5"
@@ -681,7 +713,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
dependencies = [
- "roxmltree",
+ "roxmltree 0.20.0",
]
[[package]]
@@ -810,6 +842,16 @@ dependencies = [
"wasip2",
]
+[[package]]
+name = "gif"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
[[package]]
name = "gl_generator"
version = "0.14.0"
@@ -969,6 +1011,12 @@ dependencies = [
"cc",
]
+[[package]]
+name = "imagesize"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
+
[[package]]
name = "indexmap"
version = "2.14.0"
@@ -1013,6 +1061,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
+
[[package]]
name = "js-sys"
version = "0.3.99"
@@ -1042,6 +1096,17 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+[[package]]
+name = "kurbo"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "smallvec",
+]
+
[[package]]
name = "libc"
version = "0.2.186"
@@ -1303,6 +1368,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "pico-args"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -1515,6 +1586,38 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
+[[package]]
+name = "resvg"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2327ced609dadeed3e9702fec3e6b2ddd208758a9268d13e06566c6101ba533"
+dependencies = [
+ "gif",
+ "jpeg-decoder",
+ "log",
+ "pico-args",
+ "png",
+ "rgb",
+ "svgtypes",
+ "tiny-skia",
+ "usvg",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
+
[[package]]
name = "roxmltree"
version = "0.20.0"
@@ -1565,6 +1668,22 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+[[package]]
+name = "rustybuzz"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88117946aa1bfb53c2ae0643ceac6506337f44887f8c9fbfb43587b1cc52ba49"
+dependencies = [
+ "bitflags 2.11.1",
+ "bytemuck",
+ "smallvec",
+ "ttf-parser 0.20.0",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-properties",
+ "unicode-script",
+]
+
[[package]]
name = "rustybuzz"
version = "0.14.1"
@@ -1687,6 +1806,21 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+[[package]]
+name = "simplecss"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
[[package]]
name = "skrifa"
version = "0.22.3"
@@ -1771,6 +1905,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+dependencies = [
+ "float-cmp",
+]
+
[[package]]
name = "strum"
version = "0.26.3"
@@ -1799,6 +1942,16 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
+[[package]]
+name = "svgtypes"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
+dependencies = [
+ "kurbo",
+ "siphasher",
+]
+
[[package]]
name = "swash"
version = "0.1.19"
@@ -1892,6 +2045,32 @@ dependencies = [
"syn",
]
+[[package]]
+name = "tiny-skia"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
[[package]]
name = "tinyvec"
version = "1.11.0"
@@ -2084,6 +2263,12 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
[[package]]
name = "unicode-width"
version = "0.1.14"
@@ -2096,6 +2281,33 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+[[package]]
+name = "usvg"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c704361d822337cfc00387672c7b59eaa72a1f0744f62b2a68aa228a0c6927d"
+dependencies = [
+ "base64",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "imagesize",
+ "kurbo",
+ "log",
+ "pico-args",
+ "roxmltree 0.19.0",
+ "rustybuzz 0.13.0",
+ "simplecss",
+ "siphasher",
+ "strict-num",
+ "svgtypes",
+ "tiny-skia-path",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
[[package]]
name = "version_check"
version = "0.9.5"
@@ -2277,6 +2489,12 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
[[package]]
name = "wgpu"
version = "24.0.5"
@@ -2666,6 +2884,12 @@ version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
[[package]]
name = "yazi"
version = "0.1.6"
diff --git a/Cargo.toml b/Cargo.toml
index dad0cb6..a0547ef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,9 +21,12 @@ serde_json = "1"
chrono = "0.4"
tokio-stream = "0.1"
zbus = "4"
+resvg = "0.41.0"
png = "0.17"
+
+
diff --git a/src/bin/check_metrics.rs b/src/bin/check_metrics.rs
new file mode 100644
index 0000000..b6ea506
--- /dev/null
+++ b/src/bin/check_metrics.rs
@@ -0,0 +1,15 @@
+use glyphon::{FontSystem, Buffer, Metrics, Attrs, Shaping};
+
+fn main() {
+ let mut font_system = FontSystem::new();
+ let font_size = 11.0;
+ let metrics = Metrics::new(font_size, font_size * 1.4);
+ let mut buffer = Buffer::new(&mut font_system, metrics);
+ let attrs = Attrs::new().family(glyphon::Family::Name("Berkeley Mono"));
+ buffer.set_text(&mut font_system, "Vol 0%", attrs, Shaping::Advanced);
+ buffer.shape_until_scroll(&mut font_system, true);
+
+ for run in buffer.layout_runs() {
+ println!("line_y: {}, line_w: {}", run.line_y, run.line_w);
+ }
+}
diff --git a/src/bin/dbus_test.rs b/src/bin/dbus_test.rs
index ab20bd3..dd29326 100644
--- a/src/bin/dbus_test.rs
+++ b/src/bin/dbus_test.rs
@@ -1,53 +1,8 @@
use zbus::{proxy, Connection};
-
-#[derive(Debug, Clone)]
-pub struct NotifierAddress {
- pub destination: String,
- pub path: String,
-}
-
-impl NotifierAddress {
- pub fn from_notifier_service(service: &str, sender: &str) -> Result<Self, String> {
- if service.starts_with('/') {
- Ok(NotifierAddress {
- destination: sender.to_string(),
- path: service.to_string(),
- })
- } else if let Some((destination, path)) = service.split_once('/') {
- Ok(NotifierAddress {
- destination: destination.to_string(),
- path: format!("/{}", path),
- })
- } else if service.contains(':') {
- let split = service.split(':').collect::<Vec<&str>>();
- Ok(NotifierAddress {
- destination: format!(":{}", split[1]),
- path: "/StatusNotifierItem".to_string(),
- })
- } else {
- Ok(NotifierAddress {
- destination: service.to_string(),
- path: "/StatusNotifierItem".to_string(),
- })
- }
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct TrayPixmap {
- pub width: i32,
- pub height: i32,
- pub pixels: Vec<u8>,
-}
-
-#[derive(Debug, Clone)]
-pub struct TrayItem {
- pub id: String,
- pub icon_name: Option<String>,
- pub icon_theme_path: Option<String>,
- pub pixmaps: Option<Vec<TrayPixmap>>,
- pub title: Option<String>,
-}
+use std::collections::HashMap;
+use zbus::zvariant::{OwnedValue, Value};
+use std::process::{Command, Stdio};
+use std::io::Write;
#[proxy(
interface = "org.kde.StatusNotifierItem",
@@ -55,127 +10,159 @@ pub struct TrayItem {
)]
trait StatusNotifierItem {
#[zbus(property)]
- fn id(&self) -> zbus::Result<String>;
-
- #[zbus(property)]
- fn category(&self) -> zbus::Result<String>;
-
- #[zbus(property)]
- fn status(&self) -> zbus::Result<String>;
+ fn item_is_menu(&self) -> zbus::Result<bool>;
#[zbus(property)]
- fn title(&self) -> zbus::Result<String>;
-
- #[zbus(property)]
- fn icon_name(&self) -> zbus::Result<String>;
-
- #[zbus(property)]
- fn icon_theme_path(&self) -> zbus::Result<String>;
-
- #[zbus(property)]
- fn icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;
+ fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
}
#[proxy(
- interface = "org.kde.StatusNotifierWatcher",
- default_path = "/StatusNotifierWatcher"
+ interface = "com.canonical.dbusmenu",
+ default_path = "/StatusNotifierItem/menu"
)]
-trait StatusNotifierWatcher {
- #[zbus(property)]
- fn registered_status_notifier_items(&self) -> zbus::Result<Vec<String>>;
+trait DBusMenu {
+ fn get_layout(
+ &self,
+ parent_id: i32,
+ recursion_depth: i32,
+ property_names: Vec<String>,
+ ) -> zbus::Result<(u32, (i32, HashMap<String, OwnedValue>, Vec<OwnedValue>))>;
+
+ fn event(
+ &self,
+ id: i32,
+ event_id: &str,
+ data: &zbus::zvariant::Value<'_>,
+ timestamp: u32,
+ ) -> zbus::Result<()>;
+
+ fn about_to_show(&self, id: i32) -> zbus::Result<bool>;
}
-async fn fetch_tray_item(conn: &Connection, addr: &NotifierAddress) -> Result<TrayItem, zbus::Error> {
- let proxy = StatusNotifierItemProxy::builder(conn)
- .destination(addr.destination.clone())?
- .path(addr.path.clone())?
- .build()
- .await?;
-
- println!("Querying properties for destination='{}', path='{}'...", addr.destination, addr.path);
-
- let id_res = proxy.id().await;
- println!(" id result: {:?}", id_res);
-
- let category_res = proxy.category().await;
- println!(" category result: {:?}", category_res);
-
- let status_res = proxy.status().await;
- println!(" status result: {:?}", status_res);
-
- let icon_name_res = proxy.icon_name().await;
- println!(" icon_name result: {:?}", icon_name_res);
-
- let icon_theme_path_res = proxy.icon_theme_path().await;
- println!(" icon_theme_path result: {:?}", icon_theme_path_res);
-
- let title_res = proxy.title().await;
- println!(" title result: {:?}", title_res);
-
- let pixmap_res = proxy.icon_pixmap().await;
- println!(" pixmap result: ({} pixmaps or error: {:?})",
- pixmap_res.as_ref().map(|v| v.len()).unwrap_or(0),
- pixmap_res.as_ref().err()
- );
-
- let id = format!("{}/{}", addr.destination, addr.path.trim_start_matches('/'));
- let icon_name = icon_name_res.ok();
- let icon_theme_path = icon_theme_path_res.ok();
- let title = title_res.ok();
+fn flatten_menu(
+ id: i32,
+ mut properties: HashMap<String, OwnedValue>,
+ children: Vec<OwnedValue>,
+ prefix: &str,
+ out: &mut Vec<(i32, String)>
+) {
+ let label: String = properties.remove("label")
+ .and_then(|v| {
+ let s: Result<String, _> = v.try_into();
+ s.ok()
+ })
+ .unwrap_or_default();
+ let type_: String = properties.remove("type")
+ .and_then(|v| {
+ let s: Result<String, _> = v.try_into();
+ s.ok()
+ })
+ .unwrap_or_default();
+ let enabled: bool = properties.remove("enabled")
+ .and_then(|v| {
+ let b: Result<bool, _> = v.try_into();
+ b.ok()
+ })
+ .unwrap_or(true);
+
+ if type_ == "separator" || !enabled {
+ // Skip separator or disabled items
+ } else {
+ let current_path = if prefix.is_empty() {
+ label.clone()
+ } else if !label.is_empty() {
+ format!("{} > {}", prefix, label)
+ } else {
+ prefix.to_string()
+ };
- let pixmaps = pixmap_res.ok().map(|v| {
- v.into_iter()
- .map(|(w, h, pixels)| TrayPixmap {
- width: w,
- height: h,
- pixels,
- })
- .collect()
- });
+ if !current_path.is_empty() && children.is_empty() {
+ out.push((id, current_path.clone()));
+ }
- Ok(TrayItem {
- id,
- icon_name,
- icon_theme_path,
- pixmaps,
- title,
- })
+ for child_val in children {
+ let child_val_inner = zbus::zvariant::Value::from(child_val);
+ if let Ok(child) = <(i32, HashMap<String, OwnedValue>, Vec<OwnedValue>)>::try_from(child_val_inner) {
+ flatten_menu(child.0, child.1, child.2, ¤t_path, out);
+ }
+ }
+ }
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let conn = Connection::session().await?;
- let watcher_proxy = StatusNotifierWatcherProxy::builder(&conn)
- .destination("org.kde.StatusNotifierWatcher")?
- .path("/StatusNotifierWatcher")?
+ let destination = "org.kde.StatusNotifierItem-997-1";
+ let path = "/StatusNotifierItem";
+
+ println!("Connecting to StatusNotifierItem at destination='{}', path='{}'...", destination, path);
+ let sni_proxy = StatusNotifierItemProxy::builder(&conn)
+ .destination(destination)?
+ .path(path)?
.build()
.await?;
- let items = watcher_proxy.registered_status_notifier_items().await?;
- println!("Registered status notifier items from watcher: {:?}", items);
-
- for item in items {
- println!("======================================");
- // An item string can be like ":1.5631/org/ayatana/NotificationItem/dropbox_client_1112673"
- // Let's parse it using split_once('/')
- let addr = if let Some((destination, path)) = item.split_once('/') {
- NotifierAddress {
- destination: destination.to_string(),
- path: format!("/{}", path),
- }
- } else {
- NotifierAddress {
- destination: item.clone(),
- path: "/StatusNotifierItem".to_string(),
- }
- };
+ let is_menu = sni_proxy.item_is_menu().await.unwrap_or(false);
+ let menu_path = sni_proxy.menu().await?;
+
+ println!("is_menu: {}, menu_path: {}", is_menu, menu_path.as_str());
+
+ let menu_proxy = DBusMenuProxy::builder(&conn)
+ .destination(destination)?
+ .path(menu_path.as_str())?
+ .build()
+ .await?;
- match fetch_tray_item(&conn, &addr).await {
- Ok(item) => println!("SUCCESS: {:?}", item.id),
- Err(e) => println!("ERROR for {}: {:?}", item, e),
+ // Call about_to_show
+ let _ = menu_proxy.about_to_show(0).await;
+
+ let (revision, layout) = menu_proxy.get_layout(0, 3, vec![]).await?;
+ println!("Menu revision: {}", revision);
+
+ let mut items = Vec::new();
+ flatten_menu(layout.0, layout.1, layout.2, "", &mut items);
+
+ println!("Flattened items count: {}", items.len());
+
+ // Format input for fuzzel
+ let mut fuzzel_input = String::new();
+ for (_, label) in &items {
+ fuzzel_input.push_str(label);
+ fuzzel_input.push('\n');
+ }
+
+ // Spawn fuzzel
+ println!("Spawning fuzzel...");
+ let mut child = Command::new("fuzzel")
+ .args(["-dmenu", "-p", "Tray Menu:"])
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?;
+
+ if let Some(mut stdin) = child.stdin.take() {
+ stdin.write_all(fuzzel_input.as_bytes())?;
+ }
+
+ let output = child.wait_with_output()?;
+ if output.status.success() {
+ let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ println!("Selected: '{}'", selected);
+ if let Some((id, _)) = items.iter().find(|(_, label)| label == &selected) {
+ println!("Triggering event on item ID: {}", id);
+ let timestamp = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_secs() as u32;
+ let val = Value::from("");
+ menu_proxy.event(*id, "clicked", &val, timestamp).await?;
+ println!("Event sent successfully!");
+ } else {
+ println!("Selection match not found in items list!");
}
+ } else {
+ println!("Fuzzel was cancelled or failed");
}
-
+
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index 3a81811..f044f89 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -417,13 +417,14 @@ impl StatusApp {
fn rebuild_layout(&mut self) {
let font_family = read_status_font_from_config();
+ let font_size = read_status_font_size_from_config();
let sw = self.width as f32;
let sh = self.height as f32;
let s = self.scale_factor as f32;
let sw_logical = sw / s;
let sh_logical = sh / s;
let bar_h = 28.0;
- eprintln!("[rebuild_layout] sw={}, sh={}, s={}, sw_logical={}, sh_logical={}, font_family={}", sw, sh, s, sw_logical, sh_logical, font_family);
+ eprintln!("[rebuild_layout] sw={}, sh={}, s={}, sw_logical={}, sh_logical={}, font_family={}, font_size={}", sw, sh, s, sw_logical, sh_logical, font_family, font_size);
self.current_bg_color = read_bg_color_from_config().unwrap_or(color::STATUS_BG);
eprintln!("[rebuild_layout] Using background color: {:?}", self.current_bg_color);
@@ -450,8 +451,8 @@ impl StatusApp {
let tags = parse_tags(&self.tags);
for (col, text) in tags {
let label_str = format!(" {} ", text);
- let label = Label::new_with_family(&mut self.font_system, &label_str, 11.0, col, &font_family);
- let line_w = label.draw(&mut self.text_items, left_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &label_str, font_size, col, &font_family);
+ let line_w = label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
self.tag_bounds.push(TagBounds {
name: text.clone(),
x: left_x,
@@ -468,8 +469,8 @@ impl StatusApp {
// 3. Layout Mode
if !self.layout.is_empty() {
let label_str = format!("[{}]", self.layout);
- let label = Label::new_with_family(&mut self.font_system, &label_str, 11.0, color::TEXT_ACCENT, &font_family);
- let line_w = label.draw(&mut self.text_items, left_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &label_str, font_size, color::TEXT_ACCENT, &font_family);
+ let line_w = label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
left_x += line_w + 16.0;
}
@@ -479,26 +480,34 @@ impl StatusApp {
if display_title.chars().count() > 40 {
display_title = display_title.chars().take(37).collect::<String>() + "...";
}
- let label = Label::new_with_family(&mut self.font_system, &display_title, 11.0, color::TEXT_FG, &font_family);
- label.draw(&mut self.text_items, left_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &display_title, font_size, color::TEXT_FG, &font_family);
+ label.draw(&mut self.text_items, left_x, (bar_h - font_size * 1.4) / 2.0);
}
// 5. Right Side Stats (CPU, Mem, Bat, Clock)
let mut right_x = sw_logical - 12.0;
if let Some(ref stats) = self.stats {
+ let clock_w = 270.0;
+ let battery_w = 80.0;
+ let volume_w = 80.0;
+ let memory_w = 140.0;
+ let cpu_w = 90.0;
+
// Clock
- let label = Label::new_with_family(&mut self.font_system, &stats.clock, 11.0, color::TEXT_FG, &font_family);
- right_x -= label.w;
- eprintln!("[rebuild_layout] Clock: x={}, w={}", right_x, label.w);
- label.draw(&mut self.text_items, right_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &stats.clock, font_size, color::TEXT_FG, &font_family);
+ right_x -= clock_w;
+ let draw_x = right_x + (clock_w - label.w); // right-aligned
+ eprintln!("[rebuild_layout] Clock: x={}, w={}", draw_x, label.w);
+ label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
// Battery
if !stats.battery.is_empty() {
right_x -= 16.0;
- let label = Label::new_with_family(&mut self.font_system, &stats.battery, 11.0, color::TEXT_ACCENT, &font_family);
- right_x -= label.w;
- eprintln!("[rebuild_layout] Battery: x={}, w={}", right_x, label.w);
- label.draw(&mut self.text_items, right_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &stats.battery, font_size, color::TEXT_ACCENT, &font_family);
+ right_x -= battery_w;
+ let draw_x = right_x; // left-aligned
+ eprintln!("[rebuild_layout] Battery: x={}, w={}", draw_x, label.w);
+ label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
}
// Volume
@@ -510,11 +519,11 @@ impl StatusApp {
} else {
color::TEXT_ACCENT
};
- let label = Label::new_with_family(&mut self.font_system, &stats.volume, 11.0, color_val, &font_family)
+ let label = Label::new_with_family(&mut self.font_system, &stats.volume, font_size, color_val, &font_family)
.with_strikethrough(is_muted);
- right_x -= label.w;
- let start_x = right_x;
- let start_y = (bar_h - 11.0 * 1.4) / 2.0;
+ right_x -= volume_w;
+ let start_x = right_x; // left-aligned
+ let start_y = (bar_h - font_size * 1.4) / 2.0;
eprintln!("[rebuild_layout] Volume: x={}, w={}, is_muted={}", start_x, label.w, is_muted);
if let Some((sx, sy, sw_rect, sh_rect, scol)) = label.strikethrough_rect(start_x, start_y, 1.0) {
eprintln!("[rebuild_layout] Strikethrough rect: sx={}, sy={}, sw={}, sh={}, scol={:?}", sx, sy, sw_rect, sh_rect, scol);
@@ -523,7 +532,7 @@ impl StatusApp {
y: sy,
w: sw_rect,
h: sh_rect,
- color: scol,
+ color: color::to_linear(scol),
});
}
label.draw(&mut self.text_items, start_x, start_y);
@@ -531,17 +540,19 @@ impl StatusApp {
// Memory
right_x -= 16.0;
- let label = Label::new_with_family(&mut self.font_system, &stats.memory, 11.0, color::TEXT_DIM, &font_family);
- right_x -= label.w;
- eprintln!("[rebuild_layout] Memory: x={}, w={}", right_x, label.w);
- label.draw(&mut self.text_items, right_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &stats.memory, font_size, color::TEXT_DIM, &font_family);
+ right_x -= memory_w;
+ let draw_x = right_x; // left-aligned
+ eprintln!("[rebuild_layout] Memory: x={}, w={}", draw_x, label.w);
+ label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
// CPU
right_x -= 16.0;
- let label = Label::new_with_family(&mut self.font_system, &stats.cpu, 11.0, color::TEXT_DIM, &font_family);
- right_x -= label.w;
- eprintln!("[rebuild_layout] CPU: x={}, w={}", right_x, label.w);
- label.draw(&mut self.text_items, right_x, (bar_h - 11.0 * 1.4) / 2.0);
+ let label = Label::new_with_family(&mut self.font_system, &stats.cpu, font_size, color::TEXT_DIM, &font_family);
+ right_x -= cpu_w;
+ let draw_x = right_x; // left-aligned
+ eprintln!("[rebuild_layout] CPU: x={}, w={}", draw_x, label.w);
+ label.draw(&mut self.text_items, draw_x, (bar_h - font_size * 1.4) / 2.0);
}
// 5b. System Tray Icons (render to the left of the CPU/stats block)
@@ -558,6 +569,7 @@ impl StatusApp {
let y = (bar_h - icon_size) / 2.0;
// Record bounds for hit-testing
+ eprintln!("[rebuild_layout] Tray icon bounds: id={}, x={}, y={}, w={}, h={}", item.id, x, y, icon_size, icon_size);
self.tray_item_bounds.push(TrayIconBounds {
id: item.id.clone(),
x,
@@ -571,7 +583,8 @@ impl StatusApp {
let mut drawn_pixmap = false;
if let Some(ref pixmaps) = item.pixmaps {
if !pixmaps.is_empty() {
- if let Some(pixmap) = pixmaps.iter().min_by_key(|p| (p.width - 16).abs()) {
+ let target_pixel_width = (icon_size * self.scale_factor as f32) as i32;
+ if let Some(pixmap) = pixmaps.iter().min_by_key(|p| (p.width - target_pixel_width).abs()) {
if pixmap.width > 0 && pixmap.height > 0 {
// Calculate average brightness of visible pixels to see if we need to recolor
let mut total_brightness = 0.0;
@@ -599,8 +612,12 @@ impl StatusApp {
};
let recolor_light = avg_brightness < 0.35;
- let draw_w = 16;
- let draw_h = 16;
+ let mut draw_w = pixmap.width;
+ let mut draw_h = pixmap.height;
+ if draw_w > 48 {
+ draw_w = 48;
+ draw_h = 48;
+ }
let pixel_w = icon_size / draw_w as f32;
let pixel_h = icon_size / draw_h as f32;
for row in 0..draw_h {
@@ -666,10 +683,10 @@ impl StatusApp {
"⚙"
};
- let buf = make_text_buffer(&mut self.font_system, symbol, 11.0, &font_family);
+ let buf = make_text_buffer(&mut self.font_system, symbol, font_size, &font_family);
let tw = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
let tx = x + (icon_size - tw) / 2.0;
- let ty = y + (icon_size - 11.0 * 1.4) / 2.0;
+ let ty = y + (icon_size - font_size * 1.4) / 2.0;
self.text_items.push(TextItem {
buffer: buf,
x: tx,
@@ -690,13 +707,13 @@ impl StatusApp {
if let Some(ref hovered_id) = self.hovered_tray_item {
if let Some(bound) = self.tray_item_bounds.iter().find(|b| &b.id == hovered_id) {
let tooltip_text = bound.title.as_deref().unwrap_or(bound.id.as_str());
- let font_size = 10.0;
- let buf = make_text_buffer(&mut self.font_system, tooltip_text, font_size, &font_family);
+ let tooltip_font_size = font_size - 1.0;
+ let buf = make_text_buffer(&mut self.font_system, tooltip_text, tooltip_font_size, &font_family);
let text_w = buf.layout_runs().next().map(|r| r.line_w).unwrap_or(0.0);
let padding = 6.0;
let tooltip_w = text_w + padding * 2.0;
- let tooltip_w_h = font_size * 1.4 + padding * 2.0;
+ let tooltip_w_h = tooltip_font_size * 1.4 + padding * 2.0;
let tx = bound.x + (bound.w - tooltip_w) / 2.0;
let ty = bar_h + 4.0;
@@ -1036,6 +1053,7 @@ impl PointerHandler for AppState {
use smithay_client_toolkit::seat::pointer::PointerEventKind;
for event in events {
let (x, y) = event.position;
+ eprintln!("[pointer] Event: pos=({:.1}, {:.1}), kind={:?}", x, y, event.kind);
if let Some(ref mut st) = self.state {
st.cursor_pos = (x, y);
}
@@ -1099,11 +1117,32 @@ impl PointerHandler for AppState {
.build()
.await
{
- if btn == 272 {
- println!("[tray-click] Calling Activate on {} at ({}, {})", id, cx_i, cy_i);
- let _ = proxy.activate(cx_i, cy_i).await;
+ let is_menu = proxy.item_is_menu().await.unwrap_or(false);
+ let menu_path = proxy.menu().await.ok();
+
+ let should_show_menu = (btn == 273 && menu_path.is_some())
+ || (btn == 272 && is_menu && menu_path.is_some());
+
+ if should_show_menu {
+ if let Some(menu_p) = menu_path {
+ eprintln!("[tray-click] Displaying menu for {} at path {}", id, menu_p.as_str());
+ if let Err(e) = show_clear_cloud_menu(&conn, destination, menu_p.as_str()).await {
+ eprintln!("[tray-click] show_clear_cloud_menu failed: {:?}", e);
+ }
+ }
+ } else if btn == 272 {
+ eprintln!("[tray-click] Calling Activate on {} at ({}, {})", id, cx_i, cy_i);
+ if let Err(e) = proxy.activate(cx_i, cy_i).await {
+ eprintln!("[tray-click] Activate failed: {:?}", e);
+ if let Some(menu_p) = menu_path {
+ eprintln!("[tray-click] Fallback: Displaying menu for {}", id);
+ if let Err(e) = show_clear_cloud_menu(&conn, destination, menu_p.as_str()).await {
+ eprintln!("[tray-click] Fallback show_clear_cloud_menu failed: {:?}", e);
+ }
+ }
+ }
} else if btn == 273 {
- println!("[tray-click] Calling ContextMenu on {} at ({}, {})", id, cx_i, cy_i);
+ eprintln!("[tray-click] Calling ContextMenu on {} at ({}, {})", id, cx_i, cy_i);
let _ = proxy.context_menu(cx_i, cy_i).await;
}
}
@@ -1115,13 +1154,13 @@ impl PointerHandler for AppState {
}
if *button == 272 {
- println!("[tags-click] Mouse left click at logical: ({}, {})", cx, cy);
+ eprintln!("[tags-click] Mouse left click at logical: ({}, {})", cx, cy);
for bound in &st.tag_bounds {
- println!("[tags-click] Checking Tag '{}' bounds: x=[{}..{}], y=[{}..{}]",
+ eprintln!("[tags-click] Checking Tag '{}' bounds: x=[{}..{}], y=[{}..{}]",
bound.name, bound.x, bound.x + bound.w, bound.y, bound.y + bound.h);
if cx >= bound.x as f64 && cx <= (bound.x + bound.w) as f64
&& cy >= bound.y as f64 && cy <= (bound.y + bound.h) as f64 {
- println!("[tags-click] Tag matched: {}", bound.name);
+ eprintln!("[tags-click] Tag matched: {}", bound.name);
let name = bound.name.clone();
std::thread::spawn(move || {
let _ = std::process::Command::new("clearctl")
@@ -1283,7 +1322,11 @@ async fn spawn_status_listener(sub: &'static str, sender: calloop::channel::Send
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
loop {
- if let Ok(mut stream) = UnixStream::connect("/tmp/clearwm-status.sock").await {
+ let socket_path = match std::env::var("WAYLAND_DISPLAY") {
+ Ok(display) => format!("/tmp/clearwm-status-{}.sock", display),
+ Err(_) => "/tmp/clearwm-status.sock".to_string(),
+ };
+ if let Ok(mut stream) = UnixStream::connect(&socket_path).await {
if stream.write_all(format!("{}\n", sub).as_bytes()).await.is_ok() {
let mut reader = BufReader::new(stream);
let mut line = String::new();
@@ -1466,8 +1509,144 @@ trait StatusNotifierItem {
fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>;
+
+ #[zbus(property)]
+ fn item_is_menu(&self) -> zbus::Result<bool>;
+
+ #[zbus(property)]
+ fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
+}
+
+#[zbus::proxy(
+ interface = "com.canonical.dbusmenu",
+ default_path = "/StatusNotifierItem/menu"
+)]
+trait DBusMenu {
+ fn get_layout(
+ &self,
+ parent_id: i32,
+ recursion_depth: i32,
+ property_names: Vec<String>,
+ ) -> zbus::Result<(u32, (i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>))>;
+
+ fn event(
+ &self,
+ id: i32,
+ event_id: &str,
+ data: &zbus::zvariant::Value<'_>,
+ timestamp: u32,
+ ) -> zbus::Result<()>;
+
+ fn about_to_show(&self, id: i32) -> zbus::Result<bool>;
+}
+
+fn flatten_menu(
+ id: i32,
+ mut properties: std::collections::HashMap<String, zbus::zvariant::OwnedValue>,
+ children: Vec<zbus::zvariant::OwnedValue>,
+ prefix: &str,
+ out: &mut Vec<(i32, String)>
+) {
+ let label: String = properties.remove("label")
+ .and_then(|v| {
+ let s: Result<String, _> = v.try_into();
+ s.ok()
+ })
+ .unwrap_or_default();
+ let type_: String = properties.remove("type")
+ .and_then(|v| {
+ let s: Result<String, _> = v.try_into();
+ s.ok()
+ })
+ .unwrap_or_default();
+ let enabled: bool = properties.remove("enabled")
+ .and_then(|v| {
+ let b: Result<bool, _> = v.try_into();
+ b.ok()
+ })
+ .unwrap_or(true);
+
+ if type_ == "separator" || !enabled {
+ // Skip
+ } else {
+ let current_path = if prefix.is_empty() {
+ label.clone()
+ } else if !label.is_empty() {
+ format!("{} > {}", prefix, label)
+ } else {
+ prefix.to_string()
+ };
+
+ if !current_path.is_empty() && children.is_empty() {
+ out.push((id, current_path.clone()));
+ }
+
+ for child_val in children {
+ let child_val_inner = zbus::zvariant::Value::from(child_val);
+ if let Ok(child) = <(i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>)>::try_from(child_val_inner) {
+ flatten_menu(child.0, child.1, child.2, ¤t_path, out);
+ }
+ }
+ }
}
+async fn show_clear_cloud_menu(conn: &zbus::Connection, destination: &str, menu_path: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ let menu_proxy = DBusMenuProxy::builder(conn)
+ .destination(destination)?
+ .path(menu_path)?
+ .build()
+ .await?;
+
+ let _ = menu_proxy.about_to_show(0).await;
+ let (_, layout) = menu_proxy.get_layout(0, 3, vec![]).await?;
+ eprintln!("[tray-click] show_clear_cloud_menu layout: root_id={}, properties={:?}, children_len={}", layout.0, layout.1, layout.2.len());
+
+ let mut items = Vec::new();
+ flatten_menu(layout.0, layout.1, layout.2, "", &mut items);
+ eprintln!("[tray-click] show_clear_cloud_menu flattened items: {:?}", items);
+
+ if items.is_empty() {
+ eprintln!("[tray-click] show_clear_cloud_menu: items is empty, returning early");
+ return Ok(());
+ }
+
+ let mut clear_cloud_input = String::new();
+ for (_, label) in &items {
+ clear_cloud_input.push_str(label);
+ clear_cloud_input.push('\n');
+ }
+
+ let mut child = std::process::Command::new("clear-cloud")
+ .args(["--dmenu", "-p", "Tray Menu:"])
+ .stdin(std::process::Stdio::piped())
+ .stdout(std::process::Stdio::piped())
+ .stderr(std::process::Stdio::piped())
+ .spawn()?;
+
+ if let Some(mut stdin) = child.stdin.take() {
+ use std::io::Write;
+ stdin.write_all(clear_cloud_input.as_bytes())?;
+ }
+
+ let output = child.wait_with_output()?;
+ if output.status.success() {
+ let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ if let Some((id, _)) = items.iter().find(|(_, label)| label == &selected) {
+ let timestamp = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_secs() as u32;
+ let val = zbus::zvariant::Value::from("");
+ let _ = menu_proxy.event(*id, "clicked", &val, timestamp).await;
+ }
+ } else {
+ let stderr_str = String::from_utf8_lossy(&output.stderr).trim().to_string();
+ eprintln!("[tray-click] clear-cloud failed with status: {:?}, stderr: {:?}", output.status, stderr_str);
+ }
+ Ok(())
+}
+
+
fn find_icon_file(dir: &std::path::Path, icon_name: &str) -> Option<std::path::PathBuf> {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.filter_map(Result::ok) {
@@ -1481,7 +1660,7 @@ fn find_icon_file(dir: &std::path::Path, icon_name: &str) -> Option<std::path::P
}
} else if file_type.is_file() {
if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
- if file_name == format!("{}.png", icon_name) {
+ if file_name == format!("{}.png", icon_name) || file_name == format!("{}.svg", icon_name) {
return Some(path);
}
}
@@ -1554,6 +1733,40 @@ fn load_png_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
})
}
+fn load_svg_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
+ let svg_data = std::fs::read(path).ok()?;
+ let opt = resvg::usvg::Options::default();
+ let fontdb = resvg::usvg::fontdb::Database::new();
+ let tree = resvg::usvg::Tree::from_data(&svg_data, &opt, &fontdb).ok()?;
+
+ let target_w = 48;
+ let target_h = 48;
+ let mut pixmap = resvg::tiny_skia::Pixmap::new(target_w, target_h)?;
+
+ let orig_w = tree.size().width();
+ let orig_h = tree.size().height();
+ let sx = target_w as f32 / orig_w;
+ let sy = target_h as f32 / orig_h;
+ let transform = resvg::tiny_skia::Transform::from_scale(sx, sy);
+
+ resvg::render(&tree, transform, &mut pixmap.as_mut());
+
+ let raw_pixels = pixmap.data();
+ let mut argb_pixels = Vec::with_capacity((target_w * target_h * 4) as usize);
+ for chunk in raw_pixels.chunks_exact(4) {
+ argb_pixels.push(chunk[3]); // A
+ argb_pixels.push(chunk[0]); // R
+ argb_pixels.push(chunk[1]); // G
+ argb_pixels.push(chunk[2]); // B
+ }
+
+ Some(TrayPixmap {
+ width: target_w as i32,
+ height: target_h as i32,
+ pixels: argb_pixels,
+ })
+}
+
fn resolve_icon_path(theme_path: Option<&str>, icon_name: &str) -> Option<std::path::PathBuf> {
if icon_name.is_empty() {
return None;
@@ -1586,28 +1799,36 @@ fn resolve_icon_path(theme_path: Option<&str>, icon_name: &str) -> Option<std::p
"hicolor/24x24/status",
"hicolor/32x32/status",
"hicolor/48x48/status",
+ "hicolor/scalable/status",
"hicolor/16x16/apps",
"hicolor/22x22/apps",
"hicolor/24x24/apps",
"hicolor/32x32/apps",
"hicolor/48x48/apps",
+ "hicolor/scalable/apps",
"gnome/16x16/status",
"gnome/22x22/status",
"gnome/24x24/status",
"gnome/32x32/status",
"gnome/48x48/status",
+ "gnome/scalable/status",
"gnome/16x16/apps",
"gnome/22x22/apps",
"gnome/24x24/apps",
"gnome/32x32/apps",
"gnome/48x48/apps",
+ "gnome/scalable/apps",
];
for base in &search_dirs {
for sub in &sub_paths {
- let path = std::path::Path::new(base).join(sub).join(format!("{}.png", icon_name));
- if path.exists() && path.is_file() {
- return Some(path);
+ let path_png = std::path::Path::new(base).join(sub).join(format!("{}.png", icon_name));
+ if path_png.exists() && path_png.is_file() {
+ return Some(path_png);
+ }
+ let path_svg = std::path::Path::new(base).join(sub).join(format!("{}.svg", icon_name));
+ if path_svg.exists() && path_svg.is_file() {
+ return Some(path_svg);
}
}
let base_path = std::path::Path::new(base);
@@ -1652,7 +1873,13 @@ async fn fetch_tray_item(conn: &zbus::Connection, addr: &NotifierAddress) -> Res
println!("[status-tray] Trying to resolve theme icon for '{}' (theme path: {:?})", name, icon_theme_path);
if let Some(icon_path) = resolve_icon_path(icon_theme_path.as_deref(), name) {
println!("[status-tray] Found icon file at {:?}", icon_path);
- if let Some(pixmap) = load_png_as_pixmap(&icon_path) {
+ let ext = icon_path.extension().and_then(|e| e.to_str()).unwrap_or("");
+ let pixmap = if ext.eq_ignore_ascii_case("svg") {
+ load_svg_as_pixmap(&icon_path)
+ } else {
+ load_png_as_pixmap(&icon_path)
+ };
+ if let Some(pixmap) = pixmap {
println!("[status-tray] Successfully decoded icon file to pixmap (size: {}x{})", pixmap.width, pixmap.height);
pixmaps = Some(vec![pixmap]);
} else {
@@ -2004,6 +2231,21 @@ fn read_status_font_from_config() -> String {
"sans-serif".to_string()
}
+fn read_status_font_size_from_config() -> f32 {
+ let content = std::fs::read_to_string("/home/lsgalante/.config/clearwm/config.toml").unwrap_or_default();
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("status_font_size") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ if let Ok(val) = rest.trim_end_matches('"').trim().parse::<f32>() {
+ return val;
+ }
+ }
+ }
+ 11.0
+}
+
+
fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
let lines: Vec<&str> = content.lines().collect();
for i in 0..lines.len() {