Files
dotfiles/mgr/src/power.rs

224 lines
5.1 KiB
Rust
Raw Normal View History

use thiserror::Error;
use tracing::{Level, event};
use super::{
Exec,
cli::{self, CliCommand},
cmd, dmenu, dunst, spotify,
wire::{WireCommand, server},
};
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Dunst(#[from] dunst::Error),
#[error("unknown action: {action}")]
UnknownAction { action: String },
#[error(transparent)]
Cmd(#[from] cmd::Error),
#[error(transparent)]
Dmenu(#[from] dmenu::Error),
#[error(transparent)]
Cli(#[from] cli::ParseError),
#[error(transparent)]
Spotify(#[from] spotify::Error),
}
#[derive(Debug, Clone, Copy)]
pub enum Action {
Menu,
Lock,
Suspend,
Hibernate,
Reboot,
Poweroff,
}
impl Action {
fn as_str(self) -> &'static str {
match self {
Self::Menu => "menu",
Self::Lock => "lock",
Self::Suspend => "suspend",
Self::Hibernate => "hibernate",
Self::Reboot => "reboot",
Self::Poweroff => "poweroff",
}
}
}
impl WireCommand for Action {
fn parse_wire(mut input: impl Iterator<Item = u8>) -> Result<Self, server::ParseError> {
match input.next().ok_or(server::ParseError::Eof)? {
0x01 => Ok(Self::Menu),
0x02 => Ok(Self::Lock),
0x03 => Ok(Self::Suspend),
0x04 => Ok(Self::Hibernate),
0x05 => Ok(Self::Reboot),
0x06 => Ok(Self::Poweroff),
byte => Err(server::ParseError::Unknown(byte)),
}
}
fn to_wire(&self) -> Vec<u8> {
match *self {
Self::Menu => vec![0x01],
Self::Lock => vec![0x02],
Self::Suspend => vec![0x03],
Self::Hibernate => vec![0x04],
Self::Reboot => vec![0x05],
Self::Poweroff => vec![0x06],
}
}
}
impl Exec for Action {
type ExecErr = Error;
fn execute(&self) -> Result<Option<String>, Self::ExecErr> {
match *self {
Self::Menu => menu()?,
Self::Lock => lock_and_screen_off()?,
Self::Suspend => lock_and_suspend()?,
Self::Hibernate => hibernate()?,
Self::Reboot => reboot()?,
Self::Poweroff => poweroff()?,
}
Ok(None)
}
}
impl CliCommand for Action {
type ExecErr = Error;
fn parse_str(input: &str, rest: impl Iterator<Item = String>) -> Result<Self, cli::ParseError>
where
Self: Sized,
{
let result = match input {
"menu" => Self::Menu,
"lock" => Self::Lock,
"suspend" => Self::Suspend,
"hibernate" => Self::Hibernate,
"reboot" => Self::Reboot,
"shutdown" => Self::Poweroff,
s => {
return Err(cli::ParseError::UnknownAction {
action: s.to_owned(),
});
}
};
let rest = rest.collect::<Vec<String>>();
if rest.is_empty() {
Ok(result)
} else {
Err(cli::ParseError::UnexpectedInput { rest })
}
}
}
const MENU_ACTIONS: &[Action] = &[
Action::Lock,
Action::Suspend,
Action::Hibernate,
Action::Reboot,
Action::Poweroff,
];
fn menu() -> Result<(), Error> {
let choice = dmenu::get_choice(
&MENU_ACTIONS
.iter()
.map(|action| action.as_str())
.collect::<Vec<&str>>(),
)?;
if let Some(choice) = choice {
MENU_ACTIONS
.iter()
.find(|action| action.as_str() == choice)
.copied()
.expect("choice must be one of the valid values")
.execute()?;
} else {
event!(Level::DEBUG, "rofi was cancelled");
}
Ok(())
}
fn screen_off() -> Result<(), Error> {
Ok(cmd::command("xset", &["dpms", "force", "off"])?)
}
fn lock() -> Result<cmd::RunningProcess, Error> {
spotify::pause()?;
let lock_handle = cmd::start_command(
"i3lock",
&[
"--nofork",
"--show-failed-attempts",
"--ignore-empty-password",
"--color",
"000000",
],
);
Ok(lock_handle)
}
fn reset_screen() -> Result<(), Error> {
Ok(cmd::command(
"systemctl",
&["--user", "restart", "dpms.service"],
)?)
}
fn lock_and_screen_off() -> Result<(), Error> {
let dunst_paused = dunst::is_paused()?;
if dunst_paused {
dunst::set_status(dunst::Status::Paused)?;
}
lock()?.with(|| -> Result<(), Error> {
screen_off()?;
Ok(())
})?;
if dunst_paused {
dunst::set_status(dunst::Status::Unpaused)?;
}
reset_screen()?;
Ok(())
}
fn suspend() -> Result<(), Error> {
Ok(cmd::command("systemctl", &["suspend"])?)
}
fn hibernate() -> Result<(), Error> {
Ok(cmd::command("systemctl", &["hibernate"])?)
}
fn reboot() -> Result<(), Error> {
Ok(cmd::command("systemctl", &["reboot"])?)
}
fn poweroff() -> Result<(), Error> {
Ok(cmd::command("systemctl", &["poweroff"])?)
}
fn lock_and_suspend() -> Result<(), Error> {
lock()?.with(|| -> Result<(), Error> {
screen_off()?;
suspend()?;
Ok(())
})?;
Ok(())
}