use std::{ convert::Infallible, fmt::Display, process::{self, Stdio}, str::FromStr, }; use log::{error, trace}; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::cmd; use self::{ attribute::{Attribute, AttributeType}, command::{CommandError, HlwmCommand}, parser::{ArgParser, FromStrings, ParseError}, setting::{Setting, SettingName}, tag::TagStatus, }; mod and_or_command; 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 parser; pub mod rule; pub mod setting; pub mod tag; pub mod theme; pub mod window; 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(); trace!("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_from_str_attr, T: FromStr>( &self, attr: String, ) -> Result { Ok(self .get_str_attr(attr)? .parse() .map_err(|err: E| err.into())?) } pub fn get_attr(&self, attr: String) -> Result { let attr_type = AttributeType::get_type(&attr)?; 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 { let setting_value = String::from_utf8(self.execute(HlwmCommand::Get(setting))?.stdout)?; Ok( Setting::from_str(&format!("{setting}\t{}", setting_value)).map_err(|err| { error!("failed getting setting [{setting}]: {err}"); ParseError::InvalidValue { value: setting_value, expected: "setting value (get_setting)", } })?, ) } 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(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 = ParseError; fn from_str(s: &str) -> Result { let mut chars = s.chars(); let prefix = chars.next().ok_or(ParseError::Empty)?; match prefix { '+' => Ok(Self::Relative({ let s = chars.collect::(); N::from_str(&s).map_err(|_| ParseError::InvalidValue { value: s, expected: std::any::type_name::(), })? })), '-' => Ok(Self::Relative(N::from_str(s).map_err(|_| { ParseError::InvalidValue { value: s.to_string(), expected: std::any::type_name::(), } })?)), _ => Ok(Self::Absolute(N::from_str(s).map_err(|_| { ParseError::InvalidValue { value: s.to_string(), expected: std::any::type_name::(), } })?)), } } } 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, Copy, Serialize, Deserialize, strum::EnumIter, PartialEq)] pub enum Separator { Comma, Period, } impl FromStr for Separator { type Err = ParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|i| i.to_string() == s) .ok_or(ParseError::InvalidCommand(s.to_string())) } } 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 = ParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|i| i.to_string() == s) .ok_or(ParseError::InvalidCommand(s.to_string())) } } 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 = ParseError; fn from_str(s: &str) -> Result { Self::iter() .into_iter() .find(|dir| dir.to_string() == s) .ok_or(ParseError::InvalidCommand(s.to_string())) } } 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 FromStrings for Align { fn from_strings>(s: I) -> Result { let mut args = ArgParser::from_strings(s); let alignment = args.must_string("align(align)")?; let fraction = args.optional_next_from_str("align(fraction)")?; match alignment.as_str() { "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(ParseError::InvalidCommand(alignment)), } } } 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") } }