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    /// State of the overview.
45    pub overview: OverviewState,
46
47    /// State of the config.
48    pub config: ConfigState,
49}
50
51/// The workspaces state communicated over the event stream.
52#[derive(Debug, Default)]
53pub struct WorkspacesState {
54    /// Map from a workspace id to the workspace.
55    pub workspaces: HashMap<u64, Workspace>,
56}
57
58/// The windows state communicated over the event stream.
59#[derive(Debug, Default)]
60pub struct WindowsState {
61    /// Map from a window id to the window.
62    pub windows: HashMap<u64, Window>,
63}
64
65/// The keyboard layout state communicated over the event stream.
66#[derive(Debug, Default)]
67pub struct KeyboardLayoutsState {
68    /// Configured keyboard layouts.
69    pub keyboard_layouts: Option<KeyboardLayouts>,
70}
71
72/// The overview state communicated over the event stream.
73#[derive(Debug, Default)]
74pub struct OverviewState {
75    /// Whether the overview is currently open.
76    pub is_open: bool,
77}
78
79/// The config state communicated over the event stream.
80#[derive(Debug, Default)]
81pub struct ConfigState {
82    /// Whether the last config load attempt had failed.
83    pub failed: bool,
84}
85
86impl EventStreamStatePart for EventStreamState {
87    fn replicate(&self) -> Vec<Event> {
88        let mut events = Vec::new();
89        events.extend(self.workspaces.replicate());
90        events.extend(self.windows.replicate());
91        events.extend(self.keyboard_layouts.replicate());
92        events.extend(self.overview.replicate());
93        events.extend(self.config.replicate());
94        events
95    }
96
97    fn apply(&mut self, event: Event) -> Option<Event> {
98        let event = self.workspaces.apply(event)?;
99        let event = self.windows.apply(event)?;
100        let event = self.keyboard_layouts.apply(event)?;
101        let event = self.overview.apply(event)?;
102        let event = self.config.apply(event)?;
103        Some(event)
104    }
105}
106
107impl EventStreamStatePart for WorkspacesState {
108    fn replicate(&self) -> Vec<Event> {
109        let workspaces = self.workspaces.values().cloned().collect();
110        vec![Event::WorkspacesChanged { workspaces }]
111    }
112
113    fn apply(&mut self, event: Event) -> Option<Event> {
114        match event {
115            Event::WorkspacesChanged { workspaces } => {
116                self.workspaces = workspaces.into_iter().map(|ws| (ws.id, ws)).collect();
117            }
118            Event::WorkspaceUrgencyChanged { id, urgent } => {
119                for ws in self.workspaces.values_mut() {
120                    if ws.id == id {
121                        ws.is_urgent = urgent;
122                    }
123                }
124            }
125            Event::WorkspaceActivated { id, focused } => {
126                let ws = self.workspaces.get(&id);
127                let ws = ws.expect("activated workspace was missing from the map");
128                let output = ws.output.clone();
129
130                for ws in self.workspaces.values_mut() {
131                    let got_activated = ws.id == id;
132                    if ws.output == output {
133                        ws.is_active = got_activated;
134                    }
135
136                    if focused {
137                        ws.is_focused = got_activated;
138                    }
139                }
140            }
141            Event::WorkspaceActiveWindowChanged {
142                workspace_id,
143                active_window_id,
144            } => {
145                let ws = self.workspaces.get_mut(&workspace_id);
146                let ws = ws.expect("changed workspace was missing from the map");
147                ws.active_window_id = active_window_id;
148            }
149            event => return Some(event),
150        }
151        None
152    }
153}
154
155impl EventStreamStatePart for WindowsState {
156    fn replicate(&self) -> Vec<Event> {
157        let windows = self.windows.values().cloned().collect();
158        vec![Event::WindowsChanged { windows }]
159    }
160
161    fn apply(&mut self, event: Event) -> Option<Event> {
162        match event {
163            Event::WindowsChanged { windows } => {
164                self.windows = windows.into_iter().map(|win| (win.id, win)).collect();
165            }
166            Event::WindowOpenedOrChanged { window } => {
167                let (id, is_focused) = match self.windows.entry(window.id) {
168                    Entry::Occupied(mut entry) => {
169                        let entry = entry.get_mut();
170                        *entry = window;
171                        (entry.id, entry.is_focused)
172                    }
173                    Entry::Vacant(entry) => {
174                        let entry = entry.insert(window);
175                        (entry.id, entry.is_focused)
176                    }
177                };
178
179                if is_focused {
180                    for win in self.windows.values_mut() {
181                        if win.id != id {
182                            win.is_focused = false;
183                        }
184                    }
185                }
186            }
187            Event::WindowClosed { id } => {
188                let win = self.windows.remove(&id);
189                win.expect("closed window was missing from the map");
190            }
191            Event::WindowFocusChanged { id } => {
192                for win in self.windows.values_mut() {
193                    win.is_focused = Some(win.id) == id;
194                }
195            }
196            Event::WindowFocusTimestampChanged {
197                id,
198                focus_timestamp,
199            } => {
200                for win in self.windows.values_mut() {
201                    if win.id == id {
202                        win.focus_timestamp = focus_timestamp;
203                        break;
204                    }
205                }
206            }
207            Event::WindowUrgencyChanged { id, urgent } => {
208                for win in self.windows.values_mut() {
209                    if win.id == id {
210                        win.is_urgent = urgent;
211                        break;
212                    }
213                }
214            }
215            Event::WindowLayoutsChanged { changes } => {
216                for (id, update) in changes {
217                    let win = self.windows.get_mut(&id);
218                    let win = win.expect("changed window was missing from the map");
219                    win.layout = update;
220                }
221            }
222            event => return Some(event),
223        }
224        None
225    }
226}
227
228impl EventStreamStatePart for KeyboardLayoutsState {
229    fn replicate(&self) -> Vec<Event> {
230        if let Some(keyboard_layouts) = self.keyboard_layouts.clone() {
231            vec![Event::KeyboardLayoutsChanged { keyboard_layouts }]
232        } else {
233            vec![]
234        }
235    }
236
237    fn apply(&mut self, event: Event) -> Option<Event> {
238        match event {
239            Event::KeyboardLayoutsChanged { keyboard_layouts } => {
240                self.keyboard_layouts = Some(keyboard_layouts);
241            }
242            Event::KeyboardLayoutSwitched { idx } => {
243                let kb = self.keyboard_layouts.as_mut();
244                let kb = kb.expect("keyboard layouts must be set before a layout can be switched");
245                kb.current_idx = idx;
246            }
247            event => return Some(event),
248        }
249        None
250    }
251}
252
253impl EventStreamStatePart for OverviewState {
254    fn replicate(&self) -> Vec<Event> {
255        vec![Event::OverviewOpenedOrClosed {
256            is_open: self.is_open,
257        }]
258    }
259
260    fn apply(&mut self, event: Event) -> Option<Event> {
261        match event {
262            Event::OverviewOpenedOrClosed { is_open } => {
263                self.is_open = is_open;
264            }
265            event => return Some(event),
266        }
267        None
268    }
269}
270
271impl EventStreamStatePart for ConfigState {
272    fn replicate(&self) -> Vec<Event> {
273        vec![Event::ConfigLoaded {
274            failed: self.failed,
275        }]
276    }
277
278    fn apply(&mut self, event: Event) -> Option<Event> {
279        match event {
280            Event::ConfigLoaded { failed } => {
281                self.failed = failed;
282            }
283            event => return Some(event),
284        }
285        None
286    }
287}