1use serde::{Deserialize, Serialize};
2use std::{fmt::Display, process::Command};
3use thiserror::Error;
4
5#[derive(Debug, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub enum Layout {
8 Tiled,
9 PsuedoTiled,
10 Floating,
11 Monocle,
12}
13
14impl Default for Layout {
15 fn default() -> Self {
16 Self::Tiled
17 }
18}
19
20impl Display for Layout {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 Self::Tiled => write!(f, "tiled"),
24 Self::PsuedoTiled => write!(f, "pseudo-tiled"),
25 Self::Floating => write!(f, "floating"),
26 Self::Monocle => write!(f, "monocle"),
27 }
28 }
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub enum SplitType {
34 Vertical,
35 Horizontal,
36}
37
38impl Default for SplitType {
39 fn default() -> Self {
40 Self::Vertical
41 }
42}
43
44impl Display for SplitType {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 SplitType::Vertical => write!(f, "vertical"),
48 SplitType::Horizontal => write!(f, "horizontal"),
49 }
50 }
51}
52
53#[derive(Debug, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub enum Layer {
56 Normal,
57}
58
59impl Default for Layer {
60 fn default() -> Self {
61 Self::Normal
62 }
63}
64
65impl Display for Layer {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Layer::Normal => write!(f, "normal"),
69 }
70 }
71}
72
73#[derive(Debug, Default, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct Constraints {
76 pub min_width: usize,
77 pub min_height: usize,
78}
79
80#[derive(Debug, Default, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct Padding {
83 pub top: usize,
84 pub right: usize,
85 pub bottom: usize,
86 pub left: usize,
87}
88
89#[derive(Debug, Default, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct Rectangle {
92 pub x: usize,
93 pub y: usize,
94 pub width: usize,
95 pub height: usize,
96}
97
98#[derive(Debug, Default, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Focus {
101 monitor_id: usize,
102 desktop_id: usize,
103 node_id: usize,
104}
105
106#[derive(Debug, Default, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct State {
109 pub focused_monitor_id: usize,
110 pub primary_monitor_id: usize,
111 pub clients_count: usize,
112 pub monitors: Vec<Monitor>,
113 pub focus_history: Vec<Focus>,
114 pub stacking_list: Vec<usize>,
115}
116
117#[derive(Debug, Default, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct Monitor {
120 pub name: String,
121 pub id: usize,
122 pub randr_id: usize,
123 pub wired: bool,
124 pub sticky_count: usize,
125 pub window_gap: usize,
126 pub border_width: usize,
127 pub focused_desktop_id: usize,
128 pub desktops: Vec<Desktop>,
129}
130
131#[derive(Debug, Default, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct Desktop {
134 pub name: String,
135 pub id: usize,
136 pub layout: Layout,
137 pub user_layout: Layout,
138 pub window_gap: usize,
139 pub border_width: usize,
140 pub focused_node_id: usize,
141 pub padding: Padding,
142 pub root: Option<Tree>,
143}
144
145#[derive(Debug, Default, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct Tree {
148 pub id: usize,
149 pub split_type: SplitType,
150 pub vacant: bool,
151 pub hidden: bool,
152 pub sticky: bool,
153 pub private: bool,
154 pub locked: bool,
155 pub marked: bool,
156 pub presel: Option<String>,
157 pub rectangle: Rectangle,
158}
159
160#[derive(Debug, Default, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct Node {
163 pub id: usize,
164 pub split_type: SplitType,
165 pub split_ratio: f32,
166 pub vacant: bool,
167 pub hidden: bool,
168 pub sticky: bool,
169 pub private: bool,
170 pub locked: bool,
171 pub marked: bool,
172 pub presel: Option<String>,
173 pub rectangle: Rectangle,
174 pub constrainnts: Option<Constraints>,
175 pub first_child: Option<Box<Node>>,
176 pub second_child: Option<Box<Node>>,
177 pub client: Client,
178}
179
180#[derive(Debug, Default, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct Client {
183 pub class_name: String,
184 pub instance_name: String,
185 pub border_width: usize,
186 pub state: Layout,
187 pub last_state: Layout,
188 pub layer: Layer,
189 pub last_layer: Layer,
190 pub urgent: bool,
191 pub shown: bool,
192 pub tiled_rectangle: Rectangle,
193 pub floating_rectangle: Rectangle,
194}
195
196#[derive(Debug, Error)]
197pub enum Error {
198 #[error("Failed to deserialize output from `bspc wm -d` or `bspc query <DOMAIN>`")]
199 Deserialize,
200
201 #[error(transparent)]
202 IoError(#[from] std::io::Error),
203}
204
205const PROG: &'static str = "bspc";
206const DUMP_ARGS: &'static [&'static str; 2] = &["wm", "-d"];
207const QUERY_ARGS: &'static [&'static str; 1] = &["query"];
208const QUERY_TREE_FLAG: &'static str = "-T";
209const MON_SEL_QUERY_FLAG: &'static str = "-m";
210const DESK_SEL_QUERY_FLAG: &'static str = "-d";
211const NODE_SEL_QUERY_FLAG: &'static str = "-n";
212const _MON_ID_OUT_FLAG: &'static str = "-M";
213const _DESK_ID_OUT_FLAG: &'static str = "-D";
214const _NODE_ID_OUT_FLAG: &'static str = "-N";
215
216pub fn dump_state() -> Result<State, self::Error> {
217 match serde_json::from_slice::<State>(&Command::new(PROG).args(DUMP_ARGS).output()?.stdout) {
218 Ok(state) => Ok(state),
219 Err(_) => Err(self::Error::Deserialize),
220 }
221}
222
223#[derive(Debug)]
224pub struct Query {
225 args: Vec<String>,
226}
227
228impl Query {
229 pub fn new() -> Self {
230 Self {
231 args: QUERY_ARGS.iter().map(|s| s.to_string()).collect(),
232 }
233 }
234
235 pub fn monitor(mut self, name: &str) -> Self {
236 if name.is_empty() {
237 return self;
238 }
239
240 let mut args: Vec<String> = QUERY_ARGS.iter().map(|s| s.to_string()).collect();
243 args.push(MON_SEL_QUERY_FLAG.to_string());
244 args.push(name.to_string());
245 self.args = args;
246
247 self
248 }
249
250 pub fn desktop(mut self, name: &str) -> Self {
251 if name.is_empty() {
252 return self;
253 }
254
255 let mut args: Vec<String> = QUERY_ARGS.iter().map(|s| s.to_string()).collect();
256 args.push(DESK_SEL_QUERY_FLAG.to_string());
257 args.push(name.to_string());
258 self.args = args;
259
260 self
261 }
262
263 pub fn node(mut self, id: usize) -> Self {
264 let mut args: Vec<String> = QUERY_ARGS.iter().map(|s| s.to_string()).collect();
265 args.push(NODE_SEL_QUERY_FLAG.to_string());
266 args.push(id.to_string());
267 self.args = args;
268
269 self
270 }
271
272 fn _get_monitor_ids(&mut self) -> Vec<usize> {
273 self.args.push(_MON_ID_OUT_FLAG.to_string());
274 todo!()
275 }
276
277 fn _get_desktop_ids(&mut self) -> Vec<usize> {
278 self.args.push(_DESK_ID_OUT_FLAG.to_string());
279 todo!()
280 }
281
282 fn _get_node_ids(&mut self) -> Vec<usize> {
283 self.args.push(_NODE_ID_OUT_FLAG.to_string());
284 todo!()
285 }
286
287 pub fn get_monitor_tree(&mut self) -> Result<Monitor, self::Error> {
288 if self.args.len() == 1 {
292 self.args.push(MON_SEL_QUERY_FLAG.to_string());
293 }
294
295 self.args.push(QUERY_TREE_FLAG.to_string());
296 match serde_json::from_slice::<Monitor>(
297 &Command::new(PROG).args(&self.args).output()?.stdout,
298 ) {
299 Ok(m) => Ok(m),
300 Err(_) => Err(self::Error::Deserialize),
301 }
302 }
303
304 pub fn get_desktop_tree(&mut self) -> Result<Desktop, self::Error> {
305 if self.args.len() == 1 {
306 self.args.push(DESK_SEL_QUERY_FLAG.to_string());
307 }
308
309 self.args.push(QUERY_TREE_FLAG.to_string());
310 match serde_json::from_slice::<Desktop>(
311 &Command::new(PROG).args(&self.args).output()?.stdout,
312 ) {
313 Ok(d) => Ok(d),
314 Err(_) => Err(self::Error::Deserialize),
315 }
316 }
317
318 pub fn get_node_tree(&mut self) -> Result<Node, self::Error> {
319 if self.args.len() == 1 {
320 self.args.push(NODE_SEL_QUERY_FLAG.to_string());
321 }
322
323 self.args.push(QUERY_TREE_FLAG.to_string());
324 match serde_json::from_slice::<Node>(&Command::new(PROG).args(&self.args).output()?.stdout)
325 {
326 Ok(n) => Ok(n),
327 Err(_) => Err(self::Error::Deserialize),
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use std::process::Command;
335
336 use crate::{dump_state, Query, DUMP_ARGS, PROG, QUERY_ARGS};
337
338 #[test]
339 fn dump_args() {
340 let mut args = vec![PROG];
341 for arg in DUMP_ARGS {
342 args.push(arg)
343 }
344
345 assert_eq!(args, vec![PROG, "wm", "-d"])
346 }
347
348 #[test]
349 fn query_args() {
350 let mut args = vec![PROG];
351 for arg in QUERY_ARGS {
352 args.push(arg)
353 }
354
355 assert_eq!(args, vec![PROG, "query"])
356 }
357
358 #[test]
359 fn outputs() {
360 if let Err(e) = Command::new(PROG).args(DUMP_ARGS).output() {
361 assert!(false, "{}", e)
362 }
363 }
364
365 #[test]
366 fn deserializes() {
367 if let Err(e) = dump_state() {
368 assert!(false, "{}", e)
369 }
370 }
371
372 #[test]
373 fn monitor_tree() {
374 let mut query = Query::new();
375 if let Err(e) = query.get_monitor_tree() {
376 eprintln!("{:#?}", query);
377 assert!(false, "{}", e)
378 }
379 }
380
381 #[test]
382 fn desktop_tree() {
383 let mut query = Query::new();
384 if let Err(e) = query.get_desktop_tree() {
385 eprintln!("{:#?}", query);
386 assert!(false, "{}", e)
387 }
388 }
389
390 #[test]
391 fn node_tree() {
392 let mut query = Query::new();
393 if let Err(e) = query.get_node_tree() {
394 eprintln!("{:#?}", query);
395 assert!(false, "{}", e)
396 }
397 }
398
399 #[test]
400 fn with_param() {
401 let query = Query::new().monitor("testies");
402 assert_eq!(query.args, vec!["query", "-m", "testies"])
403 }
404
405 #[test]
406 fn try_many_param() {
407 let query = Query::new().monitor("discarded").monitor("testies");
408 assert_eq!(query.args, vec!["query", "-m", "testies"])
409 }
410}