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.
89use std::collections::hash_map::Entry;
10use std::collections::HashMap;
1112use crate::{Event, KeyboardLayouts, Window, Workspace};
1314/// 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.
17fn replicate(&self) -> Vec<Event>;
1819/// 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.
23fn apply(&mut self, event: Event) -> Option<Event>;
24}
2526/// 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.
36pub workspaces: WorkspacesState,
3738/// State of workspaces.
39pub windows: WindowsState,
4041/// State of the keyboard layouts.
42pub keyboard_layouts: KeyboardLayoutsState,
43}
4445/// The workspaces state communicated over the event stream.
46#[derive(Debug, Default)]
47pub struct WorkspacesState {
48/// Map from a workspace id to the workspace.
49pub workspaces: HashMap<u64, Workspace>,
50}
5152/// The windows state communicated over the event stream.
53#[derive(Debug, Default)]
54pub struct WindowsState {
55/// Map from a window id to the window.
56pub windows: HashMap<u64, Window>,
57}
5859/// The keyboard layout state communicated over the event stream.
60#[derive(Debug, Default)]
61pub struct KeyboardLayoutsState {
62/// Configured keyboard layouts.
63pub keyboard_layouts: Option<KeyboardLayouts>,
64}
6566impl EventStreamStatePart for EventStreamState {
67fn replicate(&self) -> Vec<Event> {
68let 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 }
7475fn apply(&mut self, event: Event) -> Option<Event> {
76let event = self.workspaces.apply(event)?;
77let event = self.windows.apply(event)?;
78let event = self.keyboard_layouts.apply(event)?;
79Some(event)
80 }
81}
8283impl EventStreamStatePart for WorkspacesState {
84fn replicate(&self) -> Vec<Event> {
85let workspaces = self.workspaces.values().cloned().collect();
86vec![Event::WorkspacesChanged { workspaces }]
87 }
8889fn apply(&mut self, event: Event) -> Option<Event> {
90match event {
91 Event::WorkspacesChanged { workspaces } => {
92self.workspaces = workspaces.into_iter().map(|ws| (ws.id, ws)).collect();
93 }
94 Event::WorkspaceActivated { id, focused } => {
95let ws = self.workspaces.get(&id);
96let ws = ws.expect("activated workspace was missing from the map");
97let output = ws.output.clone();
9899for ws in self.workspaces.values_mut() {
100let got_activated = ws.id == id;
101if ws.output == output {
102 ws.is_active = got_activated;
103 }
104105if focused {
106 ws.is_focused = got_activated;
107 }
108 }
109 }
110 Event::WorkspaceActiveWindowChanged {
111 workspace_id,
112 active_window_id,
113 } => {
114let ws = self.workspaces.get_mut(&workspace_id);
115let 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 }
120None
121}
122}
123124impl EventStreamStatePart for WindowsState {
125fn replicate(&self) -> Vec<Event> {
126let windows = self.windows.values().cloned().collect();
127vec![Event::WindowsChanged { windows }]
128 }
129130fn apply(&mut self, event: Event) -> Option<Event> {
131match event {
132 Event::WindowsChanged { windows } => {
133self.windows = windows.into_iter().map(|win| (win.id, win)).collect();
134 }
135 Event::WindowOpenedOrChanged { window } => {
136let (id, is_focused) = match self.windows.entry(window.id) {
137 Entry::Occupied(mut entry) => {
138let entry = entry.get_mut();
139*entry = window;
140 (entry.id, entry.is_focused)
141 }
142 Entry::Vacant(entry) => {
143let entry = entry.insert(window);
144 (entry.id, entry.is_focused)
145 }
146 };
147148if is_focused {
149for win in self.windows.values_mut() {
150if win.id != id {
151 win.is_focused = false;
152 }
153 }
154 }
155 }
156 Event::WindowClosed { id } => {
157let win = self.windows.remove(&id);
158 win.expect("closed window was missing from the map");
159 }
160 Event::WindowFocusChanged { id } => {
161for win in self.windows.values_mut() {
162 win.is_focused = Some(win.id) == id;
163 }
164 }
165 event => return Some(event),
166 }
167None
168}
169}
170171impl EventStreamStatePart for KeyboardLayoutsState {
172fn replicate(&self) -> Vec<Event> {
173if let Some(keyboard_layouts) = self.keyboard_layouts.clone() {
174vec![Event::KeyboardLayoutsChanged { keyboard_layouts }]
175 } else {
176vec![]
177 }
178 }
179180fn apply(&mut self, event: Event) -> Option<Event> {
181match event {
182 Event::KeyboardLayoutsChanged { keyboard_layouts } => {
183self.keyboard_layouts = Some(keyboard_layouts);
184 }
185 Event::KeyboardLayoutSwitched { idx } => {
186let kb = self.keyboard_layouts.as_mut();
187let 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 }
192None
193}
194}