1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-17 23:01:04 +02:00

refactor: overhaul .desktop and image resolver systems

Rewrites the desktop file parser code and image resolver code to introduce caching system and make fully async. They should be much faster now.

BREAKING CHANGE: The `icon_theme` setting has been moved from per-bar to top-level
This commit is contained in:
Jake Stanger 2025-05-25 15:50:21 +01:00
parent ca524f19f6
commit 3e55d87c3a
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
26 changed files with 1840 additions and 600 deletions

View file

@ -147,7 +147,8 @@ impl Module<Button> for ClipboardModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> color_eyre::Result<ModuleParts<Button>> {
let button = IconButton::new(&self.icon, info.icon_theme, self.icon_size);
let button = IconButton::new(&self.icon, self.icon_size, context.ironbar.image_provider());
button.label().set_angle(self.layout.angle(info));
button.label().set_justify(self.layout.justify.into());

View file

@ -1,11 +1,9 @@
use crate::build;
use crate::dynamic_value::dynamic_string;
use gtk::Image;
use gtk::prelude::*;
use serde::Deserialize;
use crate::build;
use crate::dynamic_value::dynamic_string;
use crate::image::ImageProvider;
use super::{CustomWidget, CustomWidgetContext};
#[derive(Debug, Deserialize, Clone)]
@ -48,11 +46,15 @@ impl CustomWidget for ImageWidget {
{
let gtk_image = gtk_image.clone();
let icon_theme = context.icon_theme.clone();
dynamic_string(&self.src, move |src| {
ImageProvider::parse(&src, &icon_theme, false, self.size)
.map(|image| image.load_into_image(&gtk_image));
let gtk_image = gtk_image.clone();
let image_provider = context.image_provider.clone();
glib::spawn_future_local(async move {
image_provider
.load_into_image_silent(&src, self.size, false, &gtk_image)
.await;
});
});
}

View file

@ -21,7 +21,7 @@ use crate::script::Script;
use crate::{module_impl, spawn};
use color_eyre::Result;
use gtk::prelude::*;
use gtk::{Button, IconTheme, Orientation};
use gtk::{Button, Orientation};
use serde::Deserialize;
use std::cell::RefCell;
use std::rc::Rc;
@ -93,9 +93,9 @@ struct CustomWidgetContext<'a> {
tx: &'a mpsc::Sender<ExecEvent>,
bar_orientation: Orientation,
is_popup: bool,
icon_theme: &'a IconTheme,
popup_buttons: Rc<RefCell<Vec<Button>>>,
module_factory: AnyModuleFactory,
image_provider: crate::image::Provider,
}
trait CustomWidget {
@ -134,7 +134,7 @@ pub fn set_length<W: WidgetExt>(widget: &W, length: i32, bar_orientation: Orient
Orientation::Horizontal => widget.set_width_request(length),
Orientation::Vertical => widget.set_height_request(length),
_ => {}
};
}
}
impl WidgetOrModule {
@ -236,10 +236,10 @@ impl Module<gtk::Box> for CustomModule {
tx: &context.controller_tx,
bar_orientation: orientation,
is_popup: false,
icon_theme: info.icon_theme,
popup_buttons: popup_buttons.clone(),
module_factory: BarModuleFactory::new(context.ironbar.clone(), context.popup.clone())
.into(),
image_provider: context.ironbar.image_provider(),
};
self.bar.clone().into_iter().for_each(|widget| {
@ -283,8 +283,8 @@ impl Module<gtk::Box> for CustomModule {
tx: &context.controller_tx,
bar_orientation: Orientation::Horizontal,
is_popup: true,
icon_theme: info.icon_theme,
popup_buttons: Rc::new(RefCell::new(vec![])),
image_provider: context.ironbar.image_provider(),
module_factory: PopupModuleFactory::new(
context.ironbar,
context.popup,

View file

@ -3,7 +3,6 @@ use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt;
use crate::gtk_helpers::IronbarLabelExt;
use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
use color_eyre::Result;
@ -93,7 +92,7 @@ impl Module<gtk::Box> for FocusedModule {
tx.send_update(Some((focused.title.clone(), focused.app_id)))
.await;
};
}
while let Ok(event) = wlrx.recv().await {
match event {
@ -153,31 +152,33 @@ impl Module<gtk::Box> for FocusedModule {
container.add(&label);
{
let icon_overrides = info.icon_overrides.clone();
let icon_theme = info.icon_theme.clone();
let image_provider = context.ironbar.image_provider();
context.subscribe().recv_glib(move |data| {
if let Some((name, mut id)) = data {
if self.show_icon {
if let Some(icon) = icon_overrides.get(&id) {
id = icon.clone();
context.subscribe().recv_glib_async(move |data| {
let icon = icon.clone();
let label = label.clone();
let image_provider = image_provider.clone();
async move {
if let Some((name, id)) = data {
if self.show_icon {
match image_provider
.load_into_image(&id, self.icon_size, true, &icon)
.await
{
Ok(true) => icon.show(),
_ => icon.hide(),
}
}
match ImageProvider::parse(&id, &icon_theme, true, self.icon_size)
.map(|image| image.load_into_image(&icon))
{
Some(Ok(())) => icon.show(),
_ => icon.hide(),
if self.show_title {
label.show();
label.set_label(&name);
}
} else {
icon.hide();
label.hide();
}
if self.show_title {
label.show();
label.set_label(&name);
}
} else {
icon.hide();
label.hide();
}
});
}

View file

@ -231,7 +231,7 @@ impl Module<gtk::Box> for KeyboardModule {
tracing::error!("{err:?}");
break;
}
};
}
}
});
}
@ -257,9 +257,15 @@ impl Module<gtk::Box> for KeyboardModule {
) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(self.layout.orientation(info), 0);
let caps = IconLabel::new(&self.icons.caps_off, info.icon_theme, self.icon_size);
let num = IconLabel::new(&self.icons.num_off, info.icon_theme, self.icon_size);
let scroll = IconLabel::new(&self.icons.scroll_off, info.icon_theme, self.icon_size);
let image_provider = context.ironbar.image_provider();
let caps = IconLabel::new(&self.icons.caps_off, self.icon_size, &image_provider);
let num = IconLabel::new(&self.icons.num_off, self.icon_size, &image_provider);
let scroll = IconLabel::new(
&self.icons.scroll_off,
self.icon_size,
&image_provider,
);
caps.label().set_angle(self.layout.angle(info));
caps.label().set_justify(self.layout.justify.into());
@ -270,7 +276,7 @@ impl Module<gtk::Box> for KeyboardModule {
scroll.label().set_angle(self.layout.angle(info));
scroll.label().set_justify(self.layout.justify.into());
let layout_button = IconButton::new("", info.icon_theme, self.icon_size);
let layout_button = IconButton::new("", self.icon_size, image_provider);
if self.show_caps {
caps.add_class("key");

View file

@ -3,20 +3,18 @@ use crate::channels::AsyncSenderExt;
use crate::clients::wayland::ToplevelInfo;
use crate::config::{BarPosition, TruncateMode};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::image::ImageProvider;
use crate::modules::ModuleUpdateEvent;
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
use crate::read_lock;
use crate::{image, read_lock};
use glib::Propagation;
use gtk::gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY};
use gtk::prelude::*;
use gtk::{Align, Button, IconTheme, Image, Justification, Label, Orientation};
use gtk::{Align, Button, Image, Justification, Label, Orientation};
use indexmap::IndexMap;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::RwLock;
use tokio::sync::mpsc::Sender;
use tracing::error;
#[derive(Debug, Clone)]
pub struct Item {
@ -166,7 +164,7 @@ impl ItemButton {
pub fn new(
item: &Item,
appearance: AppearanceOptions,
icon_theme: &IconTheme,
image_provider: image::Provider,
bar_position: BarPosition,
tx: &Sender<ModuleUpdateEvent<LauncherUpdate>>,
controller_tx: &Sender<ItemEvent>,
@ -188,14 +186,13 @@ impl ItemButton {
} else {
item.app_id.clone()
};
let image = ImageProvider::parse(&input, icon_theme, true, appearance.icon_size);
if let Some(image) = image {
button.set_always_show_image(true);
if let Err(err) = image.load_into_image(&button.image) {
error!("{err:?}");
}
};
let button = button.clone();
glib::spawn_future_local(async move {
image_provider
.load_into_image_silent(&input, appearance.icon_size, true, &button.image)
.await;
});
}
button.add_class("item");

View file

@ -8,7 +8,6 @@ use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, Wid
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::wayland::{self, ToplevelEvent};
use crate::config::{CommonConfig, EllipsizeMode, LayoutConfig, TruncateMode};
use crate::desktop_file::find_desktop_file;
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::modules::launcher::item::ImageTextButton;
use crate::modules::launcher::pagination::{IconContext, Pagination};
@ -18,11 +17,10 @@ use gtk::prelude::*;
use gtk::{Button, Orientation};
use indexmap::IndexMap;
use serde::Deserialize;
use std::ops::Deref;
use std::process::{Command, Stdio};
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, error, trace};
use tracing::{debug, error, trace, warn};
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@ -212,7 +210,7 @@ impl Module<gtk::Box> for LauncherModule {
fn spawn_controller(
&self,
info: &ModuleInfo,
_info: &ModuleInfo,
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> crate::Result<()> {
@ -223,14 +221,9 @@ impl Module<gtk::Box> for LauncherModule {
favorites
.iter()
.map(|app_id| {
let icon_override = info
.icon_overrides
.get(app_id)
.map_or_else(String::new, ToString::to_string);
(
app_id.to_string(),
Item::new(app_id.to_string(), icon_override, OpenState::Closed, true),
Item::new(app_id.to_string(), OpenState::Closed, true),
)
})
.collect::<IndexMap<_, _>>()
@ -239,8 +232,6 @@ impl Module<gtk::Box> for LauncherModule {
let items = arc_mut!(items);
let items2 = Arc::clone(&items);
let icon_overrides = info.icon_overrides.clone();
let tx = context.tx.clone();
let tx2 = context.tx.clone();
@ -258,12 +249,7 @@ impl Module<gtk::Box> for LauncherModule {
if let Some(item) = item {
item.merge_toplevel(info.clone());
} else {
let mut item = Item::from(info.clone());
if let Some(icon) = icon_overrides.get(&info.app_id) {
item.icon_override.clone_from(icon);
}
let item = Item::from(info.clone());
items.insert(info.app_id.clone(), item);
}
}
@ -271,6 +257,7 @@ impl Module<gtk::Box> for LauncherModule {
{
let items = {
let items = lock!(items);
items
.iter()
.map(|(_, item)| item.clone())
@ -296,12 +283,7 @@ impl Module<gtk::Box> for LauncherModule {
let item = items.get_mut(&info.app_id);
match item {
None => {
let mut item: Item = info.into();
if let Some(icon) = icon_overrides.get(&app_id) {
item.icon_override.clone_from(icon);
}
let item: Item = info.into();
items.insert(app_id.clone(), item.clone());
ItemOrWindow::Item(item)
@ -378,7 +360,7 @@ impl Module<gtk::Box> for LauncherModule {
.await?;
}
None => {}
};
}
}
}
}
@ -389,17 +371,16 @@ impl Module<gtk::Box> for LauncherModule {
// listen to ui events
let minimize_focused = self.minimize_focused;
let wl = context.client::<wayland::Client>();
let desktop_files = context.ironbar.desktop_files();
spawn(async move {
while let Some(event) = rx.recv().await {
if let ItemEvent::OpenItem(app_id) = event {
find_desktop_file(&app_id).map_or_else(
|| error!("Could not find desktop file for {}", app_id),
|file| {
match desktop_files.find(&app_id).await {
Ok(Some(file)) => {
if let Err(err) = Command::new("gtk-launch")
.arg(
file.file_name()
.expect("File segment missing from path to desktop file"),
)
.arg(file.file_name)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
@ -408,11 +389,13 @@ impl Module<gtk::Box> for LauncherModule {
"{:?}",
Report::new(err)
.wrap_err("Failed to run gtk-launch command.")
.suggestion("Perhaps the desktop file is invalid?")
.suggestion("Perhaps the applications file is invalid?")
);
}
},
);
}
Ok(None) => warn!("Could not find applications file for {}", app_id),
Err(err) => error!("Failed to find parse file for {}: {}", app_id, err),
}
} else {
tx.send_expect(ModuleUpdateEvent::ClosePopup).await;
@ -457,11 +440,11 @@ impl Module<gtk::Box> for LauncherModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> crate::Result<ModuleParts<gtk::Box>> {
let icon_theme = info.icon_theme;
let container = gtk::Box::new(self.layout.orientation(info), 0);
let page_size = self.page_size;
let image_provider = context.ironbar.image_provider();
let pagination = Pagination::new(
&container,
self.page_size,
@ -471,11 +454,11 @@ impl Module<gtk::Box> for LauncherModule {
fwd: &self.icons.page_forward,
size: self.pagination_icon_size,
},
&image_provider,
);
{
let container = container.clone();
let icon_theme = icon_theme.clone();
let controller_tx = context.controller_tx.clone();
@ -516,7 +499,7 @@ impl Module<gtk::Box> for LauncherModule {
let button = ItemButton::new(
&item,
appearance_options,
&icon_theme,
image_provider.clone(),
bar_position,
&tx,
&controller_tx,

View file

@ -1,7 +1,8 @@
use crate::gtk_helpers::IronbarGtkExt;
use crate::image;
use crate::image::IconButton;
use gtk::prelude::*;
use gtk::{Button, IconTheme, Orientation};
use gtk::{Button, Orientation};
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
@ -24,20 +25,21 @@ impl Pagination {
container: &gtk::Box,
page_size: usize,
orientation: Orientation,
icon_context: IconContext,
icon_context: &IconContext,
image_provider: &image::Provider,
) -> Self {
let scroll_box = gtk::Box::new(orientation, 0);
let scroll_back = IconButton::new(
icon_context.icon_back,
icon_context.icon_theme,
icon_context.icon_size,
icon_context.back,
icon_context.size,
image_provider.clone(),
);
let scroll_fwd = IconButton::new(
icon_context.icon_fwd,
icon_context.icon_theme,
icon_context.icon_size,
icon_context.fwd,
icon_context.size,
image_provider.clone(),
);
scroll_back.set_sensitive(false);

View file

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::rc::Rc;
use std::sync::Arc;
@ -7,7 +6,7 @@ use color_eyre::Result;
use glib::IsA;
use gtk::gdk::{EventMask, Monitor};
use gtk::prelude::*;
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
use gtk::{Application, Button, EventBox, Orientation, Revealer, Widget};
use tokio::sync::{broadcast, mpsc};
use tracing::debug;
@ -78,8 +77,6 @@ pub struct ModuleInfo<'a> {
pub bar_position: BarPosition,
pub monitor: &'a Monitor,
pub output_name: &'a str,
pub icon_theme: &'a IconTheme,
pub icon_overrides: Arc<HashMap<String, String>>,
}
#[derive(Debug, Clone)]

View file

@ -7,10 +7,10 @@ use std::time::Duration;
use color_eyre::Result;
use glib::{Propagation, PropertySet};
use gtk::prelude::*;
use gtk::{Button, IconTheme, Label, Orientation, Scale};
use gtk::{Button, Label, Orientation, Scale};
use regex::Regex;
use tokio::sync::mpsc;
use tracing::error;
use tracing::{error, warn};
pub use self::config::MusicModule;
use self::config::PlayerType;
@ -20,12 +20,12 @@ use crate::clients::music::{
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::image::{IconButton, IconLabel, ImageProvider};
use crate::image::{IconButton, IconLabel};
use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{module_impl, spawn};
use crate::{image, module_impl, spawn};
mod config;
@ -142,7 +142,7 @@ impl Module<Button> for MusicModule {
},
PlayerUpdate::ProgressTick(progress_tick) => {
tx.send_update(ControllerEvent::UpdateProgress(progress_tick))
.await
.await;
}
}
}
@ -184,8 +184,10 @@ impl Module<Button> for MusicModule {
button.add(&button_contents);
let icon_play = IconLabel::new(&self.icons.play, info.icon_theme, self.icon_size);
let icon_pause = IconLabel::new(&self.icons.pause, info.icon_theme, self.icon_size);
let image_provider = context.ironbar.image_provider();
let icon_play = IconLabel::new(&self.icons.play, self.icon_size, &image_provider);
let icon_pause = IconLabel::new(&self.icons.pause, self.icon_size, &image_provider);
icon_play.label().set_angle(self.layout.angle(info));
icon_play.label().set_justify(self.layout.justify.into());
@ -267,9 +269,9 @@ impl Module<Button> for MusicModule {
fn into_popup(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
_info: &ModuleInfo,
) -> Option<gtk::Box> {
let icon_theme = info.icon_theme;
let image_provider = context.ironbar.image_provider();
let container = gtk::Box::new(Orientation::Vertical, 10);
let main_container = gtk::Box::new(Orientation::Horizontal, 10);
@ -283,9 +285,9 @@ impl Module<Button> for MusicModule {
let icons = self.icons;
let info_box = gtk::Box::new(Orientation::Vertical, 10);
let title_label = IconPrefixedLabel::new(&icons.track, None, icon_theme);
let album_label = IconPrefixedLabel::new(&icons.album, None, icon_theme);
let artist_label = IconPrefixedLabel::new(&icons.artist, None, icon_theme);
let title_label = IconPrefixedLabel::new(&icons.track, None, &image_provider);
let album_label = IconPrefixedLabel::new(&icons.album, None, &image_provider);
let artist_label = IconPrefixedLabel::new(&icons.artist, None, &image_provider);
title_label.container.add_class("title");
album_label.container.add_class("album");
@ -298,16 +300,16 @@ impl Module<Button> for MusicModule {
let controls_box = gtk::Box::new(Orientation::Horizontal, 0);
controls_box.add_class("controls");
let btn_prev = IconButton::new(&icons.prev, icon_theme, self.icon_size);
let btn_prev = IconButton::new(&icons.prev, self.icon_size, image_provider.clone());
btn_prev.add_class("btn-prev");
let btn_play = IconButton::new(&icons.play, icon_theme, self.icon_size);
let btn_play = IconButton::new(&icons.play, self.icon_size, image_provider.clone());
btn_play.add_class("btn-play");
let btn_pause = IconButton::new(&icons.pause, icon_theme, self.icon_size);
let btn_pause = IconButton::new(&icons.pause, self.icon_size, image_provider.clone());
btn_pause.add_class("btn-pause");
let btn_next = IconButton::new(&icons.next, icon_theme, self.icon_size);
let btn_next = IconButton::new(&icons.next, self.icon_size, image_provider.clone());
btn_next.add_class("btn-next");
controls_box.add(&*btn_prev);
@ -324,7 +326,7 @@ impl Module<Button> for MusicModule {
volume_slider.set_inverted(true);
volume_slider.add_class("slider");
let volume_icon = IconLabel::new(&icons.volume, icon_theme, self.icon_size);
let volume_icon = IconLabel::new(&icons.volume, self.icon_size, &image_provider);
volume_icon.add_class("icon");
volume_box.pack_start(&volume_slider, true, true, 0);
@ -402,7 +404,6 @@ impl Module<Button> for MusicModule {
container.show_all();
{
let icon_theme = icon_theme.clone();
let image_size = self.cover_image_size;
let mut prev_cover = None;
@ -413,19 +414,43 @@ impl Module<Button> for MusicModule {
let new_cover = update.song.cover_path;
if prev_cover != new_cover {
prev_cover.clone_from(&new_cover);
let res = if let Some(image) = new_cover.and_then(|cover_path| {
ImageProvider::parse(&cover_path, &icon_theme, false, image_size)
}) {
album_image.show();
image.load_into_image(&album_image)
if let Some(cover_path) = new_cover {
let image_provider = image_provider.clone();
let album_image = album_image.clone();
glib::spawn_future_local(async move {
let success = match image_provider
.load_into_image(
&cover_path,
image_size,
false,
&album_image,
)
.await
{
Ok(true) => {
album_image.show();
true
}
Ok(false) => {
warn!("failed to parse image: {}", cover_path);
false
}
Err(err) => {
error!("failed to load image: {}", err);
false
}
};
if !success {
album_image.set_from_pixbuf(None);
album_image.hide();
}
});
} else {
album_image.set_from_pixbuf(None);
album_image.hide();
Ok(())
};
if let Err(err) = res {
error!("{err:?}");
}
}
@ -490,7 +515,7 @@ impl Module<Button> for MusicModule {
}
}
_ => {}
};
}
});
}
@ -544,10 +569,10 @@ struct IconPrefixedLabel {
}
impl IconPrefixedLabel {
fn new(icon_input: &str, label: Option<&str>, icon_theme: &IconTheme) -> Self {
fn new(icon_input: &str, label: Option<&str>, image_provider: &image::Provider) -> Self {
let container = gtk::Box::new(Orientation::Horizontal, 5);
let icon = IconLabel::new(icon_input, icon_theme, 24);
let icon = IconLabel::new(icon_input, 24, image_provider);
let mut builder = Label::builder().use_markup(true);

View file

@ -1,3 +1,9 @@
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::networkmanager::{Client, ClientState};
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
use color_eyre::Result;
use futures_lite::StreamExt;
use futures_signals::signal::SignalExt;
@ -6,14 +12,6 @@ use gtk::{Box as GtkBox, Image};
use serde::Deserialize;
use tokio::sync::mpsc::Receiver;
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::networkmanager::{Client, ClientState};
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct NetworkManagerModule {
@ -58,18 +56,30 @@ impl Module<GtkBox> for NetworkManagerModule {
context: WidgetContext<ClientState, ()>,
info: &ModuleInfo,
) -> Result<ModuleParts<GtkBox>> {
const INITIAL_ICON_NAME: &str = "content-loading-symbolic";
let container = GtkBox::new(info.bar_position.orientation(), 0);
let icon = Image::new();
icon.add_class("icon");
container.add(&icon);
let icon_theme = info.icon_theme.clone();
let image_provider = context.ironbar.image_provider();
let initial_icon_name = "content-loading-symbolic";
ImageProvider::parse(initial_icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(&icon));
glib::spawn_future_local({
let image_provider = image_provider.clone();
let icon = icon.clone();
async move {
image_provider
.load_into_image_silent(INITIAL_ICON_NAME, self.icon_size, false, &icon)
.await;
}
});
context.subscribe().recv_glib_async(move |state| {
let image_provider = image_provider.clone();
let icon = icon.clone();
context.subscribe().recv_glib(move |state| {
let icon_name = match state {
ClientState::WiredConnected => "network-wired-symbolic",
ClientState::WifiConnected => "network-wireless-symbolic",
@ -79,8 +89,12 @@ impl Module<GtkBox> for NetworkManagerModule {
ClientState::Offline => "network-wireless-disabled-symbolic",
ClientState::Unknown => "dialog-question-symbolic",
};
ImageProvider::parse(icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(&icon));
async move {
image_provider
.load_into_image_silent(icon_name, self.icon_size, false, &icon)
.await;
}
});
Ok(ModuleParts::new(container, None))

View file

@ -1,4 +1,4 @@
use crate::image::ImageProvider;
use crate::image::create_and_load_surface;
use crate::modules::tray::interface::TrayMenu;
use color_eyre::{Report, Result};
use glib::ffi::g_strfreev;
@ -40,21 +40,21 @@ fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet<String> {
pub fn get_image(
item: &TrayMenu,
icon_theme: &IconTheme,
size: u32,
prefer_icons: bool,
icon_theme: &IconTheme,
) -> Result<Image> {
if !prefer_icons && item.icon_pixmap.is_some() {
get_image_from_pixmap(item, size)
} else {
get_image_from_icon_name(item, icon_theme, size)
get_image_from_icon_name(item, size, icon_theme)
.or_else(|_| get_image_from_pixmap(item, size))
}
}
/// Attempts to get a GTK `Image` component
/// for the status notifier item's icon.
fn get_image_from_icon_name(item: &TrayMenu, icon_theme: &IconTheme, size: u32) -> Result<Image> {
fn get_image_from_icon_name(item: &TrayMenu, size: u32, icon_theme: &IconTheme) -> Result<Image> {
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);
@ -68,7 +68,7 @@ fn get_image_from_icon_name(item: &TrayMenu, icon_theme: &IconTheme, size: u32)
if let Some(icon_info) = icon_info {
let pixbuf = icon_info.load_icon()?;
let image = Image::new();
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
create_and_load_surface(&pixbuf, &image)?;
Ok(image)
} else {
Err(Report::msg("could not find icon"))
@ -122,6 +122,6 @@ fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> {
.unwrap_or(pixbuf);
let image = Image::new();
ImageProvider::create_and_load_surface(&pixbuf, &image)?;
create_and_load_surface(&pixbuf, &image)?;
Ok(image)
}

View file

@ -93,7 +93,7 @@ impl Module<gtk::Box> for TrayModule {
while let Some(cmd) = rx.recv().await {
if let Err(err) = client.activate(cmd).await {
error!("{err:?}");
};
}
}
Ok::<_, Report>(())
@ -120,7 +120,7 @@ impl Module<gtk::Box> for TrayModule {
{
let container = container.clone();
let mut menus = HashMap::new();
let icon_theme = info.icon_theme.clone();
let icon_theme = context.ironbar.image_provider().icon_theme();
// listen for UI updates
context.subscribe().recv_glib(move |update| {
@ -159,12 +159,12 @@ fn on_update(
let mut menu_item = TrayMenu::new(&address, *item);
container.pack_start(&menu_item.event_box, true, true, 0);
if let Ok(image) = icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
if let Ok(image) = icon::get_image(&menu_item, icon_size, prefer_icons, icon_theme) {
menu_item.set_image(&image);
} else {
let label = menu_item.title.clone().unwrap_or(address.clone());
menu_item.set_label(&label);
};
}
menu_item.event_box.show();
menus.insert(address.into(), menu_item);
@ -185,10 +185,10 @@ 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) {
match icon::get_image(menu_item, icon_size, prefer_icons, icon_theme) {
Ok(image) => menu_item.set_image(&image),
Err(_) => menu_item.show_label(),
};
}
}
}
UpdateEvent::OverlayIcon(_icon) => {
@ -219,5 +219,5 @@ fn on_update(
container.remove(&menu.event_box);
}
}
};
}
}

View file

@ -3,6 +3,7 @@ use futures_lite::stream::StreamExt;
use gtk::{Button, prelude::*};
use gtk::{Label, Orientation};
use serde::Deserialize;
use std::fmt::Write;
use tokio::sync::mpsc;
use zbus;
use zbus::fdo::PropertiesProxy;
@ -11,7 +12,6 @@ use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::upower::BatteryState;
use crate::config::{CommonConfig, LayoutConfig};
use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::image::ImageProvider;
use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
@ -171,7 +171,6 @@ impl Module<Button> for UpowerModule {
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleParts<Button>> {
let icon_theme = info.icon_theme.clone();
let icon = gtk::Image::new();
icon.add_class("icon");
@ -202,15 +201,20 @@ impl Module<Button> for UpowerModule {
let format = self.format.clone();
let rx = context.subscribe();
rx.recv_glib(move |properties| {
let provider = context.ironbar.image_provider();
rx.recv_glib_async(move |properties| {
let state = properties.state;
let is_charging =
state == BatteryState::Charging || state == BatteryState::PendingCharge;
let time_remaining = if is_charging {
seconds_to_string(properties.time_to_full)
} else {
seconds_to_string(properties.time_to_empty)
};
}
.unwrap_or_default();
let format = format
.replace("{percentage}", &properties.percentage.to_string())
.replace("{time_remaining}", &time_remaining)
@ -219,10 +223,16 @@ impl Module<Button> for UpowerModule {
let mut icon_name = String::from("icon:");
icon_name.push_str(&properties.icon_name);
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(&icon));
let provider = provider.clone();
let icon = icon.clone();
label.set_label_escaped(&format);
async move {
provider
.load_into_image_silent(&icon_name, self.icon_size, false, &icon)
.await;
}
});
let popup = self
@ -254,7 +264,7 @@ impl Module<Button> for UpowerModule {
BatteryState::Charging | BatteryState::PendingCharge => {
let ttf = properties.time_to_full;
if ttf > 0 {
format!("Full in {}", seconds_to_string(ttf))
format!("Full in {}", seconds_to_string(ttf).unwrap_or_default())
} else {
String::new()
}
@ -262,7 +272,7 @@ impl Module<Button> for UpowerModule {
BatteryState::Discharging | BatteryState::PendingDischarge => {
let tte = properties.time_to_empty;
if tte > 0 {
format!("Empty in {}", seconds_to_string(tte))
format!("Empty in {}", seconds_to_string(tte).unwrap_or_default())
} else {
String::new()
}
@ -279,21 +289,22 @@ impl Module<Button> for UpowerModule {
}
}
fn seconds_to_string(seconds: i64) -> String {
fn seconds_to_string(seconds: i64) -> Result<String> {
let mut time_string = String::new();
let days = seconds / (DAY);
if days > 0 {
time_string += &format!("{days}d");
write!(time_string, "{days}d")?;
}
let hours = (seconds % DAY) / HOUR;
if hours > 0 {
time_string += &format!(" {hours}h");
write!(time_string, " {hours}h")?;
}
let minutes = (seconds % HOUR) / MINUTE;
if minutes > 0 {
time_string += &format!(" {minutes}m");
write!(time_string, " {minutes}m")?;
}
time_string.trim_start().to_string()
Ok(time_string.trim_start().to_string())
}
const fn u32_to_battery_state(number: u32) -> Result<BatteryState, u32> {

View file

@ -16,7 +16,7 @@ impl Button {
pub fn new(id: i64, name: &str, open_state: OpenState, context: &WorkspaceItemContext) -> Self {
let label = context.name_map.get(name).map_or(name, String::as_str);
let button = IconButton::new(label, &context.icon_theme, context.icon_size);
let button = IconButton::new(label, context.icon_size, context.image_provider.clone());
button.set_widget_name(name);
button.add_class("item");

View file

@ -9,9 +9,8 @@ use crate::config::{CommonConfig, LayoutConfig};
use crate::modules::workspaces::button_map::{ButtonMap, Identifier};
use crate::modules::workspaces::open_state::OpenState;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
use crate::{image, module_impl, spawn};
use color_eyre::{Report, Result};
use gtk::IconTheme;
use gtk::prelude::*;
use serde::Deserialize;
use std::cmp::Ordering;
@ -145,8 +144,8 @@ const fn default_icon_size() -> i32 {
#[derive(Debug, Clone)]
pub struct WorkspaceItemContext {
name_map: HashMap<String, String>,
icon_theme: IconTheme,
icon_size: i32,
image_provider: image::Provider,
tx: mpsc::Sender<i64>,
}
@ -240,8 +239,8 @@ impl Module<gtk::Box> for WorkspacesModule {
let item_context = WorkspaceItemContext {
name_map: self.name_map.clone(),
icon_theme: info.icon_theme.clone(),
icon_size: self.icon_size,
image_provider: context.ironbar.image_provider(),
tx: context.controller_tx.clone(),
};