1use serde::{Deserialize, Serialize};
3use serde_repr::{Deserialize_repr, Serialize_repr};
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
10pub struct Success {
11 pub success: bool,
12 pub error: Option<String>,
13}
14
15pub type Workspaces = Vec<Workspace>;
17
18#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
19pub struct Workspace {
20 #[serde(default)]
21 pub id: usize,
22 pub num: i32,
23 pub name: String,
24 pub visible: bool,
25 pub focused: bool,
26 pub urgent: bool,
28 pub rect: Rect,
29 pub output: String,
30 #[cfg(feature = "sway")]
31 pub focus: Vec<usize>,
32}
33
34pub type Outputs = Vec<Output>;
36
37#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
38pub struct Output {
39 pub name: String,
40 pub active: bool,
41 pub primary: bool,
42 pub current_workspace: Option<String>,
43 pub rect: Rect,
44}
45
46#[derive(Deserialize, Serialize, Clone, Debug)]
48pub struct Node {
49 pub id: usize,
50 pub name: Option<String>,
51 pub num: Option<i32>,
52 #[serde(rename = "type")]
53 pub node_type: NodeType,
54 pub layout: NodeLayout,
55 pub output: Option<String>,
56 pub orientation: NodeOrientation,
57 pub border: NodeBorder,
58 pub scratchpad_state: ScratchpadState,
59 pub percent: Option<f64>,
60 pub rect: Rect,
61 pub window_rect: Rect,
62 pub deco_rect: Rect,
63 pub geometry: Rect,
64 pub window: Option<usize>,
65 pub window_properties: Option<WindowProperties>,
66 pub window_type: Option<WindowType>,
67 pub current_border_width: i32,
68 pub urgent: bool,
69 pub marks: Option<Marks>,
70 pub focused: bool,
71 pub focus: Vec<usize>,
72 pub sticky: bool,
73 pub floating: Option<Floating>,
74 pub floating_nodes: Vec<Node>,
75 pub fullscreen_mode: FullscreenMode,
76 pub nodes: Vec<Node>,
77 #[cfg(feature = "sway")]
78 pub app_id: Option<String>,
79}
80
81impl PartialEq for Node {
82 fn eq(&self, other: &Self) -> bool {
83 self.id == other.id
84 }
85}
86
87impl Eq for Node {}
88
89#[derive(Eq, Serialize, PartialEq, Clone, Debug)]
90pub struct WindowProperties {
91 pub title: Option<String>,
92 pub instance: Option<String>,
93 pub class: Option<String>,
94 pub window_role: Option<String>,
95 pub transient_for: Option<u64>,
96 pub machine: Option<String>,
97 #[cfg(feature = "sway")]
98 pub window_type: Option<String>,
99}
100
101impl<'de> serde::Deserialize<'de> for WindowProperties {
102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103 where
104 D: serde::Deserializer<'de>,
105 {
106 #[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Debug)]
107 struct Intermediate(HashMap<WindowProperty, Option<WindowData>>);
108
109 #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
110 #[serde(untagged)]
111 enum WindowData {
112 Str(String),
113 Num(u64),
114 }
115 impl WindowData {
116 fn unwrap_str(self) -> String {
117 match self {
118 WindowData::Str(s) => s,
119 _ => unreachable!("cant have non-string value"),
120 }
121 }
122
123 fn unwrap_num(self) -> u64 {
124 match self {
125 WindowData::Num(n) => n,
126 _ => unreachable!("cant have non-num value"),
127 }
128 }
129 }
130 let mut input = Intermediate::deserialize(deserializer)?;
131 let title = input
132 .0
133 .get_mut(&WindowProperty::Title)
134 .and_then(|x| x.take().map(|x| x.unwrap_str()));
135 let instance = input
136 .0
137 .get_mut(&WindowProperty::Instance)
138 .and_then(|x| x.take().map(|x| x.unwrap_str()));
139 let class = input
140 .0
141 .get_mut(&WindowProperty::Class)
142 .and_then(|x| x.take().map(|x| x.unwrap_str()));
143 let window_role = input
144 .0
145 .get_mut(&WindowProperty::WindowRole)
146 .and_then(|x| x.take().map(|x| x.unwrap_str()));
147 let transient_for = input
148 .0
149 .get_mut(&WindowProperty::TransientFor)
150 .and_then(|x| x.take().map(|x| x.unwrap_num()));
151 let machine = input
152 .0
153 .get_mut(&WindowProperty::Machine)
154 .and_then(|x| x.take().map(|x| x.unwrap_str()));
155 #[cfg(feature = "sway")]
156 let window_type = input
157 .0
158 .get_mut(&WindowProperty::WindowType)
159 .and_then(|x| x.take().map(|x| x.unwrap_str()));
160
161 Ok(WindowProperties {
162 title,
163 instance,
164 class,
165 window_role,
166 transient_for,
167 machine,
168 #[cfg(feature = "sway")]
169 window_type,
170 })
171 }
172}
173
174#[derive(Deserialize, Serialize, Eq, PartialEq, Copy, Clone, Hash, Debug)]
175#[serde(rename_all = "snake_case")]
176pub enum Floating {
177 AutoOff,
178 AutoOn,
179 UserOff,
180 UserOn,
181}
182
183#[derive(Deserialize_repr, Serialize_repr, Eq, PartialEq, Copy, Clone, Hash, Debug)]
184#[repr(u8)]
185pub enum FullscreenMode {
186 None = 0,
187 Output = 1,
188 Global = 2,
189}
190
191#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Copy, Hash, Debug)]
192#[serde(rename_all = "snake_case")]
193pub enum WindowProperty {
194 Title,
195 Instance,
196 Class,
197 WindowRole,
198 TransientFor,
199 Machine,
200 #[cfg(feature = "sway")]
201 WindowType,
202}
203
204#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Copy, Hash, Debug)]
205#[serde(rename_all = "snake_case")]
206pub enum WindowType {
207 Normal,
208 Dock,
209 Dialog,
210 Utility,
211 Toolbar,
212 Splash,
213 Menu,
214 DropdownMenu,
215 PopupMenu,
216 Tooltip,
217 Notification,
218 Unknown,
219}
220
221#[cfg(feature = "sway")]
222#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
223pub struct Rect {
224 pub x: isize,
225 pub y: isize,
226 pub width: isize,
227 pub height: isize,
228}
229
230#[cfg(not(feature = "sway"))]
231#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
232pub struct Rect {
233 pub x: isize,
234 pub y: isize,
235 pub width: isize,
236 pub height: isize,
237}
238
239#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug, Copy)]
240#[serde(rename_all = "snake_case")]
241pub enum NodeType {
242 Root,
243 Output,
244 Con,
245 FloatingCon,
246 Workspace,
247 Dockarea,
248}
249#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
250#[serde(rename_all = "lowercase")]
251pub enum NodeBorder {
252 Normal,
253 None,
254 Pixel,
255 #[cfg(feature = "sway")]
256 CSD,
257}
258
259#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone, Copy)]
260#[serde(rename_all = "lowercase")]
261pub enum NodeLayout {
262 SplitH,
263 SplitV,
264 Stacked,
265 Tabbed,
266 Dockarea,
267 Output,
268 #[cfg(feature = "sway")]
269 None,
270}
271
272#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone, Copy)]
273#[serde(rename_all = "lowercase")]
274pub enum NodeOrientation {
275 Horizontal,
276 Vertical,
277 None,
278}
279
280#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone, Copy)]
281#[serde(rename_all = "lowercase")]
282pub enum ScratchpadState {
283 None,
284 Fresh,
285 Changed,
286}
287
288#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
290pub struct Marks(pub Vec<String>);
291
292#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
294pub struct BarIds(pub Vec<String>);
295
296#[derive(Deserialize, Serialize, Eq, PartialEq, Debug, Clone)]
298pub struct BarConfig {
299 pub id: String,
300 pub mode: String,
301 pub position: String,
302 pub status_command: String,
303 pub font: String,
304 pub workspace_buttons: bool,
305 pub binding_mode_indicator: bool,
306 pub verbose: bool,
307 pub colors: HashMap<BarPart, String>,
308}
309
310#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
311#[serde(rename_all = "snake_case")]
312pub enum BarPart {
313 Background,
314 Statusline,
315 Separator,
316 FocusedBackground,
317 FocusedStatusline,
318 FocusedSeparator,
319 FocusedWorkspaceText,
320 FocusedWorkspaceBg,
321 FocusedWorkspaceBorder,
322 ActiveWorkspaceText,
323 ActiveWorkspaceBg,
324 ActiveWorkspaceBorder,
325 InactiveWorkspaceText,
326 InactiveWorkspaceBg,
327 InactiveWorkspaceBorder,
328 UrgentWorkspaceText,
329 UrgentWorkspaceBg,
330 UrgentWorkspaceBorder,
331 BindingModeText,
332 BindingModeBg,
333 BindingModeBorder,
334}
335
336#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
338pub struct Version {
339 pub major: usize,
340 pub minor: usize,
341 pub patch: usize,
342 pub human_readable: String,
343 pub loaded_config_file_name: String,
344}
345
346#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
348pub struct BindingModes(Vec<String>);
349
350#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
352pub struct Config {
353 pub config: String,
354 pub included_configs: Option<Vec<IncludedConfig>>,
355}
356
357#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
359pub struct IncludedConfig {
360 pub path: PathBuf,
361 pub raw_contents: String,
362 pub variable_replaced_contents: String,
363}
364
365#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
367pub struct BindingState {
368 pub name: String,
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_output() {
377 let output = "{\"name\":\"xroot-0\",\"active\":false,\"primary\":false,\"rect\":{\"x\":0,\"y\":0,\"width\":5120,\"height\":1600},\"current_workspace\":null}";
378 let o: Result<Output, serde_json::error::Error> = serde_json::from_str(output);
379 assert!(o.is_ok());
380 }
381
382 #[test]
383 fn test_success() {
384 let output = "{\"success\":true}";
385 let o: Result<Success, serde_json::error::Error> = serde_json::from_str(output);
386 assert!(o.is_ok());
387 }
388
389 #[test]
390 #[cfg_attr(feature = "sway", ignore)]
391 fn test_workspace() {
392 let output = "{\"id\":1,\"num\":2,\"name\":\"2\",\"visible\":false,\"focused\":false,\"rect\":{\"x\":2560,\"y\":29,\"width\":2560,\"height\":1571},\"output\":\"DVI-I-3\",\"urgent\":false}";
393 let o: Result<Workspace, serde_json::error::Error> = serde_json::from_str(output);
394 dbg!(&o);
395 assert!(o.is_ok());
396 }
397
398 #[test]
399 #[cfg_attr(feature = "sway", ignore)]
400 fn test_workspace_no_id() {
401 let output = "{\"num\":2,\"name\":\"2\",\"visible\":false,\"focused\":false,\"rect\":{\"x\":2560,\"y\":29,\"width\":2560,\"height\":1571},\"output\":\"DVI-I-3\",\"urgent\":false}";
402 let o: Result<Workspace, serde_json::error::Error> = serde_json::from_str(output);
403 dbg!(&o);
404 assert!(o.is_ok());
405 assert_eq!(o.unwrap().id, 0);
406 }
407
408 #[test]
409 fn test_binding_modes() {
410 let output = "[\"resize\",\"default\"]";
411 let o: Result<BindingModes, serde_json::error::Error> = serde_json::from_str(output);
412 assert!(o.is_ok());
413 }
414
415 #[test]
416 fn test_config() {
417 let output = "{\"config\": \"some config data here\"}";
418 let o: Result<Config, serde_json::error::Error> = serde_json::from_str(output);
419 assert!(o.is_ok());
420 }
421
422 #[test]
423 fn test_included_configs() {
424 let output = r#"{"config": "some config data", "included_configs": [{"path": "/some/path", "raw_contents": "some contents", "variable_replaced_contents": "some contents"}]}"#;
425 let o: Result<Config, serde_json::error::Error> = serde_json::from_str(output);
426 assert!(o.is_ok());
427 }
428
429 #[test]
430 fn test_binding_state() {
431 let output = r#"{"name": "default"}"#;
432 let o: Result<BindingState, serde_json::error::Error> = serde_json::from_str(output);
433 assert!(o.is_ok());
434 }
435
436 #[test]
437 fn test_tree() {
438 let output = include_str!("../test/tree.json");
439 let o: Result<Node, serde_json::error::Error> = serde_json::from_str(output);
440 assert!(o.is_ok());
441 }
442
443 #[test]
444 fn test_other_tree() {
445 let output = include_str!("../test/other_tree.json");
446 let o: Result<Node, serde_json::error::Error> = serde_json::from_str(output);
447 assert!(o.is_ok());
448 }
449
450 #[test]
451 fn test_last_tree() {
452 let output = include_str!("../test/last_tree.json");
453 let o: Result<Node, serde_json::error::Error> = serde_json::from_str(output);
454 assert!(o.is_ok());
455 }
456
457 #[test]
458 fn test_version() {
459 let output = include_str!("../test/version.json");
460 let o: Result<Version, serde_json::error::Error> = serde_json::from_str(output);
461 assert!(o.is_ok());
462 }
463}