diff --git a/extra/config.toml b/extra/config.toml index d3096d3..bd0aa0d 100644 --- a/extra/config.toml +++ b/extra/config.toml @@ -95,39 +95,58 @@ show_border = true border_color = "white" [power_controls] -# Allow for the shutdown option to be used -allow_shutdown = true -# The text in the top-left to display how to shutdown. The text '%key%' will be -# replaced with the shutdown_key. -shutdown_hint = "Shutdown %key%" +# The margin between hints +hint_margin = 2 + +# There are no additional entries by default +entries = [] + +# Example +# Reboot to another os option +#[[power_controls.entries]] +## The text in the top-left to display how to reboot. +#hint = "Reboot to OS" +# +## The color and modifiers of the hint in the top-left corner +#hint_color = "dark gray" +#hint_modifiers = "" +# +## The key used to reboot. Possibilities are F1 to F12. +#key = "F3" +## The command that is executed when the key is pressed +#cmd = "efibootmgr -n0 && systemctl reboot -l" + + +# If you want to remove the base_entries +# base_entries = [] + +# Shutdown option +[[power_controls.base_entries]] +# The text in the top-left to display how to shutdown. +hint = "Shutdown" # The color and modifiers of the hint in the top-left corner -shutdown_hint_color = "dark gray" -shutdown_hint_modifiers = "" +hint_color = "dark gray" +hint_modifiers = "" # The key used to shutdown. Possibilities are F1 to F12. -shutdown_key = "F1" +key = "F1" # The command that is executed when the key is pressed -shutdown_cmd = "systemctl poweroff -l" - -# Allow for the reboot option to be used -allow_reboot = true +cmd = "systemctl poweroff -l" -# The text in the top-left to display how to reboot. The text '%key%' will be -# replaced with the shutdown_key. -reboot_hint = "Reboot %key%" +# Reboot option +[[power_controls.base_entries]] +# The text in the top-left to display how to reboot. +hint = "Reboot" # The color and modifiers of the hint in the top-left corner -reboot_hint_color = "dark gray" -reboot_hint_modifiers = "" +hint_color = "dark gray" +hint_modifiers = "" # The key used to reboot. Possibilities are F1 to F12. -reboot_key = "F2" +key = "F2" # The command that is executed when the key is pressed -reboot_cmd = "systemctl reboot -l" - -# The margin between the shutdown and reboot hints -hint_margin = 2 +cmd = "systemctl reboot -l" # Setting for the selector of the desktop environment you are using. [environment_switcher] diff --git a/src/config.rs b/src/config.rs index f4bca31..d6b9587 100644 --- a/src/config.rs +++ b/src/config.rs @@ -246,21 +246,42 @@ toml_config_struct! { BackgroundConfig, PartialBackgroundConfig, RoughBackground } toml_config_struct! { PowerControlConfig, PartialPowerControlConfig, RoughPowerControlConfig, - allow_shutdown => bool, - shutdown_hint => String, - shutdown_hint_color => String, - shutdown_hint_modifiers => String, - shutdown_key => String, - shutdown_cmd => String, - - allow_reboot => bool, - reboot_hint => String, - reboot_hint_color => String, - reboot_hint_modifiers => String, - reboot_key => String, - reboot_cmd => String, - hint_margin => u16, + base_entries => PowerControlVec [PartialPowerControlVec, RoughPowerControlVec], + entries => PowerControlVec [PartialPowerControlVec, RoughPowerControlVec], +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct PowerControlVec(pub Vec); +#[derive(Clone, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct PartialPowerControlVec(pub Vec); +#[derive(Clone, Debug, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +struct RoughPowerControlVec(pub Vec); + +toml_config_struct! { PowerControl, PartialPowerControl, RoughPowerControl, + hint => String, + hint_color => String, + hint_modifiers => String, + key => String, + cmd => String, +} + +impl Default for PowerControl { + fn default() -> Self { + PowerControl { + hint: "".to_string(), + hint_color: "dark gray".to_string(), + hint_modifiers: "".to_string(), + key: "".to_string(), + cmd: "true".to_string(), + } + } } toml_config_struct! { SwitcherConfig, PartialSwitcherConfig, RoughSwitcherConfig, @@ -397,8 +418,8 @@ impl<'de> Deserialize<'de> for SwitcherVisibility { impl Default for Config { fn default() -> Config { - toml::from_str(include_str!("../extra/config.toml")).unwrap_or_else(|_| { - eprintln!("Default configuration file cannot be properly parsed"); + toml::from_str(include_str!("../extra/config.toml")).unwrap_or_else(|e| { + eprintln!("Default configuration file cannot be properly parsed: {e}"); process::exit(1); }) } @@ -517,6 +538,35 @@ impl Display for VariableInsertionError { } } +impl PowerControlVec { + pub fn merge_in_partial(&mut self, partial: PartialPowerControlVec) { + *self = PowerControlVec( + partial + .0 + .into_iter() + .map(|partial_elem| { + let mut elem = PowerControl::default(); + elem.merge_in_partial(partial_elem); + elem + }) + .collect::>(), + ); + } +} + +impl RoughPowerControlVec { + pub fn into_partial( + self, + variables: &Variables, + ) -> Result { + self.0 + .into_iter() + .map(|rough_elem| rough_elem.into_partial(variables)) + .collect::, VariableInsertionError>>() + .map(PartialPowerControlVec) + } +} + impl std::error::Error for VariableInsertionError {} macro_rules! non_string_var_insert { @@ -645,7 +695,7 @@ struct Variable<'a> { } impl<'a> Variable<'a> { - const START_SYMBOL: &str = "$"; + const START_SYMBOL: &'static str = "$"; fn span(&self) -> std::ops::Range { self.start..self.start + Self::START_SYMBOL.len() + self.ident.len() diff --git a/src/ui/key_menu.rs b/src/ui/key_menu.rs index 16e8a7c..9fb5780 100644 --- a/src/ui/key_menu.rs +++ b/src/ui/key_menu.rs @@ -2,13 +2,14 @@ use std::process::{Command, Output}; use crossterm::event::KeyCode; use ratatui::layout::{Alignment, Rect}; -use ratatui::style::Style; +use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::Paragraph; use ratatui::Frame; use crate::config::{ - get_color, get_key, get_modifiers, PowerControlConfig, SwitcherConfig, SwitcherVisibility, + get_color, get_key, get_modifiers, PowerControl, PowerControlConfig, SwitcherConfig, + SwitcherVisibility, }; #[derive(Clone)] @@ -17,31 +18,24 @@ pub struct KeyMenuWidget { switcher_config: SwitcherConfig, } -impl KeyMenuWidget { - pub fn new(power_config: PowerControlConfig, switcher_config: SwitcherConfig) -> Self { - Self { - power_config, - switcher_config, - } - } - fn shutdown_style(&self) -> Style { - let mut style = Style::default().fg(get_color(&self.power_config.shutdown_hint_color)); +impl PowerControl { + fn style(&self) -> Style { + let mut style = Style::default().fg(get_color(&self.hint_color)); - for modifier in get_modifiers(&self.power_config.shutdown_hint_modifiers) { + for modifier in get_modifiers(&self.hint_modifiers) { style = style.add_modifier(modifier); } style } +} - fn reboot_style(&self) -> Style { - let mut style = Style::default().fg(get_color(&self.power_config.reboot_hint_color)); - - for modifier in get_modifiers(&self.power_config.reboot_hint_modifiers) { - style = style.add_modifier(modifier); +impl KeyMenuWidget { + pub fn new(power_config: PowerControlConfig, switcher_config: SwitcherConfig) -> Self { + Self { + power_config, + switcher_config, } - - style } fn switcher_toggle_style(&self) -> Style { @@ -57,27 +51,27 @@ impl KeyMenuWidget { pub fn render(&self, frame: &mut Frame, area: Rect) { let mut items = Vec::new(); - if self.power_config.allow_shutdown { + for power_control in self + .power_config + .base_entries + .0 + .iter() + .chain(self.power_config.entries.0.iter()) + { items.push(Span::styled( - self.power_config - .shutdown_hint - .replace("%key%", &self.power_config.shutdown_key), - self.shutdown_style(), + power_control.key.as_str(), + power_control.style().add_modifier(Modifier::UNDERLINED), + )); + items.push(Span::raw(" ")); + items.push(Span::styled( + power_control.hint.as_str(), + power_control.style(), )); // Add margin items.push(Span::raw(" ".repeat(self.power_config.hint_margin.into()))); } - if self.power_config.allow_reboot { - items.push(Span::styled( - self.power_config - .reboot_hint - .replace("%key%", &self.power_config.reboot_key), - self.reboot_style(), - )); - } - let left_widget = Paragraph::new(Line::from(items)); frame.render_widget(left_widget, area); @@ -98,55 +92,41 @@ impl KeyMenuWidget { pub(crate) fn key_press(&self, key_code: KeyCode) -> Option { // TODO: Properly handle StdIn - if self.power_config.allow_shutdown && key_code == get_key(&self.power_config.shutdown_key) + for power_control in self + .power_config + .base_entries + .0 + .iter() + .chain(self.power_config.entries.0.iter()) { - let cmd_status = Command::new("bash") - .arg("-c") - .arg(self.power_config.shutdown_cmd.clone()) - .output(); - - match cmd_status { - Err(err) => { - log::error!("Failed to execute shutdown command: {:?}", err); - return Some(super::ErrorStatusMessage::FailedShutdown); - } - Ok(Output { - status, - stdout, - stderr, - }) if !status.success() => { - log::error!("Error while executing shutdown command"); - log::error!("STDOUT:\n{:?}", stdout); - log::error!("STDERR:\n{:?}", stderr); - - return Some(super::ErrorStatusMessage::FailedShutdown); - } - _ => {} - } - } - if self.power_config.allow_reboot && key_code == get_key(&self.power_config.reboot_key) { - let cmd_status = Command::new("bash") - .arg("-c") - .arg(self.power_config.reboot_cmd.clone()) - .output(); - - match cmd_status { - Err(err) => { - log::error!("Failed to execute reboot command: {:?}", err); - return Some(super::ErrorStatusMessage::FailedReboot); - } - Ok(Output { - status, - stdout, - stderr, - }) if !status.success() => { - log::error!("Error while executing reboot command"); - log::error!("STDOUT:\n{:?}", stdout); - log::error!("STDERR:\n{:?}", stderr); - - return Some(super::ErrorStatusMessage::FailedReboot); + if key_code == get_key(&power_control.key) { + let cmd_status = Command::new("bash") + .arg("-c") + .arg(power_control.cmd.clone()) + .output(); + + match cmd_status { + Err(err) => { + log::error!("Failed to execute shutdown command: {:?}", err); + return Some(super::ErrorStatusMessage::FailedPowerControl( + power_control.hint.clone(), + )); + } + Ok(Output { + status, + stdout, + stderr, + }) if !status.success() => { + log::error!("Error while executing \"{}\"", power_control.hint); + log::error!("STDOUT:\n{:?}", stdout); + log::error!("STDERR:\n{:?}", stderr); + + return Some(super::ErrorStatusMessage::FailedPowerControl( + power_control.hint.clone(), + )); + } + _ => {} } - _ => {} } } diff --git a/src/ui/status_message.rs b/src/ui/status_message.rs index 141b5aa..43d0b65 100644 --- a/src/ui/status_message.rs +++ b/src/ui/status_message.rs @@ -12,21 +12,21 @@ pub enum ErrorStatusMessage { NoGraphicalEnvironment, FailedGraphicalEnvironment, FailedDesktop, - FailedShutdown, - FailedReboot, + FailedPowerControl(String), } -impl From for &'static str { +impl From for Box { fn from(err: ErrorStatusMessage) -> Self { use ErrorStatusMessage::*; match err { - AuthenticationError(_) => "Authentication failed", - NoGraphicalEnvironment => "No graphical environment specified", - FailedGraphicalEnvironment => "Failed booting into the graphical environment", - FailedDesktop => "Failed booting into desktop environment", - FailedShutdown => "Failed to shutdown... Check the logs for more information", - FailedReboot => "Failed to reboot... Check the logs for more information", + AuthenticationError(_) => "Authentication failed".into(), + NoGraphicalEnvironment => "No graphical environment specified".into(), + FailedGraphicalEnvironment => "Failed booting into the graphical environment".into(), + FailedDesktop => "Failed booting into desktop environment".into(), + FailedPowerControl(name) => { + format!("Failed to {name}... Check the logs for more information").into() + } } } } @@ -43,13 +43,13 @@ pub enum InfoStatusMessage { Authenticating, } -impl From for &'static str { +impl From for Box { fn from(info: InfoStatusMessage) -> Self { use InfoStatusMessage::*; match info { - LoggingIn => "Authentication successful. Logging in...", - Authenticating => "Verifying credentials", + LoggingIn => "Authentication successful. Logging in...".into(), + Authenticating => "Verifying credentials".into(), } } } @@ -66,7 +66,7 @@ pub enum StatusMessage { Info(InfoStatusMessage), } -impl From for &'static str { +impl From for Box { fn from(msg: StatusMessage) -> Self { use StatusMessage::*; @@ -85,13 +85,14 @@ impl StatusMessage { pub fn render(status: Option, frame: &mut Frame, area: Rect) { if let Some(status_message) = status { - let widget = Paragraph::new(<&'static str>::from(status_message.clone())).style( - Style::default().fg(if status_message.is_error() { + let text: Box = status_message.clone().into(); + let widget = Paragraph::new(text.as_ref()).style(Style::default().fg( + if status_message.is_error() { Color::Red } else { Color::Yellow - }), - ); + }, + )); frame.render_widget(widget, area); } else {