mirror of
https://github.com/coastalwhite/lemurs
synced 2025-02-16 14:08:25 +00:00
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:
parent
2f5b654533
commit
09003a8304
4 changed files with 186 additions and 136 deletions
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue