i3status_rs/blocks/
scratchpad.rs1use swayipc_async::{Connection, Event as SwayEvent, EventType, Node, WindowChange};
25
26use super::prelude::*;
27
28#[derive(Deserialize, Debug, SmartDefault)]
29#[serde(deny_unknown_fields, default)]
30pub struct Config {
31 pub format: FormatConfig,
32}
33
34fn count_scratchpad_windows(node: &Node) -> usize {
35 node.find_as_ref(|n| n.name.as_deref() == Some("__i3_scratch"))
36 .map_or(0, |node| node.floating_nodes.len())
37}
38
39pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
40 BlockPlan::new(vec![
41 OutputPlan::new(
42 "main",
43 config
44 .format
45 .with_default(" $icon $count.eng(range:1..) |")?,
46 )
47 .icon("icon", IconChoices::one(icons::SCRATCHPAD)),
48 ])
49}
50
51pub(crate) async fn run(_config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
52 let output_main = plan.output("main")?;
53
54 let connection_for_events = Connection::new()
55 .await
56 .error("failed to open connection with swayipc")?;
57
58 let mut connection_for_tree = Connection::new()
59 .await
60 .error("failed to open connection with swayipc")?;
61
62 let mut events = connection_for_events
63 .subscribe(&[EventType::Window])
64 .await
65 .error("could not subscribe to window events")?;
66
67 loop {
68 let mut widget = output_main.new_widget();
69
70 let root_node = connection_for_tree
71 .get_tree()
72 .await
73 .error("could not get windows tree")?;
74 let count = count_scratchpad_windows(&root_node);
75
76 widget.set_values(map! {
77 "icon" => Value::icon(icons::SCRATCHPAD),
78 "count" => Value::number(count),
79 });
80
81 api.set_widget(widget)?;
82
83 loop {
84 let event = events
85 .next()
86 .await
87 .error("swayipc channel closed")?
88 .error("bad event")?;
89
90 match event {
91 SwayEvent::Window(e) if e.change == WindowChange::Move => break,
92 _ => continue,
93 }
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn plan_declares_main_output_with_scratchpad_icon() {
104 let plan = prepare(&Config::default()).unwrap();
105 let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
106 assert_eq!(declared, ["main"]);
107 let output = plan.output("main").unwrap();
108 assert_eq!(output.single_icon("icon").unwrap(), "scratchpad");
109 }
110
111 #[test]
112 fn custom_format_is_respected() {
113 let config = Config {
114 format: " $count ".parse().unwrap(),
115 };
116 let plan = prepare(&config).unwrap();
117 let output = plan.output("main").unwrap();
118 assert!(output.format().contains_key("count"));
119 assert!(!output.format().contains_key("icon"));
120 }
121}