i3status_rs/blocks/
menu.rs1use tokio::sync::mpsc::UnboundedReceiver;
35
36use super::{BlockAction, prelude::*};
37use crate::subprocess::spawn_shell;
38
39#[derive(Deserialize, Debug)]
40#[serde(deny_unknown_fields)]
41pub struct Config {
42 pub text: String,
43 pub items: Vec<Item>,
44}
45
46#[derive(Deserialize, Debug, Clone)]
47#[serde(deny_unknown_fields)]
48pub struct Item {
49 pub display: String,
50 pub cmd: String,
51 #[serde(default)]
52 pub confirm_msg: Option<String>,
53}
54
55struct Block<'a> {
56 actions: UnboundedReceiver<BlockAction>,
57 api: &'a CommonApi,
58 text: &'a str,
59 items: &'a [Item],
60}
61
62impl Block<'_> {
63 async fn reset(&mut self) -> Result<()> {
64 self.set_text(self.text.to_owned()).await
65 }
66
67 async fn set_text(&mut self, text: String) -> Result<()> {
68 self.api.set_widget(Widget::new().with_text(text))
69 }
70
71 async fn wait_for_click(&mut self, button: &str) -> Result<()> {
72 while self.actions.recv().await.error("channel closed")? != button {}
73 Ok(())
74 }
75
76 async fn run_menu(&mut self) -> Result<Option<Item>> {
77 let mut index = 0;
78 loop {
79 self.set_text(self.items[index].display.clone()).await?;
80 match &*self.actions.recv().await.error("channel closed")? {
81 "_up" => index += 1,
82 "_down" => index += self.items.len() + 1,
83 "_left" => return Ok(Some(self.items[index].clone())),
84 "_right" => return Ok(None),
85 _ => (),
86 }
87 index %= self.items.len();
88 }
89 }
90
91 async fn confirm(&mut self, msg: String) -> Result<bool> {
92 self.set_text(msg).await?;
93 Ok(self.actions.recv().await.as_deref() == Some("_left"))
94 }
95}
96
97pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
98 api.set_default_actions(&[
99 (MouseButton::Left, None, "_left"),
100 (MouseButton::Right, None, "_right"),
101 (MouseButton::WheelUp, None, "_up"),
102 (MouseButton::WheelDown, None, "_down"),
103 ])?;
104
105 let mut block = Block {
106 actions: api.get_actions()?,
107 api,
108 text: &config.text,
109 items: &config.items,
110 };
111
112 loop {
113 block.reset().await?;
114 block.wait_for_click("_left").await?;
115 if let Some(res) = block.run_menu().await? {
116 if let Some(msg) = res.confirm_msg {
117 if !block.confirm(msg).await? {
118 continue;
119 }
120 }
121 spawn_shell(&res.cmd).or_error(|| format!("Failed to run '{}'", res.cmd))?;
122 }
123 }
124}