diff --git a/Cargo.toml b/Cargo.toml index 45789f6..1c70257 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"] diff --git a/src/modules/tray/icon.rs b/src/modules/tray/icon.rs index a187702..3b78a9c 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 7baefbf..84dd755 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); {