i3status_rs/
click.rs

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/// Can be one of `left`, `middle`, `right`, `up`/`wheel_up`, `down`/`wheel_down`, `wheel_left`, `wheel_right`, `forward`, `back` or `double_left`.
12///
13/// Note that in order for double clicks to be registered, you have to set `double_click_delay` to a
14/// non-zero value. `200` might be a good choice. Note that enabling this functionality will
15/// make left clicks less responsive and feel a bit laggy.
16#[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    /// Which button to handle
73    button: MouseButton,
74    /// To which part of the block this entry applies
75    #[serde(default)]
76    widget: Option<SerdeRegex>,
77    /// Which command to run
78    #[serde(default)]
79    cmd: Option<String>,
80    /// Which block action to trigger
81    #[serde(default)]
82    action: Option<String>,
83    /// Whether to wait for command to exit or not (default is `false`)
84    #[serde(default)]
85    sync: bool,
86    /// Whether to update the block on click (default is `false`)
87    #[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            // ```toml
106            // button = "left"
107            // ```
108            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                    // Experimental
124                    "double_left" => DoubleLeft,
125                    other => return Err(E::custom(format!("unknown button '{other}'"))),
126                })
127            }
128
129            // ```toml
130            // button = 1
131            // ```
132            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}