niri_ipc/
state.rs

1//! Helpers for keeping track of the event stream state.
2//!
3//! 1. Create an [`EventStreamState`] using `Default::default()`, or any individual state part if
4//!    you only care about part of the state.
5//! 2. Connect to the niri socket and request an event stream.
6//! 3. Pass every [`Event`] to [`EventStreamStatePart::apply`] on your state.
7//! 4. Read the fields of the state as needed.
8
9use std::collections::hash_map::Entry;
10use std::collections::HashMap;
11
12use crate::{Event, KeyboardLayouts, Window, Workspace};
13
14/// Part of the state communicated via the event stream.
15pub trait EventStreamStatePart {
16    /// Returns a sequence of events that replicates this state from default initialization.
17    fn replicate(&self) -> Vec<Event>;
18
19    /// Applies the event to this state.
20    ///
21    /// Returns `None` after applying the event, and `Some(event)` if the event is ignored by this
22    /// part of the state.
23    fn apply(&mut self, event: Event) -> Option<Event>;
24}
25
26/// The full state communicated over the event stream.
27///
28/// Different parts of the state are not guaranteed to be consistent across every single event
29/// sent by niri. For example, you may receive the first [`Event::WindowOpenedOrChanged`] for a
30/// just-opened window *after* an [`Event::WorkspaceActiveWindowChanged`] for that window. Between
31/// these two events, the workspace active window id refers to a window that does not yet exist in
32/// the windows state part.
33#[derive(Debug, Default)]
34pub struct EventStreamState {
35    /// State of workspaces.
36    pub workspaces: WorkspacesState,
37
38    /// State of workspaces.
39    pub windows: WindowsState,
40
41    /// State of the keyboard layouts.
42    pub keyboard_layouts: KeyboardLayoutsState,
43}
44
45/// The workspaces state communicated over the event stream.
46#[derive(Debug, Default)]
47pub struct WorkspacesState {
48    /// Map from a workspace id to the workspace.
49    pub workspaces: HashMap<u64, Workspace>,
50}
51
52/// The windows state communicated over the event stream.
53#[derive(Debug, Default)]
54pub struct WindowsState {
55    /// Map from a window id to the window.
56    pub windows: HashMap<u64, Window>,
57}
58
59/// The keyboard layout state communicated over the event stream.
60#[derive(Debug, Default)]
61pub struct KeyboardLayoutsState {
62    /// Configured keyboard layouts.
63    pub keyboard_layouts: Option<KeyboardLayouts>,
64}
65
66impl EventStreamStatePart for EventStreamState {
67    fn replicate(&self) -> Vec<Event> {
68        let mut events = Vec::new();
69        events.extend(self.workspaces.replicate());
70        events.extend(self.windows.replicate());
71        events.extend(self.keyboard_layouts.replicate());
72        events
73    }
74
75    fn apply(&mut self, event: Event) -> Option<Event> {
76        let event = self.workspaces.apply(event)?;
77        let event = self.windows.apply(event)?;
78        let event = self.keyboard_layouts.apply(event)?;
79        Some(event)
80    }
81}
82
83impl EventStreamStatePart for WorkspacesState {
84    fn replicate(&self) -> Vec<Event> {
85        let workspaces = self.workspaces.values().cloned().collect();
86        vec![Event::WorkspacesChanged { workspaces }]
87    }
88
89    fn apply(&mut self, event: Event) -> Option<Event> {
90        match event {
91            Event::WorkspacesChanged { workspaces } => {
92                self.workspaces = workspaces.into_iter().map(|ws| (ws.id, ws)).collect();
93            }
94            Event::WorkspaceActivated { id, focused } => {
95                let ws = self.workspaces.get(&id);
96                let ws = ws.expect("activated workspace was missing from the map");
97                let output = ws.output.clone();
98
99                for ws in self.workspaces.values_mut() {
100                    let got_activated = ws.id == id;
101                    if ws.output == output {
102                        ws.is_active = got_activated;
103                    }
104
105                    if focused {
106                        ws.is_focused = got_activated;
107                    }
108                }
109            }
110            Event::WorkspaceActiveWindowChanged {
111                workspace_id,
112                active_window_id,
113            } => {
114                let ws = self.workspaces.get_mut(&workspace_id);
115                let ws = ws.expect("changed workspace was missing from the map");
116                ws.active_window_id = active_window_id;
117            }
118            event => return Some(event),
119        }
120        None
121    }
122}
123
124impl EventStreamStatePart for WindowsState {
125    fn replicate(&self) -> Vec<Event> {
126        let windows = self.windows.values().cloned().collect();
127        vec![Event::WindowsChanged { windows }]
128    }
129
130    fn apply(&mut self, event: Event) -> Option<Event> {
131        match event {
132            Event::WindowsChanged { windows } => {
133                self.windows = windows.into_iter().map(|win| (win.id, win)).collect();
134            }
135            Event::WindowOpenedOrChanged { window } => {
136                let (id, is_focused) = match self.windows.entry(window.id) {
137                    Entry::Occupied(mut entry) => {
138                        let entry = entry.get_mut();
139                        *entry = window;
140                        (entry.id, entry.is_focused)
141                    }
142                    Entry::Vacant(entry) => {
143                        let entry = entry.insert(window);
144                        (entry.id, entry.is_focused)
145                    }
146                };
147
148                if is_focused {
149                    for win in self.windows.values_mut() {
150                        if win.id != id {
151                            win.is_focused = false;
152                        }
153                    }
154                }
155            }
156            Event::WindowClosed { id } => {
157                let win = self.windows.remove(&id);
158                win.expect("closed window was missing from the map");
159            }
160            Event::WindowFocusChanged { id } => {
161                for win in self.windows.values_mut() {
162                    win.is_focused = Some(win.id) == id;
163                }
164            }
165            event => return Some(event),
166        }
167        None
168    }
169}
170
171impl EventStreamStatePart for KeyboardLayoutsState {
172    fn replicate(&self) -> Vec<Event> {
173        if let Some(keyboard_layouts) = self.keyboard_layouts.clone() {
174            vec![Event::KeyboardLayoutsChanged { keyboard_layouts }]
175        } else {
176            vec![]
177        }
178    }
179
180    fn apply(&mut self, event: Event) -> Option<Event> {
181        match event {
182            Event::KeyboardLayoutsChanged { keyboard_layouts } => {
183                self.keyboard_layouts = Some(keyboard_layouts);
184            }
185            Event::KeyboardLayoutSwitched { idx } => {
186                let kb = self.keyboard_layouts.as_mut();
187                let kb = kb.expect("keyboard layouts must be set before a layout can be switched");
188                kb.current_idx = idx;
189            }
190            event => return Some(event),
191        }
192        None
193    }
194}