Skip to content

Commit

Permalink
Merge pull request #766 from JakeStanger/fix/tray-fixes
Browse files Browse the repository at this point in the history
A whole load of tray fixes 🎉
  • Loading branch information
JakeStanger authored Nov 9, 2024
2 parents e53a906 + f1e8783 commit ed338e9
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 67 deletions.
166 changes: 125 additions & 41 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ notifications = ["zbus"]

sys_info = ["sysinfo", "regex"]

tray = ["system-tray"]
tray = ["system-tray", "png"]

upower = ["upower_dbus", "zbus", "futures-lite"]

Expand Down Expand Up @@ -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 }
Expand Down
6 changes: 2 additions & 4 deletions src/clients/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions src/modules/tray/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ pub struct MenuItemDiff {
/// True if the item is visible in the menu.
pub visible: Option<bool>,
/// Icon name of the item, following the freedesktop.org icon spec.
// pub icon_name: Option<Option<String>>,
pub icon_name: Option<Option<String>>,
/// PNG icon data.
pub icon_data: Option<Option<Vec<u8>>>,
/// Describe the current state of a "togglable" item. Can be one of:
/// - Some(true): on
/// - Some(false): off
Expand Down Expand Up @@ -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),
}
Expand Down
34 changes: 34 additions & 0 deletions src/modules/tray/icon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -125,3 +126,36 @@ fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> {
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
Ok(image)
}

pub struct PngData<'a>(pub &'a [u8]);
impl TryFrom<PngData<'_>> for Image {
type Error = Report;

fn try_from(value: PngData) -> std::result::Result<Self, Self::Error> {
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)
}
}
63 changes: 50 additions & 13 deletions src/modules/tray/interface.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -214,6 +220,37 @@ enum TrayMenuWidget {
Checkbox(CheckMenuItem),
}

fn setup_item<W>(widget: &W, info: &MenuItemInfo)
where
W: IsA<MenuItem> + IsA<Container>,
{
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<i32>) -> Self {
let mut submenu = HashMap::new();
Expand All @@ -234,18 +271,17 @@ 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)
.sensitive(info.enabled)
.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);

{
Expand All @@ -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);

{
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions src/modules/tray/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down

0 comments on commit ed338e9

Please sign in to comment.