From 72440e69c9e665f3e82e569e770747fc63765b53 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Sun, 25 Feb 2024 17:13:28 +0000 Subject: [PATCH 1/3] feat(tray): icon size setting --- docs/modules/Tray.md | 7 ++++--- src/modules/tray/icon.rs | 29 +++++++++++++++++++++-------- src/modules/tray/mod.rs | 20 ++++++++++++++------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/docs/modules/Tray.md b/docs/modules/Tray.md index a80c48e5..19244408 100644 --- a/docs/modules/Tray.md +++ b/docs/modules/Tray.md @@ -7,9 +7,10 @@ Displays a fully interactive icon tray using the KDE `libappindicator` protocol. > Type: `tray` -| Name | Type | Default | Description | -|-------------|----------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| -| `direction` | `string` | `left_to_right` if bar is horizontal, `top_to_bottom` otherwise | Direction to display the tray items. Possible values: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` | +| Name | Type | Default | Description | +|-------------|-----------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| +| `direction` | `string` | `left_to_right` if bar is horizontal, `top_to_bottom` otherwise | Direction to display the tray items. Possible values: `top_to_bottom`, `bottom_to_top`, `left_to_right`, `right_to_left` | +| `icon_size` | `integer` | `16` | Size in pixels to display tray icons as |
JSON diff --git a/src/modules/tray/icon.rs b/src/modules/tray/icon.rs index 850137f6..35929414 100644 --- a/src/modules/tray/icon.rs +++ b/src/modules/tray/icon.rs @@ -1,3 +1,5 @@ +use crate::image::ImageProvider; +use color_eyre::{Report, Result}; use glib::ffi::g_strfreev; use glib::translate::ToGlibPtr; use gtk::ffi::gtk_icon_theme_get_search_path; @@ -36,29 +38,40 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet { paths } +pub fn get_image(item: &StatusNotifierItem, icon_theme: &IconTheme, size: u32) -> Result { + get_image_from_icon_name(item, icon_theme, size) + .or_else(|_| get_image_from_pixmap(item, size)) +} + /// Attempts to get a GTK `Image` component /// for the status notifier item's icon. -pub(crate) fn get_image_from_icon_name( +fn get_image_from_icon_name( item: &StatusNotifierItem, icon_theme: &IconTheme, -) -> Option { + size: u32, +) -> Result { if let Some(path) = item.icon_theme_path.as_ref() { if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) { icon_theme.append_search_path(path); } } - item.icon_name.as_ref().and_then(|icon_name| { - let icon_info = icon_theme.lookup_icon(icon_name, 16, IconLookupFlags::empty()); - icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref())) - }) + let icon_info = item.icon_name.as_ref().and_then(|icon_name| { + icon_theme.lookup_icon(icon_name, size as i32, IconLookupFlags::empty()) + }); + + let pixbuf = icon_info.unwrap().load_icon()?; + + let image = Image::new(); + ImageProvider::create_and_load_surface(&pixbuf, &image)?; + Ok(image) } /// Attempts to get an image from the item pixmap. /// /// The pixmap is supplied in ARGB32 format, /// which has 8 bits per sample and a bit stride of `4*width`. -pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option { +fn get_image_from_pixmap(item: &StatusNotifierItem, size: u32) -> Result { const BITS_PER_SAMPLE: i32 = 8; let pixmap = item @@ -80,7 +93,7 @@ pub(crate) fn get_image_from_pixmap(item: &StatusNotifierItem) -> Option ); let pixbuf = pixbuf - .scale_simple(16, 16, InterpType::Bilinear) + .scale_simple(size as i32, size as i32, InterpType::Bilinear) .unwrap_or(pixbuf); Some(Image::from_pixbuf(Some(&pixbuf))) } diff --git a/src/modules/tray/mod.rs b/src/modules/tray/mod.rs index cf4a2ff4..d64654a0 100644 --- a/src/modules/tray/mod.rs +++ b/src/modules/tray/mod.rs @@ -18,12 +18,20 @@ use tokio::sync::mpsc; #[derive(Debug, Deserialize, Clone)] pub struct TrayModule { + #[serde(default = "default_icon_size")] + icon_size: u32, + #[serde(default, deserialize_with = "deserialize_orientation")] - pub direction: Option, + direction: Option, + #[serde(flatten)] pub common: Option, } +const fn default_icon_size() -> u32 { + 16 +} + fn deserialize_orientation<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, @@ -106,7 +114,7 @@ impl Module for TrayModule { // listen for UI updates glib_recv!(context.subscribe(), update => - on_update(update, &container, &mut menus, &icon_theme, &context.controller_tx) + on_update(update, &container, &mut menus, &icon_theme, self.icon_size, &context.controller_tx) ); }; @@ -124,6 +132,7 @@ fn on_update( container: &MenuBar, menus: &mut HashMap, TrayMenu>, icon_theme: &IconTheme, + icon_size: u32, tx: &mpsc::Sender, ) { match update { @@ -148,11 +157,10 @@ fn on_update( } if item.icon_name.as_ref() != menu_item.icon_name() { - match icon::get_image_from_icon_name(&item, icon_theme) - .or_else(|| icon::get_image_from_pixmap(&item)) + match icon::get_image(&item, icon_theme, icon_size) { - Some(image) => menu_item.set_image(&image), - None => menu_item.set_label(label), + Ok(image) => menu_item.set_image(&image), + Err(_) => menu_item.set_label(label), }; } From 0675b917f2beeed3e6b626dad8fe34b8063d9c83 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Sun, 25 Feb 2024 17:13:43 +0000 Subject: [PATCH 2/3] fix(tray): icons ignoring scaling --- src/image/provider.rs | 13 ++++++++----- src/modules/tray/icon.rs | 13 ++++++++----- src/modules/tray/mod.rs | 3 +-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/image/provider.rs b/src/image/provider.rs index a23debfe..1f3b6469 100644 --- a/src/image/provider.rs +++ b/src/image/provider.rs @@ -171,7 +171,7 @@ impl<'a> ImageProvider<'a> { ); // Different error types makes this a bit awkward - match pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, &image, scale)) + match pixbuf.map(|pixbuf| Self::create_and_load_surface(&pixbuf, &image)) { Ok(Err(err)) => error!("{err:?}"), Err(err) => error!("{err:?}"), @@ -202,7 +202,7 @@ impl<'a> ImageProvider<'a> { _ => unreachable!(), // handled above }?; - Self::create_and_load_surface(&pixbuf, image, scale) + Self::create_and_load_surface(&pixbuf, image) } /// Attempts to create a Cairo surface from the provided `Pixbuf`, @@ -210,10 +210,13 @@ impl<'a> ImageProvider<'a> { /// The surface is then loaded into the provided image. /// /// This is necessary for HiDPI since `Pixbuf`s are always treated as scale factor 1. - fn create_and_load_surface(pixbuf: &Pixbuf, image: >k::Image, scale: i32) -> Result<()> { + pub fn create_and_load_surface(pixbuf: &Pixbuf, image: >k::Image) -> Result<()> { let surface = unsafe { - let ptr = - gdk_cairo_surface_create_from_pixbuf(pixbuf.as_ptr(), scale, std::ptr::null_mut()); + let ptr = gdk_cairo_surface_create_from_pixbuf( + pixbuf.as_ptr(), + image.scale_factor(), + std::ptr::null_mut(), + ); Surface::from_raw_full(ptr) }?; diff --git a/src/modules/tray/icon.rs b/src/modules/tray/icon.rs index 35929414..7cf442e1 100644 --- a/src/modules/tray/icon.rs +++ b/src/modules/tray/icon.rs @@ -39,8 +39,7 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet { } pub fn get_image(item: &StatusNotifierItem, icon_theme: &IconTheme, size: u32) -> Result { - get_image_from_icon_name(item, icon_theme, size) - .or_else(|_| get_image_from_pixmap(item, size)) + get_image_from_icon_name(item, icon_theme, size).or_else(|_| get_image_from_pixmap(item, size)) } /// Attempts to get a GTK `Image` component @@ -77,10 +76,11 @@ fn get_image_from_pixmap(item: &StatusNotifierItem, size: u32) -> Result let pixmap = item .icon_pixmap .as_ref() - .and_then(|pixmap| pixmap.first())?; + .and_then(|pixmap| pixmap.first()) + .ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?; let bytes = glib::Bytes::from(&pixmap.pixels); - let row_stride = pixmap.width * 4; // + let row_stride = pixmap.width * 4; let pixbuf = gdk_pixbuf::Pixbuf::from_bytes( &bytes, @@ -95,5 +95,8 @@ fn get_image_from_pixmap(item: &StatusNotifierItem, size: u32) -> Result let pixbuf = pixbuf .scale_simple(size as i32, size as i32, InterpType::Bilinear) .unwrap_or(pixbuf); - Some(Image::from_pixbuf(Some(&pixbuf))) + + let image = Image::new(); + ImageProvider::create_and_load_surface(&pixbuf, &image)?; + Ok(image) } diff --git a/src/modules/tray/mod.rs b/src/modules/tray/mod.rs index d64654a0..d0f85e46 100644 --- a/src/modules/tray/mod.rs +++ b/src/modules/tray/mod.rs @@ -157,8 +157,7 @@ fn on_update( } if item.icon_name.as_ref() != menu_item.icon_name() { - match icon::get_image(&item, icon_theme, icon_size) - { + match icon::get_image(&item, icon_theme, icon_size) { Ok(image) => menu_item.set_image(&image), Err(_) => menu_item.set_label(label), }; From c62d47555ec31baa1a7094491e2977a832f4cfcc Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Sun, 25 Feb 2024 17:59:03 +0000 Subject: [PATCH 3/3] fix(tray): submenus not working Fixes a regression from previous tray fixes that caused submenus within the main tray menu to never show. Fixes #455 --- src/modules/tray/interface.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/modules/tray/interface.rs b/src/modules/tray/interface.rs index 3b9d988d..d35a1bff 100644 --- a/src/modules/tray/interface.rs +++ b/src/modules/tray/interface.rs @@ -188,8 +188,23 @@ enum TrayMenuWidget { impl TrayMenuItem { fn new(info: &MenuItemInfo, tx: mpsc::Sender) -> Self { + let mut submenu = HashMap::new(); let menu = Menu::new(); + macro_rules! add_submenu { + ($menu:expr, $widget:expr) => { + if !info.submenu.is_empty() { + for sub_item in &info.submenu { + let sub_item = TrayMenuItem::new(sub_item, tx.clone()); + call!($menu, add, sub_item.widget); + submenu.insert(sub_item.id, sub_item); + } + + $widget.set_submenu(Some(&menu)); + } + }; + } + let widget = match (info.menu_type, info.toggle_type) { (MenuType::Separator, _) => TrayMenuWidget::Separator(SeparatorMenuItem::new()), (MenuType::Standard, ToggleType::Checkmark) => { @@ -200,6 +215,8 @@ impl TrayMenuItem { .active(info.toggle_state == ToggleState::On) .build(); + add_submenu!(menu, widget); + { let tx = tx.clone(); let id = info.id; @@ -212,12 +229,13 @@ impl TrayMenuItem { TrayMenuWidget::Checkbox(widget) } (MenuType::Standard, _) => { - let builder = MenuItem::builder() + let widget = MenuItem::builder() .label(&info.label) .visible(info.visible) - .sensitive(info.enabled); + .sensitive(info.enabled) + .build(); - let widget = builder.build(); + add_submenu!(menu, widget); { let tx = tx.clone(); @@ -236,7 +254,7 @@ impl TrayMenuItem { id: info.id, widget, menu_widget: menu, - submenu: HashMap::new(), + submenu, tx, } }