use std::{ convert::Infallible, fmt::Display, num::{ParseFloatError, ParseIntError}, process::{self, Stdio}, str::FromStr, }; use log::{debug, error}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use thiserror::Error; use crate::cmd; use self::{ attribute::{Attribute, AttributeError}, command::{CommandError, HlwmCommand}, key::KeyParseError, setting::{Setting, SettingName}, tag::TagStatus, }; pub mod attribute; pub mod color; pub mod command; mod hex; mod hlwmbool; pub mod hook; pub mod key; mod octal; pub mod pad; pub mod rule; pub mod setting; pub mod tag; pub mod theme; pub mod window; #[macro_use] mod macros; pub use hlwmbool::ToggleBool; pub type Monitor = u32; #[derive(Clone, Copy, Debug)] pub struct Client; impl Client { pub fn new() -> Self { Self } #[inline(always)] fn herbstclient() -> std::process::Command { std::process::Command::new("herbstclient") } /// Run the command and wait for it to finish. pub fn execute(&self, command: HlwmCommand) -> Result { let args = command.args(); debug!("running command: [{}]", (&args).join(" "),); let output = Self::herbstclient() .arg("--no-newline") .args(args) .stderr(Stdio::null()) .stdin(Stdio::null()) .stdout(Stdio::piped()) .spawn()? .wait_with_output()?; cmd::check_status(&output)?; Ok(output) } pub fn execute_iter(&self, commands: I) -> Result<(), (HlwmCommand, CommandError)> where I: IntoIterator, { for cmd in commands.into_iter() { if let Err(err) = self.execute(cmd.clone()) { return Err((cmd, err)); } } Ok(()) } pub fn get_str_attr(&self, attr: String) -> Result { self.query(HlwmCommand::GetAttr(attr))? .first() .cloned() .ok_or(CommandError::Empty) } pub fn get_attr(&self, attr: String) -> Result { let attr_type = self .query(HlwmCommand::AttrType(attr.clone()))? .first() .cloned() .ok_or(CommandError::Empty)?; let attr_val = self .query(HlwmCommand::GetAttr(attr))? .first() .cloned() .ok_or(CommandError::Empty)?; Ok(Attribute::new(&attr_type, &attr_val)?) } pub fn get_setting(&self, setting: SettingName) -> Result { Ok(Setting::from_str(&format!( "{setting}\t{}", String::from_utf8(self.execute(HlwmCommand::Get(setting))?.stdout,)? )) .map_err(|err| { error!("failed getting setting [{setting}]: {err}"); StringParseError::UnknownValue })?) } pub fn query(&self, command: HlwmCommand) -> Result, CommandError> { let lines = String::from_utf8(self.execute(command)?.stdout)? .split("\n") .map(|l| l.trim()) .filter(|l| !l.is_empty()) .map(|l| l.to_owned()) .collect::>(); if lines.is_empty() { return Err(CommandError::Empty); } Ok(lines) } #[allow(unused)] pub fn tag_status(&self) -> Result, CommandError> { Ok(self .query(HlwmCommand::TagStatus { monitor: None })? .first() .ok_or(CommandError::Empty)? .split("\t") .filter(|f| !f.is_empty()) .map(|i| i.parse()) .collect::, _>>()?) } } pub trait ToCommandString { fn to_command_string(&self) -> String; } #[derive(Debug, Clone, Error)] pub enum StringParseError { #[error("unknown value")] UnknownValue, #[error("failed parsing float: [{0}]")] FloatError(#[from] ParseFloatError), #[error("invalid bool value: [{0}]")] BoolError(String), #[error("failed parsing int: [{0}]")] IntError(#[from] ParseIntError), #[error("command parse error")] CommandParseError(String), #[error("invalid length for part [{1}]: [{0}]")] InvalidLength(usize, &'static str), #[error("required arguments missing: [{0}]")] RequiredArgMissing(String), #[error("attribute error: [{0}]")] AttributeError(#[from] AttributeError), #[error("key parse error: [{0}]")] KeyParseError(#[from] KeyParseError), } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub enum TagSelect { Index(i32), Name(String), } impl FromStr for TagSelect { type Err = Infallible; fn from_str(s: &str) -> Result { if let Ok(val) = i32::from_str(s) { Ok(Self::Index(val)) } else { Ok(Self::Name(s.to_string())) } } } impl Default for TagSelect { fn default() -> Self { Self::Index(Default::default()) } } impl std::fmt::Display for TagSelect { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TagSelect::Index(i) => write!(f, "{i}"), TagSelect::Name(name) => f.write_str(name), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Index where N: PartialEq, { Relative(N), Absolute(N), } impl FromStr for Index where N: PartialEq + FromStr, { type Err = StringParseError; fn from_str(s: &str) -> Result { let mut chars = s.chars(); let prefix = chars.next().ok_or(StringParseError::UnknownValue)?; match prefix { '+' => Ok(Self::Relative( N::from_str(&chars.collect::()) .map_err(|_| StringParseError::UnknownValue)?, )), '-' => Ok(Self::Relative( N::from_str(s).map_err(|_| StringParseError::UnknownValue)?, )), _ => Ok(Self::Absolute( N::from_str(s).map_err(|_| StringParseError::UnknownValue)?, )), } } } impl Default for Index where N: PartialEq + Default, { fn default() -> Self { Self::Absolute(Default::default()) } } impl Display for Index where N: Display + PartialOrd + Default + PartialEq, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Index::Relative(rel) => { if *rel > N::default() { write!(f, "+{rel}") } else { write!(f, "{rel}") } } Index::Absolute(abs) => write!(f, "{abs}"), } } } #[derive(Debug, Clone, Serialize, Deserialize, strum::EnumIter, PartialEq)] pub enum Separator { Comma, Period, } impl FromStr for Separator { type Err = StringParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|i| i.to_string() == s) .ok_or(StringParseError::UnknownValue) } } impl Default for Separator { fn default() -> Self { Self::Comma } } impl Display for Separator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Separator::Comma => f.write_str(","), Separator::Period => f.write_str("."), } } } #[derive(Debug, Clone, Serialize, Deserialize, strum::EnumIter, PartialEq)] pub enum Operator { /// = Equal, /// != NotEqual, /// <= LessThanEqual, /// < LessThan, /// \>= GreaterThanEqual, /// \> GreaterThan, } impl FromStr for Operator { type Err = StringParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|i| i.to_string() == s) .ok_or(StringParseError::UnknownValue) } } impl Default for Operator { fn default() -> Self { Self::Equal } } impl Display for Operator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Operator::Equal => f.write_str("="), Operator::NotEqual => f.write_str("!="), Operator::LessThanEqual => f.write_str("<="), Operator::LessThan => f.write_str("<"), Operator::GreaterThanEqual => f.write_str(">="), Operator::GreaterThan => f.write_str(">"), } } } #[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)] #[strum(serialize_all = "snake_case")] pub enum Direction { Up, Down, Left, Right, } impl FromStr for Direction { type Err = StringParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|dir| dir.to_string() == s) .ok_or(StringParseError::UnknownValue) } } impl Default for Direction { fn default() -> Self { Self::Up } } #[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)] #[strum(serialize_all = "snake_case")] pub enum Align { Top(Option), Left(Option), Right(Option), Bottom(Option), Explode, Auto, } impl FromStr for Align { type Err = StringParseError; fn from_str(s: &str) -> Result { let mut parts = s.split("\t"); let align = parts.next().ok_or(StringParseError::UnknownValue)?; let fraction = match parts.next().map(|f| f64::from_str(f)) { Some(val) => Some(val?), None => None, }; match align { "bottom" | "vertical" | "vert" | "v" => Ok(Self::Bottom(fraction)), "left" => Ok(Self::Left(fraction)), "right" | "horizontal" | "horiz" | "h" => Ok(Self::Right(fraction)), "top" => Ok(Self::Top(fraction)), "explode" => Ok(Self::Explode), "auto" => Ok(Self::Auto), _ => Err(StringParseError::UnknownValue), } } } impl Default for Align { fn default() -> Self { Self::Top(None) } } impl ToCommandString for Align { fn to_command_string(&self) -> String { match self { Align::Top(fraction) | Align::Left(fraction) | Align::Right(fraction) | Align::Bottom(fraction) => match fraction { Some(fraction) => vec![self.to_string(), fraction.to_string()], None => vec![self.to_string()], }, Align::Explode | Align::Auto => vec![self.to_string()], } .join("\t") } }