customize power controls in config (#185)

* customize power controls in config

* streamline config

* appearance + use hint in logs

* re-enable substitution

* Re-add proper variable subsitution and partial fields

---------

Co-authored-by: Gijs Burghoorn <g.burghoorn@gmail.com>
This commit is contained in:
bits0rcerer 2023-12-01 17:14:50 +01:00 committed by GitHub
parent 2f5b654533
commit 09003a8304
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 136 deletions

View file

@ -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"
cmd = "systemctl poweroff -l"
# Allow for the reboot option to be used
allow_reboot = true
# 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]

View file

@ -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<PowerControl>);
#[derive(Clone, Deserialize)]
#[serde(transparent)]
#[repr(transparent)]
pub struct PartialPowerControlVec(pub Vec<PartialPowerControl>);
#[derive(Clone, Debug, Deserialize)]
#[serde(transparent)]
#[repr(transparent)]
struct RoughPowerControlVec(pub Vec<RoughPowerControl>);
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::<Vec<PowerControl>>(),
);
}
}
impl RoughPowerControlVec {
pub fn into_partial(
self,
variables: &Variables,
) -> Result<PartialPowerControlVec, VariableInsertionError> {
self.0
.into_iter()
.map(|rough_elem| rough_elem.into_partial(variables))
.collect::<Result<Vec<PartialPowerControl>, 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<usize> {
self.start..self.start + Self::START_SYMBOL.len() + self.ident.len()

View file

@ -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,6 +18,18 @@ pub struct KeyMenuWidget {
switcher_config: SwitcherConfig,
}
impl PowerControl {
fn style(&self) -> Style {
let mut style = Style::default().fg(get_color(&self.hint_color));
for modifier in get_modifiers(&self.hint_modifiers) {
style = style.add_modifier(modifier);
}
style
}
}
impl KeyMenuWidget {
pub fn new(power_config: PowerControlConfig, switcher_config: SwitcherConfig) -> Self {
Self {
@ -24,25 +37,6 @@ impl KeyMenuWidget {
switcher_config,
}
}
fn shutdown_style(&self) -> Style {
let mut style = Style::default().fg(get_color(&self.power_config.shutdown_hint_color));
for modifier in get_modifiers(&self.power_config.shutdown_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);
}
style
}
fn switcher_toggle_style(&self) -> Style {
let mut style = Style::default().fg(get_color(&self.switcher_config.toggle_hint_color));
@ -57,27 +51,27 @@ impl KeyMenuWidget {
pub fn render(&self, frame: &mut Frame<impl ratatui::backend::Backend>, 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<super::ErrorStatusMessage> {
// 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();
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::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);
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::FailedShutdown);
return Some(super::ErrorStatusMessage::FailedPowerControl(
power_control.hint.clone(),
));
}
_ => {}
}
_ => {}
}
}
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);
}
_ => {}
}
}

View file

@ -12,21 +12,21 @@ pub enum ErrorStatusMessage {
NoGraphicalEnvironment,
FailedGraphicalEnvironment,
FailedDesktop,
FailedShutdown,
FailedReboot,
FailedPowerControl(String),
}
impl From<ErrorStatusMessage> for &'static str {
impl From<ErrorStatusMessage> for Box<str> {
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<InfoStatusMessage> for &'static str {
impl From<InfoStatusMessage> for Box<str> {
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<StatusMessage> for &'static str {
impl From<StatusMessage> for Box<str> {
fn from(msg: StatusMessage) -> Self {
use StatusMessage::*;
@ -85,13 +85,14 @@ impl StatusMessage {
pub fn render<B: Backend>(status: Option<Self>, frame: &mut Frame<B>, 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<str> = 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 {