1use std::fmt;
2
3use serde::Deserialize;
4use serde::de::{self, Deserializer, Visitor};
5
6use crate::errors::{ErrorContext as _, Result};
7use crate::protocol::i3bar_event::I3BarEvent;
8use crate::subprocess::{spawn_shell, spawn_shell_sync};
9use crate::wrappers::SerdeRegex;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum MouseButton {
18 Left,
19 Middle,
20 Right,
21 WheelUp,
22 WheelDown,
23 WheelLeft,
24 WheelRight,
25 Forward,
26 Back,
27 DoubleLeft,
28}
29
30#[derive(Debug, Clone)]
31pub struct PostActions {
32 pub action: Option<String>,
33 pub update: bool,
34}
35
36#[derive(Deserialize, Debug, Clone, Default)]
37pub struct ClickHandler(Vec<ClickConfigEntry>);
38
39impl ClickHandler {
40 pub async fn handle(&self, event: &I3BarEvent) -> Result<Option<PostActions>> {
41 let Some(entry) = self
42 .0
43 .iter()
44 .filter(|e| e.button == event.button)
45 .find(|e| match &e.widget {
46 None => event.instance.is_none(),
47 Some(re) => re.0.is_match(event.instance.as_deref().unwrap_or("block")),
48 })
49 else {
50 return Ok(None);
51 };
52
53 if let Some(cmd) = &entry.cmd {
54 if entry.sync {
55 spawn_shell_sync(cmd).await
56 } else {
57 spawn_shell(cmd)
58 }
59 .or_error(|| format!("'{:?}' button handler: Failed to run '{cmd}", event.button))?;
60 }
61
62 Ok(Some(PostActions {
63 action: entry.action.clone(),
64 update: entry.update,
65 }))
66 }
67}
68
69#[derive(Deserialize, Debug, Clone)]
70#[serde(deny_unknown_fields)]
71pub struct ClickConfigEntry {
72 button: MouseButton,
74 #[serde(default)]
76 widget: Option<SerdeRegex>,
77 #[serde(default)]
79 cmd: Option<String>,
80 #[serde(default)]
82 action: Option<String>,
83 #[serde(default)]
85 sync: bool,
86 #[serde(default)]
88 update: bool,
89}
90
91impl<'de> Deserialize<'de> for MouseButton {
92 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
93 where
94 D: Deserializer<'de>,
95 {
96 struct MouseButtonVisitor;
97
98 impl Visitor<'_> for MouseButtonVisitor {
99 type Value = MouseButton;
100
101 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
102 formatter.write_str("button as int or string")
103 }
104
105 fn visit_str<E>(self, name: &str) -> Result<MouseButton, E>
109 where
110 E: de::Error,
111 {
112 use MouseButton::*;
113 Ok(match name {
114 "left" => Left,
115 "middle" => Middle,
116 "right" => Right,
117 "up" | "wheel_up" => WheelUp,
118 "down" | "wheel_down" => WheelDown,
119 "wheel_left" => WheelLeft,
120 "wheel_right" => WheelRight,
121 "forward" => Forward,
122 "back" => Back,
123 "double_left" => DoubleLeft,
125 other => return Err(E::custom(format!("unknown button '{other}'"))),
126 })
127 }
128
129 fn visit_i64<E>(self, number: i64) -> Result<MouseButton, E>
133 where
134 E: de::Error,
135 {
136 use MouseButton::*;
137 Ok(match number {
138 1 => Left,
139 2 => Middle,
140 3 => Right,
141 4 => WheelUp,
142 5 => WheelDown,
143 6 => WheelLeft,
144 7 => WheelRight,
145 8 => Back,
146 9 => Forward,
147 other => return Err(E::custom(format!("unknown button '{other}'"))),
148 })
149 }
150 fn visit_u64<E>(self, number: u64) -> Result<MouseButton, E>
151 where
152 E: de::Error,
153 {
154 self.visit_i64(number as i64)
155 }
156 }
157
158 deserializer.deserialize_any(MouseButtonVisitor)
159 }
160}