1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-16 22:31:03 +02:00

Merge pull request #1010 from JakeStanger/refactor/glib-deps

refactor: `recv_glib` dependency arrays
This commit is contained in:
Jake Stanger 2025-05-26 22:54:04 +01:00 committed by GitHub
commit b27c601733
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 539 additions and 517 deletions

View file

@ -95,20 +95,29 @@ pub trait MpscReceiverExt<T> {
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()` /// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
/// in a loop, passing the message to `f`. /// in a loop, passing the message to `f`.
/// ///
/// This allows use of `GObjects` and futures in the same context. /// This allows use of `GObjects` and futures in the same context.#
fn recv_glib<F>(self, f: F) ///
/// `deps` is a single reference, or tuple of references of clonable objects,
/// to be consumed inside the closure.
/// This avoids needing to `element.clone()` everywhere.
fn recv_glib<D, Fn>(self, deps: D, f: Fn)
where where
F: FnMut(T) + 'static; D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) + 'static;
} }
impl<T: 'static> MpscReceiverExt<T> for mpsc::Receiver<T> { impl<T: 'static> MpscReceiverExt<T> for mpsc::Receiver<T> {
fn recv_glib<F>(mut self, mut f: F) fn recv_glib<D, Fn>(mut self, deps: D, mut f: Fn)
where where
F: FnMut(T) + 'static, D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) + 'static,
{ {
let deps = deps.clone_content();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
while let Some(val) = self.recv().await { while let Some(val) = self.recv().await {
f(val); f(&deps, val);
} }
}); });
} }
@ -122,14 +131,22 @@ where
/// in a loop, passing the message to `f`. /// in a loop, passing the message to `f`.
/// ///
/// This allows use of `GObjects` and futures in the same context. /// This allows use of `GObjects` and futures in the same context.
fn recv_glib<F>(self, f: F) ///
/// `deps` is a single reference, or tuple of references of clonable objects,
/// to be consumed inside the closure.
/// This avoids needing to `element.clone()` everywhere.
fn recv_glib<D, Fn>(self, deps: D, f: Fn)
where where
F: FnMut(T) + 'static; D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) + 'static;
/// Like [`BroadcastReceiverExt::recv_glib`], but the closure must return a [`Future`]. /// Like [`BroadcastReceiverExt::recv_glib`], but the closure must return a [`Future`].
fn recv_glib_async<Fn, F>(self, f: Fn) fn recv_glib_async<D, Fn, F>(self, deps: D, f: Fn)
where where
Fn: FnMut(T) -> F + 'static, D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) -> F + 'static,
F: Future; F: Future;
} }
@ -137,14 +154,17 @@ impl<T> BroadcastReceiverExt<T> for broadcast::Receiver<T>
where where
T: Debug + Clone + 'static, T: Debug + Clone + 'static,
{ {
fn recv_glib<F>(mut self, mut f: F) fn recv_glib<D, Fn>(mut self, deps: D, mut f: Fn)
where where
F: FnMut(T) + 'static, D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) + 'static,
{ {
let deps = deps.clone_content();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
loop { loop {
match self.recv().await { match self.recv().await {
Ok(val) => f(val), Ok(val) => f(&deps, val),
Err(broadcast::error::RecvError::Lagged(count)) => { Err(broadcast::error::RecvError::Lagged(count)) => {
tracing::warn!( tracing::warn!(
"Channel lagged behind by {count}, this may result in unexpected or broken behaviour" "Channel lagged behind by {count}, this may result in unexpected or broken behaviour"
@ -159,16 +179,19 @@ where
}); });
} }
fn recv_glib_async<Fn, F>(mut self, mut f: Fn) fn recv_glib_async<D, Fn, F>(mut self, deps: D, mut f: Fn)
where where
Fn: FnMut(T) -> F + 'static, D: Dependency,
D::Target: Clone + 'static,
Fn: FnMut(&D::Target, T) -> F + 'static,
F: Future, F: Future,
{ {
let deps = deps.clone_content();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
loop { loop {
match self.recv().await { match self.recv().await {
Ok(val) => { Ok(val) => {
f(val).await; f(&deps, val).await;
} }
Err(broadcast::error::RecvError::Lagged(count)) => { Err(broadcast::error::RecvError::Lagged(count)) => {
tracing::warn!( tracing::warn!(
@ -184,3 +207,55 @@ where
}); });
} }
} }
/// `recv_glib` callback dependency
/// or dependency tuple.
pub trait Dependency: Clone {
type Target;
fn clone_content(&self) -> Self::Target;
}
impl Dependency for () {
type Target = ();
fn clone_content(&self) -> Self::Target {}
}
impl<'a, T> Dependency for &'a T
where
T: Clone + 'a,
{
type Target = T;
fn clone_content(&self) -> T {
T::clone(self)
}
}
macro_rules! impl_dependency {
($($idx:tt $t:ident),+) => {
impl<'a, $($t),+> Dependency for ($(&'a $t),+)
where
$($t: Clone + 'a),+
{
type Target = ($($t),+);
fn clone_content(&self) -> Self::Target {
($(self.$idx.clone()),+)
}
}
};
}
impl_dependency!(0 T1, 1 T2);
impl_dependency!(0 T1, 1 T2, 2 T3);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11);
impl_dependency!(0 T1, 1 T2, 2 T3, 3 T4, 4 T5, 5 T6, 6 T7, 7 T8, 8 T9, 9 T10, 10 T11, 11 T12);

View file

@ -301,8 +301,7 @@ impl CommonConfig {
install_oneshot!(self.on_mouse_exit, connect_leave_notify_event); install_oneshot!(self.on_mouse_exit, connect_leave_notify_event);
if let Some(tooltip) = self.tooltip { if let Some(tooltip) = self.tooltip {
let container = container.clone(); dynamic_string(&tooltip, container, move |container, string| {
dynamic_string(&tooltip, move |string| {
container.set_tooltip_text(Some(&string)); container.set_tooltip_text(Some(&string));
}); });
} }
@ -314,19 +313,15 @@ impl CommonConfig {
container.show_all(); container.show_all();
}, },
|show_if| { |show_if| {
// need to keep clone here for the notify callback
let container = container.clone(); let container = container.clone();
{ show_if.subscribe((revealer, &container), |(revealer, container), success| {
let revealer = revealer.clone(); if success {
let container = container.clone(); container.show_all();
}
show_if.subscribe(move |success| { revealer.set_reveal_child(success);
if success { });
container.show_all();
}
revealer.set_reveal_child(success);
});
}
revealer.connect_child_revealed_notify(move |revealer| { revealer.connect_child_revealed_notify(move |revealer| {
if !revealer.reveals_child() { if !revealer.reveals_child() {

View file

@ -1,6 +1,6 @@
#[cfg(feature = "ipc")] #[cfg(feature = "ipc")]
use crate::Ironbar; use crate::Ironbar;
use crate::channels::{AsyncSenderExt, MpscReceiverExt}; use crate::channels::{AsyncSenderExt, Dependency, MpscReceiverExt};
use crate::script::Script; use crate::script::Script;
use crate::spawn; use crate::spawn;
use cfg_if::cfg_if; use cfg_if::cfg_if;
@ -19,9 +19,11 @@ pub enum DynamicBool {
} }
impl DynamicBool { impl DynamicBool {
pub fn subscribe<F>(self, f: F) pub fn subscribe<D, F>(self, deps: D, f: F)
where where
F: FnMut(bool) + 'static, D: Dependency,
D::Target: Clone + 'static,
F: FnMut(&D::Target, bool) + 'static,
{ {
let value = match self { let value = match self {
Self::Unknown(input) => { Self::Unknown(input) => {
@ -43,7 +45,7 @@ impl DynamicBool {
let (tx, rx) = mpsc::channel(32); let (tx, rx) = mpsc::channel(32);
rx.recv_glib(f); rx.recv_glib(deps, f);
spawn(async move { spawn(async move {
match value { match value {

View file

@ -1,6 +1,6 @@
#[cfg(feature = "ipc")] #[cfg(feature = "ipc")]
use crate::Ironbar; use crate::Ironbar;
use crate::channels::{AsyncSenderExt, MpscReceiverExt}; use crate::channels::{AsyncSenderExt, Dependency, MpscReceiverExt};
use crate::script::{OutputStream, Script}; use crate::script::{OutputStream, Script};
use crate::{arc_mut, lock, spawn}; use crate::{arc_mut, lock, spawn};
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -26,9 +26,11 @@ enum DynamicStringSegment {
/// label.set_label_escaped(&string); /// label.set_label_escaped(&string);
/// }); /// });
/// ``` /// ```
pub fn dynamic_string<F>(input: &str, f: F) pub fn dynamic_string<D, F>(input: &str, deps: D, f: F)
where where
F: FnMut(String) + 'static, D: Dependency,
D::Target: Clone + 'static,
F: FnMut(&D::Target, String) + 'static,
{ {
let (tokens, is_static) = parse_input(input); let (tokens, is_static) = parse_input(input);
@ -89,7 +91,7 @@ where
} }
} }
rx.recv_glib(f); rx.recv_glib(deps, f);
// initialize // initialize
if is_static { if is_static {

View file

@ -65,8 +65,7 @@ impl Ipc {
} }
}); });
let application = application.clone(); cmd_rx.recv_glib(application, move |application, command| {
cmd_rx.recv_glib(move |command| {
let res = Self::handle_command(command, &application, &ironbar); let res = Self::handle_command(command, &application, &ironbar);
res_tx.send_spawn(res); res_tx.send_spawn(res);
}); });

View file

@ -81,23 +81,17 @@ impl Module<Label> for Bindmode {
})); }));
} }
{ context.subscribe().recv_glib(&label, |label, mode| {
let label = label.clone(); trace!("mode: {:?}", mode);
label.set_use_markup(mode.pango_markup);
label.set_label_escaped(&mode.name);
let on_mode = move |mode: BindModeUpdate| { if mode.name.is_empty() {
trace!("mode: {:?}", mode); label.hide();
label.set_use_markup(mode.pango_markup); } else {
label.set_label_escaped(&mode.name); label.show();
}
if mode.name.is_empty() { });
label.hide();
} else {
label.show();
}
};
context.subscribe().recv_glib(on_mode);
}
Ok(ModuleParts { Ok(ModuleParts {
widget: label, widget: label,

View file

@ -192,7 +192,7 @@ impl Module<gtk::Box> for CairoModule {
} }
}); });
context.subscribe().recv_glib(move |_ev| { context.subscribe().recv_glib((), move |(), _ev| {
let res = fs::read_to_string(&self.path) let res = fs::read_to_string(&self.path)
.map(|s| s.replace("function draw", format!("function __draw_{id}").as_str())); .map(|s| s.replace("function draw", format!("function __draw_{id}").as_str()));

View file

@ -184,9 +184,9 @@ impl Module<Button> for ClipboardModule {
let mut items = HashMap::new(); let mut items = HashMap::new();
{ context
let hidden_option = hidden_option.clone(); .subscribe()
context.subscribe().recv_glib(move |event| { .recv_glib(&hidden_option, move |hidden_option, event| {
match event { match event {
ControllerEvent::Add(id, item) => { ControllerEvent::Add(id, item) => {
debug!("Adding new value with ID {}", id); debug!("Adding new value with ID {}", id);
@ -196,7 +196,7 @@ impl Module<Button> for ClipboardModule {
let button = match item.value.as_ref() { let button = match item.value.as_ref() {
ClipboardValue::Text(value) => { ClipboardValue::Text(value) => {
let button = RadioButton::from_widget(&hidden_option); let button = RadioButton::from_widget(hidden_option);
let label = Label::new(Some(value)); let label = Label::new(Some(value));
button.add(&label); button.add(&label);
@ -222,7 +222,7 @@ impl Module<Button> for ClipboardModule {
Ok(pixbuf) => { Ok(pixbuf) => {
let image = Image::from_pixbuf(Some(&pixbuf)); let image = Image::from_pixbuf(Some(&pixbuf));
let button = RadioButton::from_widget(&hidden_option); let button = RadioButton::from_widget(hidden_option);
button.set_image(Some(&image)); button.set_image(Some(&image));
button.set_always_show_image(true); button.set_always_show_image(true);
button.style_context().add_class("image"); button.style_context().add_class("image");
@ -320,7 +320,6 @@ impl Module<Button> for ClipboardModule {
} }
} }
}); });
}
container.show_all(); container.show_all();
hidden_option.hide(); hidden_option.hide();

View file

@ -139,7 +139,7 @@ impl Module<Button> for ClockModule {
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX); let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
let rx = context.subscribe(); let rx = context.subscribe();
rx.recv_glib(move |date| { rx.recv_glib((), move |(), date| {
let date_string = format!("{}", date.format_localized(&format, locale)); let date_string = format!("{}", date.format_localized(&format, locale));
label.set_label(&date_string); label.set_label(&date_string);
}); });
@ -173,7 +173,7 @@ impl Module<Button> for ClockModule {
let format = self.format_popup; let format = self.format_popup;
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX); let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
context.subscribe().recv_glib(move |date| { context.subscribe().recv_glib((), move |(), date| {
let date_string = format!("{}", date.format_localized(&format, locale)); let date_string = format!("{}", date.format_localized(&format, locale));
clock.set_label(&date_string); clock.set_label(&date_string);
}); });

View file

@ -73,7 +73,7 @@ impl CustomWidget for ButtonWidget {
button.add(&label); button.add(&label);
dynamic_string(&text, move |string| { dynamic_string(&text, (), move |(), string| {
label.set_label_escaped(&string); label.set_label_escaped(&string);
}); });
} }

View file

@ -44,19 +44,15 @@ impl CustomWidget for ImageWidget {
fn into_widget(self, context: CustomWidgetContext) -> Self::Widget { fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
let gtk_image = build!(self, Self::Widget); let gtk_image = build!(self, Self::Widget);
{ dynamic_string(&self.src, &gtk_image, move |gtk_image, src| {
let gtk_image = gtk_image.clone(); let gtk_image = gtk_image.clone();
let image_provider = context.image_provider.clone();
dynamic_string(&self.src, move |src| { glib::spawn_future_local(async move {
let gtk_image = gtk_image.clone(); image_provider
let image_provider = context.image_provider.clone(); .load_into_image_silent(&src, self.size, false, &gtk_image)
glib::spawn_future_local(async move { .await;
image_provider
.load_into_image_silent(&src, self.size, false, &gtk_image)
.await;
});
}); });
} });
gtk_image gtk_image
} }

View file

@ -55,12 +55,9 @@ impl CustomWidget for LabelWidget {
label.truncate(truncate); label.truncate(truncate);
} }
{ dynamic_string(&self.label, &label, move |label, string| {
let label = label.clone(); label.set_label_escaped(&string);
dynamic_string(&self.label, move |string| { });
label.set_label_escaped(&string);
});
}
label label
} }

View file

@ -95,14 +95,13 @@ impl CustomWidget for ProgressWidget {
.await; .await;
}); });
rx.recv_glib(move |value| progress.set_fraction(value / self.max)); rx.recv_glib((), move |(), value| progress.set_fraction(value / self.max));
} }
if let Some(text) = self.label { if let Some(text) = self.label {
let progress = progress.clone();
progress.set_show_text(true); progress.set_show_text(true);
dynamic_string(&text, move |string| { dynamic_string(&text, &progress, move |progress, string| {
progress.set_text(Some(&string)); progress.set_text(Some(&string));
}); });
} }

View file

@ -165,7 +165,7 @@ impl CustomWidget for SliderWidget {
.await; .await;
}); });
rx.recv_glib(move |value| scale.set_value(value)); rx.recv_glib((), move |(), value| scale.set_value(value));
} }
scale scale

View file

@ -154,7 +154,7 @@ impl Module<gtk::Box> for FocusedModule {
{ {
let image_provider = context.ironbar.image_provider(); let image_provider = context.ironbar.image_provider();
context.subscribe().recv_glib_async(move |data| { context.subscribe().recv_glib_async((), move |(), data| {
let icon = icon.clone(); let icon = icon.clone();
let label = label.clone(); let label = label.clone();
let image_provider = image_provider.clone(); let image_provider = image_provider.clone();

View file

@ -305,39 +305,43 @@ impl Module<gtk::Box> for KeyboardModule {
} }
let icons = self.icons; let icons = self.icons;
let handle_event = move |ev: KeyboardUpdate| match ev { context
KeyboardUpdate::Key(ev) => { .subscribe()
let parts = match (ev.key, ev.state) { .recv_glib((), move |(), ev: KeyboardUpdate| match ev {
(Key::Caps, true) if self.show_caps => Some((&caps, icons.caps_on.as_str())), KeyboardUpdate::Key(ev) => {
(Key::Caps, false) if self.show_caps => Some((&caps, icons.caps_off.as_str())), let parts = match (ev.key, ev.state) {
(Key::Num, true) if self.show_num => Some((&num, icons.num_on.as_str())), (Key::Caps, true) if self.show_caps => {
(Key::Num, false) if self.show_num => Some((&num, icons.num_off.as_str())), Some((&caps, icons.caps_on.as_str()))
(Key::Scroll, true) if self.show_scroll => { }
Some((&scroll, icons.scroll_on.as_str())) (Key::Caps, false) if self.show_caps => {
} Some((&caps, icons.caps_off.as_str()))
(Key::Scroll, false) if self.show_scroll => { }
Some((&scroll, icons.scroll_off.as_str())) (Key::Num, true) if self.show_num => Some((&num, icons.num_on.as_str())),
} (Key::Num, false) if self.show_num => Some((&num, icons.num_off.as_str())),
_ => None, (Key::Scroll, true) if self.show_scroll => {
}; Some((&scroll, icons.scroll_on.as_str()))
}
(Key::Scroll, false) if self.show_scroll => {
Some((&scroll, icons.scroll_off.as_str()))
}
_ => None,
};
if let Some((label, input)) = parts { if let Some((label, input)) = parts {
label.set_label(Some(input)); label.set_label(Some(input));
if ev.state { if ev.state {
label.add_class("enabled"); label.add_class("enabled");
} else { } else {
label.remove_class("enabled"); label.remove_class("enabled");
}
} }
} }
} KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => {
KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => { let text = icons.layout_map.get(&language).unwrap_or(&language);
let text = icons.layout_map.get(&language).unwrap_or(&language); layout_button.set_label(text);
layout_button.set_label(text); }
} });
};
context.subscribe().recv_glib(handle_event);
Ok(ModuleParts::new(container, None)) Ok(ModuleParts::new(container, None))
} }
} }

View file

@ -56,8 +56,7 @@ impl Module<Label> for LabelModule {
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_rx: mpsc::Receiver<Self::ReceiveMessage>, _rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> Result<()> { ) -> Result<()> {
let tx = context.tx.clone(); dynamic_string(&self.label, &context.tx, move |tx, string| {
dynamic_string(&self.label, move |string| {
tx.send_update_spawn(string); tx.send_update_spawn(string);
}); });
@ -79,12 +78,9 @@ impl Module<Label> for LabelModule {
label.truncate(truncate); label.truncate(truncate);
} }
{ context.subscribe().recv_glib(&label, move |label, string| {
let label = label.clone(); label.set_label_escaped(&string)
context });
.subscribe()
.recv_glib(move |string| label.set_label_escaped(&string));
}
Ok(ModuleParts { Ok(ModuleParts {
widget: label, widget: label,

View file

@ -458,10 +458,6 @@ impl Module<gtk::Box> for LauncherModule {
); );
{ {
let container = container.clone();
let controller_tx = context.controller_tx.clone();
let appearance_options = AppearanceOptions { let appearance_options = AppearanceOptions {
show_names: self.show_names, show_names: self.show_names,
show_icons: self.show_icons, show_icons: self.show_icons,
@ -477,114 +473,114 @@ impl Module<gtk::Box> for LauncherModule {
let mut buttons = IndexMap::<String, ItemButton>::new(); let mut buttons = IndexMap::<String, ItemButton>::new();
let tx = context.tx.clone();
let rx = context.subscribe(); let rx = context.subscribe();
let handle_event = move |event: LauncherUpdate| { rx.recv_glib(
// all widgets show by default (&container, &context.controller_tx, &context.tx),
// so check if pagination should be shown move |(container, controller_tx, tx), event: LauncherUpdate| {
// to ensure correct state on init. // all widgets show by default
if buttons.len() <= page_size { // so check if pagination should be shown
pagination.hide(); // to ensure correct state on init.
} if buttons.len() <= page_size {
pagination.hide();
}
match event { match event {
LauncherUpdate::AddItem(item) => { LauncherUpdate::AddItem(item) => {
debug!("Adding item with id '{}' to the bar: {item:?}", item.app_id); debug!("Adding item with id '{}' to the bar: {item:?}", item.app_id);
if let Some(button) = buttons.get(&item.app_id) { if let Some(button) = buttons.get(&item.app_id) {
button.set_open(true); button.set_open(true);
button.set_focused(item.open_state.is_focused()); button.set_focused(item.open_state.is_focused());
} else {
let button = ItemButton::new(
&item,
appearance_options,
image_provider.clone(),
bar_position,
&tx,
&controller_tx,
);
if self.reversed {
container.pack_end(&*button.button, false, false, 0);
} else { } else {
container.add(&*button.button); let button = ItemButton::new(
} &item,
appearance_options,
image_provider.clone(),
bar_position,
&tx,
&controller_tx,
);
if buttons.len() + 1 >= pagination.offset() + page_size { if self.reversed {
button.button.set_visible(false); container.pack_end(&*button.button, false, false, 0);
pagination.set_sensitive_fwd(true); } else {
} container.add(&*button.button);
if buttons.len() + 1 > page_size {
pagination.show_all();
}
buttons.insert(item.app_id, button);
}
}
LauncherUpdate::AddWindow(app_id, win) => {
if let Some(button) = buttons.get(&app_id) {
button.set_open(true);
button.set_focused(win.open_state.is_focused());
write_lock!(button.menu_state).num_windows += 1;
}
}
LauncherUpdate::RemoveItem(app_id) => {
debug!("Removing item with id {}", app_id);
if let Some(button) = buttons.get(&app_id) {
if button.persistent {
button.set_open(false);
if button.show_names {
button.button.label.set_label(&app_id);
} }
} else {
container.remove(&button.button.button); if buttons.len() + 1 >= pagination.offset() + page_size {
buttons.shift_remove(&app_id); button.button.set_visible(false);
pagination.set_sensitive_fwd(true);
}
if buttons.len() + 1 > page_size {
pagination.show_all();
}
buttons.insert(item.app_id, button);
} }
} }
LauncherUpdate::AddWindow(app_id, win) => {
if buttons.len() < pagination.offset() + page_size {
pagination.set_sensitive_fwd(false);
}
if buttons.len() <= page_size {
pagination.hide();
}
}
LauncherUpdate::RemoveWindow(app_id, win_id) => {
debug!("Removing window {win_id} with id {app_id}");
if let Some(button) = buttons.get(&app_id) {
button.set_focused(false);
let mut menu_state = write_lock!(button.menu_state);
menu_state.num_windows -= 1;
}
}
LauncherUpdate::Focus(app_id, focus) => {
debug!("Changing focus to {} on item with id {}", focus, app_id);
if let Some(button) = buttons.get(&app_id) {
button.set_focused(focus);
}
}
LauncherUpdate::Title(app_id, _, name) => {
debug!("Updating title for item with id {}: {:?}", app_id, name);
if show_names {
if let Some(button) = buttons.get(&app_id) { if let Some(button) = buttons.get(&app_id) {
button.button.label.set_label(&name); button.set_open(true);
button.set_focused(win.open_state.is_focused());
write_lock!(button.menu_state).num_windows += 1;
} }
} }
} LauncherUpdate::RemoveItem(app_id) => {
LauncherUpdate::Hover(_) => {} debug!("Removing item with id {}", app_id);
}
};
rx.recv_glib(handle_event); if let Some(button) = buttons.get(&app_id) {
if button.persistent {
button.set_open(false);
if button.show_names {
button.button.label.set_label(&app_id);
}
} else {
container.remove(&button.button.button);
buttons.shift_remove(&app_id);
}
}
if buttons.len() < pagination.offset() + page_size {
pagination.set_sensitive_fwd(false);
}
if buttons.len() <= page_size {
pagination.hide();
}
}
LauncherUpdate::RemoveWindow(app_id, win_id) => {
debug!("Removing window {win_id} with id {app_id}");
if let Some(button) = buttons.get(&app_id) {
button.set_focused(false);
let mut menu_state = write_lock!(button.menu_state);
menu_state.num_windows -= 1;
}
}
LauncherUpdate::Focus(app_id, focus) => {
debug!("Changing focus to {} on item with id {}", focus, app_id);
if let Some(button) = buttons.get(&app_id) {
button.set_focused(focus);
}
}
LauncherUpdate::Title(app_id, _, name) => {
debug!("Updating title for item with id {}: {:?}", app_id, name);
if show_names {
if let Some(button) = buttons.get(&app_id) {
button.button.label.set_label(&name);
}
}
}
LauncherUpdate::Hover(_) => {}
}
},
);
} }
let popup = self.into_popup(context, info).into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly let popup = self.into_popup(context, info).into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
@ -611,9 +607,9 @@ impl Module<gtk::Box> for LauncherModule {
let mut buttons = IndexMap::<String, IndexMap<usize, ImageTextButton>>::new(); let mut buttons = IndexMap::<String, IndexMap<usize, ImageTextButton>>::new();
{ context
let container = container.clone(); .subscribe()
context.subscribe().recv_glib(move |event| { .recv_glib(&container, move |container, event| {
match event { match event {
LauncherUpdate::AddItem(item) => { LauncherUpdate::AddItem(item) => {
let app_id = item.app_id.clone(); let app_id = item.app_id.clone();
@ -703,7 +699,6 @@ impl Module<gtk::Box> for LauncherModule {
_ => {} _ => {}
} }
}); });
}
Some(container) Some(container)
} }

View file

@ -209,17 +209,18 @@ impl Module<Button> for MenuModule {
center_section.add_class("main-center"); center_section.add_class("main-center");
end_section.add_class("main-end"); end_section.add_class("main-end");
let container2 = container.clone(); let truncate_mode = self.truncate;
{
let main_menu = main_menu.clone();
let container = container.clone();
let start_section = start_section.clone();
let center_section = center_section.clone();
let end_section = end_section.clone();
let truncate_mode = self.truncate; context.subscribe().recv_glib(
(
context.subscribe().recv_glib(move |applications| { &main_menu,
&container,
&start_section,
&center_section,
&end_section,
),
move |(main_menu, container, start_section, center_section, end_section),
applications| {
for application in applications.iter() { for application in applications.iter() {
let mut inserted = false; let mut inserted = false;
@ -288,15 +289,14 @@ impl Module<Button> for MenuModule {
add_entries!(&center_entries, &center_section); add_entries!(&center_entries, &center_section);
add_entries!(&end_entries, &end_section); add_entries!(&end_entries, &end_section);
main_menu.add(&start_section); main_menu.add(start_section);
main_menu.add(&center_section); main_menu.add(center_section);
main_menu.add(&end_section); main_menu.add(end_section);
}); },
} );
{ {
let container = container2; let container = container.clone();
context.popup.window.connect_hide(move |_| { context.popup.window.connect_hide(move |_| {
start_section.foreach(|child| { start_section.foreach(|child| {
child.remove_class("open"); child.remove_class("open");

View file

@ -388,8 +388,7 @@ impl ModuleFactory for BarModuleFactory {
) where ) where
TSend: Debug + Clone + Send + 'static, TSend: Debug + Clone + Send + 'static,
{ {
let popup = self.popup.clone(); rx.recv_glib(&self.popup, move |popup, ev| match ev {
rx.recv_glib(move |ev| match ev {
ModuleUpdateEvent::Update(update) => { ModuleUpdateEvent::Update(update) => {
tx.send_expect(update); tx.send_expect(update);
} }
@ -464,10 +463,9 @@ impl ModuleFactory for PopupModuleFactory {
) where ) where
TSend: Debug + Clone + Send + 'static, TSend: Debug + Clone + Send + 'static,
{ {
let popup = self.popup.clone();
let button_id = self.button_id; let button_id = self.button_id;
rx.recv_glib(move |ev| match ev { rx.recv_glib(&self.popup, move |popup, ev| match ev {
ModuleUpdateEvent::Update(update) => { ModuleUpdateEvent::Update(update) => {
tx.send_expect(update); tx.send_expect(update);
} }

View file

@ -217,47 +217,42 @@ impl Module<Button> for MusicModule {
}); });
} }
{ let rx = context.subscribe();
let button = button.clone();
let tx = context.tx.clone(); rx.recv_glib((&button, &context.tx), move |(button, tx), event| {
let rx = context.subscribe(); let ControllerEvent::Update(mut event) = event else {
return;
};
rx.recv_glib(move |event| { if let Some(event) = event.take() {
let ControllerEvent::Update(mut event) = event else { label.set_label_escaped(&event.display_string);
return;
};
if let Some(event) = event.take() { button.show();
label.set_label_escaped(&event.display_string);
button.show(); match event.status.state {
PlayerState::Playing if self.show_status_icon => {
match event.status.state { icon_play.show();
PlayerState::Playing if self.show_status_icon => {
icon_play.show();
icon_pause.hide();
}
PlayerState::Paused if self.show_status_icon => {
icon_pause.show();
icon_play.hide();
}
PlayerState::Stopped => {
button.hide();
}
_ => {}
}
if !self.show_status_icon {
icon_pause.hide(); icon_pause.hide();
}
PlayerState::Paused if self.show_status_icon => {
icon_pause.show();
icon_play.hide(); icon_play.hide();
} }
} else { PlayerState::Stopped => {
button.hide(); button.hide();
tx.send_spawn(ModuleUpdateEvent::ClosePopup); }
_ => {}
} }
});
}; if !self.show_status_icon {
icon_pause.hide();
icon_play.hide();
}
} else {
button.hide();
tx.send_spawn(ModuleUpdateEvent::ClosePopup);
}
});
let popup = self let popup = self
.into_popup(context, info) .into_popup(context, info)
@ -403,121 +398,114 @@ impl Module<Button> for MusicModule {
container.show_all(); container.show_all();
{ let image_size = self.cover_image_size;
let image_size = self.cover_image_size;
let mut prev_cover = None; let mut prev_cover = None;
context.subscribe().recv_glib(move |event| { context.subscribe().recv_glib((), move |(), event| {
match event { match event {
ControllerEvent::Update(Some(update)) => { ControllerEvent::Update(Some(update)) => {
// only update art when album changes // only update art when album changes
let new_cover = update.song.cover_path; let new_cover = update.song.cover_path;
if prev_cover != new_cover { if prev_cover != new_cover {
prev_cover.clone_from(&new_cover); prev_cover.clone_from(&new_cover);
if let Some(cover_path) = new_cover { if let Some(cover_path) = new_cover {
let image_provider = image_provider.clone(); let image_provider = image_provider.clone();
let album_image = album_image.clone(); let album_image = album_image.clone();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
let success = match image_provider let success = match image_provider
.load_into_image( .load_into_image(&cover_path, image_size, false, &album_image)
&cover_path, .await
image_size, {
false, Ok(true) => {
&album_image, album_image.show();
) true
.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();
} }
}); Ok(false) => {
} else { warn!("failed to parse image: {}", cover_path);
album_image.set_from_pixbuf(None); false
album_image.hide(); }
} Err(err) => {
} error!("failed to load image: {}", err);
false
}
};
update_popup_metadata_label(update.song.title, &title_label); if !success {
update_popup_metadata_label(update.song.album, &album_label); album_image.set_from_pixbuf(None);
update_popup_metadata_label(update.song.artist, &artist_label); album_image.hide();
}
match update.status.state { });
PlayerState::Stopped => {
btn_pause.hide();
btn_play.show();
btn_play.set_sensitive(false);
}
PlayerState::Playing => {
btn_play.set_sensitive(false);
btn_play.hide();
btn_pause.set_sensitive(true);
btn_pause.show();
}
PlayerState::Paused => {
btn_pause.set_sensitive(false);
btn_pause.hide();
btn_play.set_sensitive(true);
btn_play.show();
}
}
let enable_prev = update.status.playlist_position > 0;
let enable_next =
update.status.playlist_position < update.status.playlist_length;
btn_prev.set_sensitive(enable_prev);
btn_next.set_sensitive(enable_next);
if let Some(volume) = update.status.volume_percent {
volume_slider.set_value(f64::from(volume));
volume_box.show();
} else { } else {
volume_box.hide(); album_image.set_from_pixbuf(None);
album_image.hide();
} }
} }
ControllerEvent::UpdateProgress(progress_tick)
if !drag_lock.load(Ordering::Relaxed) =>
{
if let (Some(elapsed), Some(duration)) =
(progress_tick.elapsed, progress_tick.duration)
{
progress_label.set_label_escaped(&format!(
"{}/{}",
format_time(elapsed),
format_time(duration)
));
progress.set_value(elapsed.as_secs_f64()); update_popup_metadata_label(update.song.title, &title_label);
progress.set_range(0.0, duration.as_secs_f64()); update_popup_metadata_label(update.song.album, &album_label);
progress_box.show_all(); update_popup_metadata_label(update.song.artist, &artist_label);
} else {
progress_box.hide(); match update.status.state {
PlayerState::Stopped => {
btn_pause.hide();
btn_play.show();
btn_play.set_sensitive(false);
}
PlayerState::Playing => {
btn_play.set_sensitive(false);
btn_play.hide();
btn_pause.set_sensitive(true);
btn_pause.show();
}
PlayerState::Paused => {
btn_pause.set_sensitive(false);
btn_pause.hide();
btn_play.set_sensitive(true);
btn_play.show();
} }
} }
_ => {}
let enable_prev = update.status.playlist_position > 0;
let enable_next =
update.status.playlist_position < update.status.playlist_length;
btn_prev.set_sensitive(enable_prev);
btn_next.set_sensitive(enable_next);
if let Some(volume) = update.status.volume_percent {
volume_slider.set_value(f64::from(volume));
volume_box.show();
} else {
volume_box.hide();
}
} }
}); ControllerEvent::UpdateProgress(progress_tick)
} if !drag_lock.load(Ordering::Relaxed) =>
{
if let (Some(elapsed), Some(duration)) =
(progress_tick.elapsed, progress_tick.duration)
{
progress_label.set_label_escaped(&format!(
"{}/{}",
format_time(elapsed),
format_time(duration)
));
progress.set_value(elapsed.as_secs_f64());
progress.set_range(0.0, duration.as_secs_f64());
progress_box.show_all();
} else {
progress_box.hide();
}
}
_ => {}
}
});
Some(container) Some(container)
} }

View file

@ -76,7 +76,7 @@ impl Module<GtkBox> for NetworkManagerModule {
} }
}); });
context.subscribe().recv_glib_async(move |state| { context.subscribe().recv_glib_async((), move |(), state| {
let image_provider = image_provider.clone(); let image_provider = image_provider.clone();
let icon = icon.clone(); let icon = icon.clone();

View file

@ -204,17 +204,13 @@ impl Module<Overlay> for NotificationsModule {
ctx.send_spawn(UiEvent::ToggleVisibility); ctx.send_spawn(UiEvent::ToggleVisibility);
}); });
{ context.subscribe().recv_glib(&button, move |button, ev| {
let button = button.clone(); let icon = self.icons.icon(ev);
button.set_label(icon);
context.subscribe().recv_glib(move |ev| { label.set_label(&ev.count.to_string());
let icon = self.icons.icon(ev); label.set_visible(self.show_count && ev.count > 0);
button.set_label(icon); });
label.set_label(&ev.count.to_string());
label.set_visible(self.show_count && ev.count > 0);
});
}
Ok(ModuleParts { Ok(ModuleParts {
widget: overlay, widget: overlay,

View file

@ -110,12 +110,9 @@ impl Module<Label> for ScriptModule {
.justify(self.layout.justify.into()) .justify(self.layout.justify.into())
.build(); .build();
{ context
let label = label.clone(); .subscribe()
context .recv_glib(&label, |label, s| label.set_label_escaped(&s));
.subscribe()
.recv_glib(move |s| label.set_label_escaped(&s));
}
Ok(ModuleParts { Ok(ModuleParts {
widget: label, widget: label,

View file

@ -303,7 +303,7 @@ impl Module<gtk::Box> for SysInfoModule {
labels.push(label); labels.push(label);
} }
context.subscribe().recv_glib(move |data| { context.subscribe().recv_glib((), move |(), data| {
let label = &labels[data.0]; let label = &labels[data.0];
label.set_label_escaped(&data.1); label.set_label_escaped(&data.1);
}); });

View file

@ -117,13 +117,13 @@ impl Module<gtk::Box> for TrayModule {
// Each widget is wrapped in an EventBox, copying what Waybar does here. // Each widget is wrapped in an EventBox, copying what Waybar does here.
let container = gtk::Box::new(orientation, 10); let container = gtk::Box::new(orientation, 10);
{ let mut menus = HashMap::new();
let container = container.clone(); let icon_theme = context.ironbar.image_provider().icon_theme();
let mut menus = HashMap::new();
let icon_theme = context.ironbar.image_provider().icon_theme();
// listen for UI updates // listen for UI updates
context.subscribe().recv_glib(move |update| { context
.subscribe()
.recv_glib(&container, move |container, update| {
on_update( on_update(
update, update,
&container, &container,
@ -133,7 +133,6 @@ impl Module<gtk::Box> for TrayModule {
self.prefer_theme_icons, self.prefer_theme_icons,
); );
}); });
};
Ok(ModuleParts { Ok(ModuleParts {
widget: container, widget: container,

View file

@ -202,7 +202,7 @@ impl Module<Button> for UpowerModule {
let rx = context.subscribe(); let rx = context.subscribe();
let provider = context.ironbar.image_provider(); let provider = context.ironbar.image_provider();
rx.recv_glib_async(move |properties| { rx.recv_glib_async((), move |(), properties| {
let state = properties.state; let state = properties.state;
let is_charging = let is_charging =
@ -258,7 +258,7 @@ impl Module<Button> for UpowerModule {
label.add_class("upower-details"); label.add_class("upower-details");
container.add(&label); container.add(&label);
context.subscribe().recv_glib(move |properties| { context.subscribe().recv_glib((), move |(), properties| {
let state = properties.state; let state = properties.state;
let format = match state { let format = match state {
BatteryState::Charging | BatteryState::PendingCharge => { BatteryState::Charging | BatteryState::PendingCharge => {

View file

@ -228,13 +228,11 @@ impl Module<Button> for VolumeModule {
}); });
} }
{ let rx = context.subscribe();
let rx = context.subscribe();
let icons = self.icons.clone();
let format = self.format.clone(); rx.recv_glib(
(&self.icons, &self.format),
rx.recv_glib(move |event| match event { move |(icons, format), event| match event {
Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => { Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => {
let label = format let label = format
.replace( .replace(
@ -251,8 +249,8 @@ impl Module<Button> for VolumeModule {
button_label.set_label_escaped(&label); button_label.set_label_escaped(&label);
} }
_ => {} _ => {}
}); },
} );
let popup = self let popup = self
.into_popup(context, info) .into_popup(context, info)
@ -351,13 +349,11 @@ impl Module<Button> for VolumeModule {
container.show_all(); container.show_all();
let mut inputs = HashMap::new(); let mut inputs = HashMap::new();
let mut sinks = vec![];
{ context
let input_container = input_container.clone(); .subscribe()
.recv_glib(&input_container, move |input_container, event| {
let mut sinks = vec![];
context.subscribe().recv_glib(move |event| {
match event { match event {
Event::AddSink(info) => { Event::AddSink(info) => {
sink_selector.append(Some(&info.name), &info.description); sink_selector.append(Some(&info.name), &info.description);
@ -477,7 +473,6 @@ impl Module<Button> for VolumeModule {
} }
} }
}); });
}
Some(container) Some(container)
} }

View file

@ -317,93 +317,95 @@ impl Module<gtk::Box> for WorkspacesModule {
} }
let name_map = self.name_map; let name_map = self.name_map;
let handle_event = move |event: WorkspaceUpdate| match event { context
WorkspaceUpdate::Init(workspaces) => { .subscribe()
if has_initialized { .recv_glib((), move |(), event| match event {
return; WorkspaceUpdate::Init(workspaces) => {
} if has_initialized {
return;
}
trace!("Creating workspace buttons"); trace!("Creating workspace buttons");
for workspace in workspaces for workspace in workspaces
.into_iter() .into_iter()
.filter(|w| self.all_monitors || w.monitor == output_name) .filter(|w| self.all_monitors || w.monitor == output_name)
.filter(|w| !self.hidden.contains(&w.name)) .filter(|w| !self.hidden.contains(&w.name))
{ {
add_workspace(workspace, &mut button_map); add_workspace(workspace, &mut button_map);
} }
reorder!();
has_initialized = true;
}
WorkspaceUpdate::Add(workspace) => {
if !self.hidden.contains(&workspace.name)
&& (self.all_monitors || workspace.monitor == output_name)
{
add_workspace(workspace, &mut button_map);
}
reorder!();
}
WorkspaceUpdate::Remove(id) => remove_workspace(id, &mut button_map),
WorkspaceUpdate::Move(workspace) => {
if self.all_monitors {
return;
}
if workspace.monitor == output_name && !self.hidden.contains(&workspace.name) {
add_workspace(workspace, &mut button_map);
reorder!(); reorder!();
} else {
remove_workspace(workspace.id, &mut button_map); has_initialized = true;
} }
} WorkspaceUpdate::Add(workspace) => {
WorkspaceUpdate::Focus { old, new } => { if !self.hidden.contains(&workspace.name)
// Open states are calculated here rather than using the workspace visibility && (self.all_monitors || workspace.monitor == output_name)
// as that seems to come back wrong, at least on Hyprland. {
// Likely a deeper issue that needs exploring. add_workspace(workspace, &mut button_map);
}
if let Some(old) = old { reorder!();
if let Some(button) = button_map.find_button_mut(&old) { }
let open_state = if new.monitor == old.monitor { WorkspaceUpdate::Remove(id) => remove_workspace(id, &mut button_map),
OpenState::Hidden WorkspaceUpdate::Move(workspace) => {
} else { if self.all_monitors {
OpenState::Visible return;
}; }
button.set_open_state(open_state); if workspace.monitor == output_name
&& !self.hidden.contains(&workspace.name)
{
add_workspace(workspace, &mut button_map);
reorder!();
} else {
remove_workspace(workspace.id, &mut button_map);
} }
} }
WorkspaceUpdate::Focus { old, new } => {
// Open states are calculated here rather than using the workspace visibility
// as that seems to come back wrong, at least on Hyprland.
// Likely a deeper issue that needs exploring.
if let Some(button) = button_map.find_button_mut(&new) { if let Some(old) = old {
button.set_open_state(OpenState::Focused); if let Some(button) = button_map.find_button_mut(&old) {
} let open_state = if new.monitor == old.monitor {
} OpenState::Hidden
WorkspaceUpdate::Rename { id, name } => { } else {
if let Some(button) = button_map OpenState::Visible
.get(&Identifier::Id(id)) };
.or_else(|| button_map.get(&Identifier::Name(name.clone())))
.map(Button::button)
{
let display_name = name_map.get(&name).unwrap_or(&name);
button.set_label(display_name); button.set_open_state(open_state);
button.set_widget_name(&name); }
} }
}
WorkspaceUpdate::Urgent { id, urgent } => {
if let Some(button) = button_map
.get(&Identifier::Id(id))
.or_else(|| button_map.find_button_by_id(id))
{
button.set_urgent(urgent);
}
}
WorkspaceUpdate::Unknown => warn!("received unknown type workspace event"),
};
context.subscribe().recv_glib(handle_event); if let Some(button) = button_map.find_button_mut(&new) {
button.set_open_state(OpenState::Focused);
}
}
WorkspaceUpdate::Rename { id, name } => {
if let Some(button) = button_map
.get(&Identifier::Id(id))
.or_else(|| button_map.get(&Identifier::Name(name.clone())))
.map(Button::button)
{
let display_name = name_map.get(&name).unwrap_or(&name);
button.set_label(display_name);
button.set_widget_name(&name);
}
}
WorkspaceUpdate::Urgent { id, urgent } => {
if let Some(button) = button_map
.get(&Identifier::Id(id))
.or_else(|| button_map.find_button_by_id(id))
{
button.set_urgent(urgent);
}
}
WorkspaceUpdate::Unknown => warn!("received unknown type workspace event"),
});
} }
Ok(ModuleParts { Ok(ModuleParts {

View file

@ -113,21 +113,15 @@ impl Popup {
let output_size = rc_mut!(output_size); let output_size = rc_mut!(output_size);
// respond to resolution changes // respond to resolution changes
{ let rx = ironbar.clients.borrow_mut().wayland().subscribe_outputs();
let output_size = output_size.clone(); let output_name = module_info.output_name.to_string();
let output_name = module_info.output_name.to_string(); rx.recv_glib(&output_size, move |output_size, event: OutputEvent| {
if event.event_type == OutputEventType::Update
let on_output_event = move |event: OutputEvent| { && event.output.name.unwrap_or_default() == output_name
if event.event_type == OutputEventType::Update {
&& event.output.name.unwrap_or_default() == output_name *output_size.borrow_mut() = event.output.logical_size.unwrap_or_default();
{ }
*output_size.borrow_mut() = event.output.logical_size.unwrap_or_default(); });
}
};
let rx = ironbar.clients.borrow_mut().wayland().subscribe_outputs();
rx.recv_glib(on_output_event);
}
Self { Self {
window: win, window: win,

View file

@ -73,7 +73,7 @@ pub fn load_css(style_path: PathBuf, application: Application) {
} }
}); });
rx.recv_glib(move |path| { rx.recv_glib((), move |(), path| {
info!("Reloading CSS"); info!("Reloading CSS");
if let Err(err) = provider.load_from_file(&gio::File::for_path(path)) { if let Err(err) = provider.load_from_file(&gio::File::for_path(path)) {
error!("{:?}", Report::new(err) error!("{:?}", Report::new(err)