i3ipc_types/
reply.rs

1//! Contains structs for deserializing messages from i3
2use serde::{Deserialize, Serialize};
3use serde_repr::{Deserialize_repr, Serialize_repr};
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8/// Generic success reply
9#[derive(Deserialize, Serialize, Eq, PartialEq, Clone, Hash, Debug)]
10pub struct Success {
11    pub success: bool,
12    pub error: Option<String>,
13}
14
15/// Workspaces reply
16pub 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    // used in sway, TODO: put behind feature flag
27    pub urgent: bool,
28    pub rect: Rect,
29    pub output: String,
30    #[cfg(feature = "sway")]
31    pub focus: Vec<usize>,
32}
33
34/// Outputs reply
35pub 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/// Tree/Node reply
47#[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/// Marks Reply
289#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
290pub struct Marks(pub Vec<String>);
291
292/// BarIds
293#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
294pub struct BarIds(pub Vec<String>);
295
296/// BarConfig Reply
297#[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/// Version reply
337#[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/// Binding Modes Reply
347#[derive(Deserialize, Serialize, Eq, PartialEq, Hash, Debug, Clone)]
348pub struct BindingModes(Vec<String>);
349
350/// Config Reply
351#[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/// Included Config Reply
358#[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/// Binding State Reply
366#[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}