diff --git a/Cargo.lock b/Cargo.lock index e111413d..d68eecb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.3" @@ -214,7 +220,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -231,7 +237,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -279,7 +285,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] @@ -374,7 +380,7 @@ dependencies = [ "glib", "libc", "once_cell", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -399,7 +405,7 @@ dependencies = [ "polling 3.3.1", "rustix 0.38.28", "slab", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -485,7 +491,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -561,6 +567,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -811,7 +826,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -861,6 +876,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +dependencies = [ + "simd-adler32", +] + [[package]] name = "field-offset" version = "0.3.5" @@ -883,6 +907,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1018,7 +1052,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -1143,7 +1177,7 @@ checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", "uuid", ] @@ -1180,7 +1214,7 @@ dependencies = [ "once_cell", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -1216,7 +1250,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -1230,7 +1264,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -1331,7 +1365,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -1540,7 +1574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd8ce4c182ce77e485918f49262425ee51a2746fe97f14084869aeff2fbc38e" dependencies = [ "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -1671,6 +1705,7 @@ dependencies = [ "mpris", "nix 0.29.0", "notify", + "png", "regex", "reqwest", "schemars", @@ -1753,7 +1788,7 @@ dependencies = [ "pest", "pest_derive", "serde", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -1895,6 +1930,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.0.1" @@ -1940,7 +1985,7 @@ checksum = "8fe50e71b3206a46eff95e96549aa60953dc072baffaa04b71415024f8f254d2" dependencies = [ "futures", "mpd_client", - "thiserror", + "thiserror 1.0.58", "tokio", "tracing", ] @@ -1980,7 +2025,7 @@ dependencies = [ "derive_is_enum_variant", "enum-kinds", "from_variants", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -2155,7 +2200,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2253,7 +2298,7 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ - "thiserror", + "thiserror 1.0.58", "ucd-trie", ] @@ -2277,7 +2322,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2308,7 +2353,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2329,6 +2374,19 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.0", +] + [[package]] name = "polling" version = "2.7.0" @@ -2531,7 +2589,7 @@ checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall 0.2.16", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -2778,7 +2836,7 @@ dependencies = [ "proc-macro2", "quote 1.0.35", "serde_derive_internals", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2839,7 +2897,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2850,7 +2908,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2873,7 +2931,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2960,6 +3018,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.8" @@ -2989,7 +3053,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.28", - "thiserror", + "thiserror 1.0.58", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3081,7 +3145,7 @@ checksum = "44b43b4059d825ccc04adf9726f944d0e3aa20938f4cff3b5c6b53198afcd6b3" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.58", ] [[package]] @@ -3108,9 +3172,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote 1.0.35", @@ -3165,12 +3229,12 @@ dependencies = [ [[package]] name = "system-tray" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a053bfb84b11f5eb8655a762ba826a2524d02a2f355b0fd6fce4125272f2e0" +checksum = "66066cf85f8a4985ae5a40ca4387036e4f870d95fbf113ffaab714796785d0f2" dependencies = [ "serde", - "thiserror", + "thiserror 2.0.0", "tokio", "tracing", "zbus", @@ -3195,7 +3259,16 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.58", +] + +[[package]] +name = "thiserror" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +dependencies = [ + "thiserror-impl 2.0.0", ] [[package]] @@ -3206,7 +3279,18 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" +dependencies = [ + "proc-macro2", + "quote 1.0.35", + "syn 2.0.87", ] [[package]] @@ -3291,7 +3375,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3446,7 +3530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.58", "time", "tracing-subscriber", ] @@ -3459,7 +3543,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3584,7 +3668,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.58", "toml 0.8.12", "tracing", ] @@ -3736,7 +3820,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -3770,7 +3854,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.85", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 7a062e83..1c702578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ notifications = ["zbus"] sys_info = ["sysinfo", "regex"] -tray = ["system-tray"] +tray = ["system-tray", "png"] upower = ["upower_dbus", "zbus", "futures-lite"] @@ -148,7 +148,8 @@ futures-signals = { version = "0.3.34", optional = true } sysinfo = { version = "0.29.11", optional = true } # tray -system-tray = { version = "0.2.0", optional = true } +system-tray = { version = "0.3.0", optional = true } +png = { version = "0.17.14", optional = true } # upower upower_dbus = { version = "0.3.2", optional = true } diff --git a/src/clients/mod.rs b/src/clients/mod.rs index a74310b0..7b6b3123 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -1,4 +1,4 @@ -use crate::{await_sync, Ironbar}; +use crate::await_sync; use color_eyre::Result; use std::path::Path; use std::rc::Rc; @@ -151,9 +151,7 @@ impl Clients { let client = match &self.tray { Some(client) => client.clone(), None => { - let service_name = format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id()); - - let client = await_sync(async { tray::Client::new(&service_name).await })?; + let client = await_sync(async { tray::Client::new().await })?; let client = Arc::new(client); self.tray.replace(client.clone()); client diff --git a/src/main.rs b/src/main.rs index 205315dc..a642e735 100644 --- a/src/main.rs +++ b/src/main.rs @@ -464,7 +464,7 @@ where /// Blocks on a `Future` until it resolves. /// /// This is not an `async` operation -/// so can be used outside of an async function. +/// so can be used outside an async function. /// /// Use sparingly, as this risks blocking the UI thread! /// Prefer async functions wherever possible. diff --git a/src/modules/tray/diff.rs b/src/modules/tray/diff.rs index dd756afc..8afa3da0 100644 --- a/src/modules/tray/diff.rs +++ b/src/modules/tray/diff.rs @@ -18,7 +18,9 @@ pub struct MenuItemDiff { /// True if the item is visible in the menu. pub visible: Option, /// Icon name of the item, following the freedesktop.org icon spec. - // pub icon_name: Option>, + pub icon_name: Option>, + /// PNG icon data. + pub icon_data: Option>>, /// Describe the current state of a "togglable" item. Can be one of: /// - Some(true): on /// - Some(false): off @@ -52,7 +54,8 @@ impl MenuItemDiff { label: diff!(&label), enabled: diff!(enabled), visible: diff!(visible), - // icon_name: diff!(&icon_name), + icon_name: diff!(&icon_name), + icon_data: diff!(&icon_data), toggle_state: diff!(toggle_state), submenu: get_diffs(&old.submenu, &new.submenu), } diff --git a/src/modules/tray/icon.rs b/src/modules/tray/icon.rs index a1877028..3b78a9cb 100644 --- a/src/modules/tray/icon.rs +++ b/src/modules/tray/icon.rs @@ -7,6 +7,7 @@ use gtk::ffi::gtk_icon_theme_get_search_path; use gtk::gdk_pixbuf::{Colorspace, InterpType, Pixbuf}; use gtk::prelude::IconThemeExt; use gtk::{IconLookupFlags, IconTheme, Image}; +use png::ColorType; use std::collections::HashSet; use std::ffi::CStr; use std::os::raw::{c_char, c_int}; @@ -125,3 +126,36 @@ fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result { ImageProvider::create_and_load_surface(&pixbuf, &image)?; Ok(image) } + +pub struct PngData<'a>(pub &'a [u8]); +impl TryFrom> for Image { + type Error = Report; + + fn try_from(value: PngData) -> std::result::Result { + let data = value.0; + + let decoder = png::Decoder::new(data); + let mut reader = decoder.read_info()?; + let mut buf = vec![0; reader.output_buffer_size()]; + + let info = reader.next_frame(&mut buf)?; + let bytes = glib::Bytes::from(&buf[..info.buffer_size()]); + + let has_alpha = matches!(info.color_type, ColorType::Rgba | ColorType::GrayscaleAlpha); + let row_stride_multiplier = if has_alpha { 4 } else { 3 }; + + let pixbuf = Pixbuf::from_bytes( + &bytes, + Colorspace::Rgb, + has_alpha, + info.bit_depth as i32, + info.width as i32, + info.height as i32, + (info.width * row_stride_multiplier) as i32, + ); + + let image = Image::new(); + ImageProvider::create_and_load_surface(&pixbuf, &image)?; + Ok(image) + } +} diff --git a/src/modules/tray/interface.rs b/src/modules/tray/interface.rs index 7baefbfa..95162bce 100644 --- a/src/modules/tray/interface.rs +++ b/src/modules/tray/interface.rs @@ -1,13 +1,19 @@ use super::diff::{Diff, MenuItemDiff}; +use crate::image::ImageProvider; +use crate::modules::tray::icon::PngData; use crate::{spawn, try_send}; use glib::Propagation; use gtk::prelude::*; -use gtk::{CheckMenuItem, Image, Label, Menu, MenuItem, SeparatorMenuItem}; +use gtk::{ + CheckMenuItem, Container, IconTheme, Image, Label, Menu, MenuItem, Orientation, + SeparatorMenuItem, +}; use std::collections::HashMap; use system_tray::client::ActivateRequest; use system_tray::item::{IconPixmap, StatusNotifierItem}; use system_tray::menu::{MenuItem as MenuItemInfo, MenuType, ToggleState, ToggleType}; use tokio::sync::mpsc; +use tracing::{error, warn}; /// Calls a method on the underlying widget, /// passing in a single argument. @@ -214,6 +220,37 @@ enum TrayMenuWidget { Checkbox(CheckMenuItem), } +fn setup_item(widget: &W, info: &MenuItemInfo) +where + W: IsA + IsA, +{ + let container = gtk::Box::new(Orientation::Horizontal, 10); + widget.add(&container); + + if let Some(icon) = &info.icon_name { + // TODO: Get theme here + let image = Image::new(); + match ImageProvider::parse(icon, &IconTheme::new(), true, 24) + .map(|provider| provider.load_into_image(image.clone())) + { + Some(Ok(())) => container.add(&image), + _ => warn!("Failed to load icon: {icon}"), + } + } + + if let Some(icon_data) = &info.icon_data { + match Image::try_from(PngData(icon_data.as_slice())) { + Ok(image) => container.add(&image), + Err(err) => error!("{err:?}"), + }; + } + + let label = Label::new(info.label.as_deref()); + container.add(&label); + + container.show_all(); +} + impl TrayMenuItem { fn new(info: &MenuItemInfo, tx: mpsc::Sender) -> Self { let mut submenu = HashMap::new(); @@ -234,7 +271,9 @@ impl TrayMenuItem { } let widget = match (info.menu_type, info.toggle_type) { - (MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()), + (MenuType::Separator, _) => { + TrayMenuWidget::Separator(SeparatorMenuItem::builder().visible(true).build()) + } (MenuType::Standard, ToggleType::Checkmark) => { let widget = CheckMenuItem::builder() .visible(info.visible) @@ -242,10 +281,7 @@ impl TrayMenuItem { .active(info.toggle_state == ToggleState::On) .build(); - if let Some(label) = &info.label { - widget.set_label(label); - } - + setup_item(&widget, info); add_submenu!(menu, widget); { @@ -266,10 +302,7 @@ impl TrayMenuItem { .sensitive(info.enabled) .build(); - if let Some(label) = &info.label { - widget.set_label(label); - } - + setup_item(&widget, info); add_submenu!(menu, widget); { @@ -310,9 +343,13 @@ impl TrayMenuItem { } // TODO: Image support - // if let Some(icon_name) = diff.icon_name { - // - // } + if let Some(_icon_name) = diff.icon_name { + warn!("received unimplemented menu icon update"); + } + + if let Some(_icon_data) = diff.icon_data { + warn!("received unimplemented menu icon update"); + } if let Some(enabled) = diff.enabled { match &self.widget { diff --git a/src/modules/tray/mod.rs b/src/modules/tray/mod.rs index faba5892..6ec66d60 100644 --- a/src/modules/tray/mod.rs +++ b/src/modules/tray/mod.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; use system_tray::client::Event; use system_tray::client::{ActivateRequest, UpdateEvent}; use tokio::sync::mpsc; -use tracing::{debug, error, warn}; +use tracing::{debug, error, trace, warn}; #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -198,7 +198,8 @@ fn on_update( menus.insert(address.into(), menu_item); } Event::Update(address, update) => { - debug!("Received tray update for '{address}': {update:?}"); + debug!("Received tray update for '{address}'"); + trace!("Tray update for '{address}: {update:?}'"); let Some(menu_item) = menus.get_mut(address.as_str()) else { error!("Attempted to update menu at '{address}' but could not find it"); @@ -211,13 +212,12 @@ fn on_update( } UpdateEvent::Icon(icon) => { if icon.as_ref() != menu_item.icon_name() { + menu_item.set_icon_name(icon); match icon::get_image(menu_item, icon_theme, icon_size, prefer_icons) { Ok(image) => menu_item.set_image(&image), Err(_) => menu_item.show_label(), }; } - - menu_item.set_icon_name(icon); } UpdateEvent::OverlayIcon(_icon) => { warn!("received unimplemented NewOverlayIcon event");