use std::str::FromStr; use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use crate::gen_parse; use super::{command::CommandParseError, window::Window, Monitor, Tag, ToCommandString}; #[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)] #[strum(serialize_all = "snake_case")] pub enum Hook { /// The attribute `path` was changed from `old` to `new`. /// Requires that the attribute `path` has been passed to the [HlwmCommand::Watch] command before. AttributeChanged { path: String, old: String, new: String, }, /// The fullscreen state of `window` was changed to [on|off]. Fullscreen { on: bool, window: Window, }, /// The `tag` was selected on `monitor`. TagChanged { tag: String, monitor: Monitor, }, /// The `window` with title `title` was focused FocusChanged { window: Window, title: String, }, WindowTitleChanged { window: Window, title: String, }, /// The flags (i.e. urgent or filled state) have been changed. TagFlags, TagAdded(Tag), TagRenamed { old: String, new: String, }, Urgent { on: bool, window: Window, }, /// A `window` appeared which triggered a rule/hook. Rule { hook: String, window: Window, }, /// Tells a panel to quit. The default panel.sh quits on this hook. Many scripts are using this hook. QuitPanel, /// Tells all daemons that the autostart file is reloaded — and tells them to quit. /// This hook **should** be emitted in the first line of every autostart file. Reload, } impl Hook { fn from_raw_parts(command: &str, args: Vec) -> Result { let command = Self::iter() .find(|cmd| cmd.to_string() == command) .ok_or(CommandParseError::UnknownCommand(command.to_string()))?; gen_parse!(command, args); match command { Hook::AttributeChanged { path: _, old: _, new: _, } => parse!(path: String, old: String, new: String => AttributeChanged), Hook::Fullscreen { on: _, window: _ } => { parse!(on: FromStr, window: FromStr => Fullscreen) } Hook::TagChanged { tag: _, monitor: _ } => { parse!(tag: String, monitor: FromStr => TagChanged) } Hook::FocusChanged { window: _, title: _, } => parse!(window: FromStr, title: String => FocusChanged), Hook::WindowTitleChanged { window: _, title: _, } => parse!(window: FromStr, title: String => WindowTitleChanged), Hook::TagFlags => Ok(Hook::TagFlags), Hook::TagAdded(_) => parse!(FromStr => TagAdded), Hook::TagRenamed { old: _, new: _ } => parse!(old: String, new: String => TagRenamed), Hook::Urgent { on: _, window: _ } => parse!(on: Bool, window: FromStr => Urgent), Hook::Rule { hook: _, window: _ } => parse!(hook: String, window: FromStr => Rule), Hook::QuitPanel => Ok(Hook::QuitPanel), Hook::Reload => Ok(Hook::Reload), } } } impl FromStr for Hook { type Err = CommandParseError; fn from_str(s: &str) -> Result { let mut split = s.split("\t"); Hook::from_raw_parts( split .next() .ok_or(CommandParseError::UnknownCommand(format!("hook {s}")))?, split.map(String::from).collect(), ) } } impl Default for Hook { fn default() -> Self { Self::Reload } } impl ToCommandString for Hook { fn to_command_string(&self) -> String { [self.to_string()] .into_iter() .chain(match self { Hook::AttributeChanged { path, old, new } => { vec![path.to_string(), old.to_string(), new.to_string()].into_iter() } Hook::Fullscreen { on, window } => { vec![on.to_string(), window.to_string()].into_iter() } Hook::TagChanged { tag, monitor } => { vec![tag.to_string(), monitor.to_string()].into_iter() } Hook::WindowTitleChanged { window, title } | Hook::FocusChanged { window, title } => { vec![window.to_string(), title.to_string()].into_iter() } Hook::QuitPanel | Hook::Reload | Hook::TagFlags => vec![].into_iter(), Hook::TagAdded(tag) => vec![tag.to_string()].into_iter(), Hook::TagRenamed { old, new } => vec![old.to_string(), new.to_string()].into_iter(), Hook::Urgent { on, window } => vec![on.to_string(), window.to_string()].into_iter(), Hook::Rule { hook, window } => { vec![hook.to_string(), window.to_string()].into_iter() } }) .collect::>() .join("\t") } }