diff --git a/files/icons/awesome4.toml b/files/icons/awesome4.toml index 5f265efa8..b8dc4752a 100644 --- a/files/icons/awesome4.toml +++ b/files/icons/awesome4.toml @@ -58,6 +58,7 @@ pomodoro_break = "\uf0f4" # fa-coffee pomodoro_paused = "\uf04c" # fa-pause pomodoro_started = "\uf04b" # fa-play pomodoro_stopped = "\uf04d" # fa-stop +refresh = "\uf021" # fa-refresh resolution = "\uf096" # fa-square-o scratchpad = "\uf2d2" # fa-window-restore tasks = "\uf0ae" # fa-tasks diff --git a/files/icons/awesome5.toml b/files/icons/awesome5.toml index 2a878f4a7..d5d2eeae4 100644 --- a/files/icons/awesome5.toml +++ b/files/icons/awesome5.toml @@ -58,6 +58,7 @@ pomodoro_break = "\uf0f4" # fa-coffee pomodoro_paused = "\uf04c" # fa-pause pomodoro_started = "\uf04b" # fa-play pomodoro_stopped = "\uf04d" # fa-stop +refresh = "\uf021" # fa-sync resolution = "\uf096" # fa-square-o scratchpad = "\uf2d2" # fa-window-restore tasks = "\uf0ae" diff --git a/files/icons/awesome6.toml b/files/icons/awesome6.toml index b7b258a6e..54ab97af4 100644 --- a/files/icons/awesome6.toml +++ b/files/icons/awesome6.toml @@ -62,6 +62,7 @@ pomodoro_break = "\uf0f4" # fa-coffee pomodoro_paused = "\uf04c" # fa-pause pomodoro_started = "\uf04b" # fa-play pomodoro_stopped = "\uf04d" # fa-stop +refresh = "\uf021" # fa-arrows-rotate resolution = "\uf096" # fa-square-o scratchpad = "\uf2d2" # fa-window-restore tasks = "\uf0ae" diff --git a/files/icons/emoji.toml b/files/icons/emoji.toml index 3a9bdcc1c..0392b8cbc 100644 --- a/files/icons/emoji.toml +++ b/files/icons/emoji.toml @@ -54,6 +54,7 @@ pomodoro_break = "☕" pomodoro_paused = "⏸️" pomodoro_started = "▶️" pomodoro_stopped = "⏹️" +refresh = "🔄" resolution = "🔳" scratchpad = "🗔" tasks = "✅" diff --git a/files/icons/material-nf.toml b/files/icons/material-nf.toml index 4013ba4e5..891749f18 100644 --- a/files/icons/material-nf.toml +++ b/files/icons/material-nf.toml @@ -91,6 +91,7 @@ pomodoro_break = "\U000f0176" # nf-md-coffee pomodoro_paused = "\U000f03e4" # nf-md-pause pomodoro_started = "\U000f040a" # nf-md-play pomodoro_stopped = "\U000f04db" # nf-md-stop +refresh = "\U000f0450" # nf-md-refresh resolution = "\U000f0293" # nf-md-fullscreen scratchpad = "\U000f05b2" # nf-md-window_restore tasks = "\U000f05c7" # nf-md-playlist_check diff --git a/files/icons/material.toml b/files/icons/material.toml index feee90def..5bc9c4996 100644 --- a/files/icons/material.toml +++ b/files/icons/material.toml @@ -75,6 +75,7 @@ pomodoro_break = "\uefef" # coffee | TODO: broken? pomodoro_paused = "\ue034" # pause pomodoro_started = "\ue037" # play_arrow pomodoro_stopped = "\uef6a" # play_disabled ef6a | TODO: broken? +refresh = "\ue5d5" # refresh resolution = "\uf152" # crop-square-rounded scratchpad = "\ue883" # flip_to_front tasks = "\ue8f9" # work diff --git a/src/blocks.rs b/src/blocks.rs index 43755a5c3..f18e2b383 100644 --- a/src/blocks.rs +++ b/src/blocks.rs @@ -12,6 +12,7 @@ //! `error_format` | Overrides global `error_format` | None //! `error_fullscreen_format` | Overrides global `error_fullscreen_format` | None //! `error_interval` | How long to wait until restarting the block after an error occurred. | `5` +//! `max_retries` | How many times should a block be restarted the block after an error occurred. If no limit is specified none will be enforced. | `None` //! `[block.theme_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None //! `[block.icons_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None //! `[[block.click]]` | Set or override click action for the block. See below for details. | Block default / None @@ -44,6 +45,8 @@ use crate::geolocator::{Geolocator, IPAddressInfo}; use crate::widget::Widget; use crate::{BoxedFuture, Request, RequestCmd}; +pub(super) const RESTART_BLOCK_BTN: &str = "restart_block_btn"; + macro_rules! define_blocks { { $( @@ -87,14 +90,37 @@ macro_rules! define_blocks { $(#[cfg(feature = $feat)])? #[allow(deprecated)] Self::$block(config) => futures.push(async move { - while let Err(err) = $block::run(&config, &api).await { - if api.set_error(err).is_err() { + let mut error_count: u8 = 0; + while let Err(mut err) = $block::run(&config, &api).await { + let Ok(mut actions) = api.get_actions() else { return }; + if api.set_default_actions(&[ + (MouseButton::Left, Some(RESTART_BLOCK_BTN), "error_count_reset"), + ]).is_err() { + return; + } + let should_retry = api + .max_retries + .map_or(true, |max_retries| error_count < max_retries); + if !should_retry { + err = Error { + message: Some("Block stopped, left click refresh icon to restart block".into()), + cause: Some(Arc::new(err)), + }; + } + if api.set_error_with_restartable(err, !should_retry).is_err() { return; } tokio::select! { - _ = tokio::time::sleep(api.error_interval) => (), + _ = tokio::time::sleep(api.error_interval), if should_retry => (), + Some(action) = actions.recv(), if !should_retry => match action.as_ref(){ + "error_count_reset" => { + error_count = 0; + }, + _ => (), + }, _ = api.wait_for_update_request() => (), } + error_count = error_count.saturating_add(1); } }.boxed_local()), )* @@ -210,6 +236,7 @@ pub struct CommonApi { pub(crate) request_sender: mpsc::UnboundedSender, pub(crate) error_interval: Duration, pub(crate) geolocator: Arc, + pub(crate) max_retries: Option, } impl CommonApi { @@ -233,12 +260,17 @@ impl CommonApi { .error("Failed to send Request") } - /// Sends the error to be displayed. + /// Sends the error to be displayed, no restart button will be shown. pub fn set_error(&self, error: Error) -> Result<()> { + self.set_error_with_restartable(error, false) + } + + /// Sends the error to be displayed. + pub fn set_error_with_restartable(&self, error: Error, restartable: bool) -> Result<()> { self.request_sender .send(Request { block_id: self.id, - cmd: RequestCmd::SetError(error), + cmd: RequestCmd::SetError { error, restartable }, }) .error("Failed to send Request") } diff --git a/src/blocks/packages.rs b/src/blocks/packages.rs index ee2d89dfe..4fedb7881 100644 --- a/src/blocks/packages.rs +++ b/src/blocks/packages.rs @@ -82,6 +82,8 @@ //! [[block]] //! block = "packages" //! interval = 1800 +//! error_interval = 300 +//! max_retries = 5 //! package_manager = ["apt"] //! format = " $icon $apt updates available" //! format_singular = " $icon One update available " @@ -103,6 +105,8 @@ //! block = "packages" //! package_manager = ["pacman"] //! interval = 600 +//! error_interval = 300 +//! max_retries = 5 //! format = " $icon $pacman updates available " //! format_singular = " $icon $pacman update available " //! format_up_to_date = " $icon system up to date " @@ -124,6 +128,7 @@ //! package_manager = ["pacman", "aur"] //! interval = 600 //! error_interval = 300 +//! max_retries = 5 //! format = " $icon $pacman + $aur = $total updates available " //! format_singular = " $icon $total update available " //! format_up_to_date = " $icon system up to date " @@ -139,6 +144,8 @@ //! block = "packages" //! package_manager = ["dnf"] //! interval = 1800 +//! error_interval = 300 +//! max_retries = 5 //! format = " $icon $dnf.eng(w:1) updates available " //! format_singular = " $icon One update available " //! format_up_to_date = " $icon system up to date " @@ -156,6 +163,8 @@ //! block = "packages" //! package_manager = ["xbps"] //! interval = 1800 +//! error_interval = 300 +//! max_retries = 5 //! format = " $icon $xbps.eng(w:1) updates available " //! format_singular = " $icon One update available " //! format_up_to_date = " $icon system up to date " @@ -174,6 +183,8 @@ //! block = "packages" //! package_manager = ["apt", "pacman", "aur", "dnf", "xbps"] //! interval = 1800 +//! error_interval = 300 +//! max_retries = 5 //! format = " $icon $apt + $pacman + $aur + $dnf + $xbps = $total updates available " //! format_singular = " $icon One update available " //! format_up_to_date = " $icon system up to date " diff --git a/src/config.rs b/src/config.rs index f6f9ba661..ad929de0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,11 +60,15 @@ impl Default for SharedConfig { } fn default_error_format() -> FormatConfig { - " {$short_error_message|X} ".parse().unwrap() + " {$restart_block_icon |}{$short_error_message|X} " + .parse() + .unwrap() } fn default_error_fullscreen() -> FormatConfig { - " $full_error_message ".parse().unwrap() + " {$restart_block_icon |}$full_error_message " + .parse() + .unwrap() } fn default_icons_format() -> Arc { @@ -108,6 +112,7 @@ pub struct CommonBlockConfig { pub error_interval: u64, pub error_format: FormatConfig, pub error_fullscreen_format: FormatConfig, + pub max_retries: Option, pub if_command: Option, } diff --git a/src/icons.rs b/src/icons.rs index c9fcd45f3..7146ddded 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -84,6 +84,7 @@ impl Default for Icons { "pomodoro_paused" => "PAUSED", "pomodoro_started" => "STARTED", "pomodoro_stopped" => "STOPPED", + "refresh" => "REFRESH", "resolution" => "RES", "scratchpad" => "[]", "tasks" => "TSK", diff --git a/src/lib.rs b/src/lib.rs index 2bf0e722c..3ecddc9c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ use futures::stream::{FuturesUnordered, StreamExt as _}; use tokio::process::Command; use tokio::sync::{Notify, mpsc}; -use crate::blocks::{BlockAction, BlockError, CommonApi}; +use crate::blocks::{BlockAction, BlockError, CommonApi, RESTART_BLOCK_BTN}; use crate::click::{ClickHandler, MouseButton}; use crate::config::{BlockConfigEntry, Config, SharedConfig}; use crate::errors::*; @@ -131,7 +131,7 @@ struct Request { enum RequestCmd { SetWidget(Widget), UnsetWidget, - SetError(Error), + SetError { error: Error, restartable: bool }, SetDefaultActions(&'static [(MouseButton, Option<&'static str>, &'static str)]), SubscribeToActions(mpsc::UnboundedSender), } @@ -186,6 +186,10 @@ impl Block { } fn set_error(&mut self, fullscreen: bool, error: Error) { + self.set_error_with_restartable(fullscreen, false, error); + } + + fn set_error_with_restartable(&mut self, fullscreen: bool, restartable: bool, error: Error) { let error = BlockError { block_id: self.id, block_name: self.name, @@ -202,6 +206,7 @@ impl Block { widget.set_values(map! { "full_error_message" => Value::text(error.to_string()), [if let Some(v) = &error.error.message] "short_error_message" => Value::text(v.to_string()), + [if restartable] "restart_block_icon" => Value::icon("refresh").with_instance(RESTART_BLOCK_BTN), }); self.state = BlockState::Error { widget }; } @@ -270,6 +275,7 @@ impl BarState { request_sender: self.request_sender.clone(), error_interval: Duration::from_secs(block_config.common.error_interval), geolocator: self.config.geolocator.clone(), + max_retries: block_config.common.max_retries, }; let error_format = block_config @@ -325,8 +331,12 @@ impl BarState { self.fullscreen_block = None; } } - RequestCmd::SetError(error) => { - block.set_error(self.fullscreen_block == Some(request.block_id), error); + RequestCmd::SetError { error, restartable } => { + block.set_error_with_restartable( + self.fullscreen_block == Some(request.block_id), + restartable, + error, + ); } RequestCmd::SetDefaultActions(actions) => { block.default_actions = actions; @@ -414,16 +424,22 @@ impl BarState { } } BlockState::Error { widget } => { - if self.fullscreen_block == Some(event.id) { - self.fullscreen_block = None; - widget.set_format(block.error_format.clone()); + if let Some((_, _, action)) = block.default_actions + .iter() + .find(|(btn, widget, _)| *btn == event.button && *widget == event.instance.as_deref()) { + block.send_action(Cow::Borrowed(action)); } else { - self.fullscreen_block = Some(event.id); - widget.set_format(block.error_fullscreen_format.clone()); + if self.fullscreen_block == Some(event.id) { + self.fullscreen_block = None; + widget.set_format(block.error_format.clone()); + } else { + self.fullscreen_block = Some(event.id); + widget.set_format(block.error_fullscreen_format.clone()); + } + block.notify_intervals(&self.widget_updates_sender); + self.render_block(event.id)?; + self.render(); } - block.notify_intervals(&self.widget_updates_sender); - self.render_block(event.id)?; - self.render(); } } }