niri_ipc/lib.rs
1//! Types for communicating with niri via IPC.
2//!
3//! After connecting to the niri socket, you can send [`Request`]s. Niri will process them one by
4//! one, in order, and to each request it will respond with a single [`Reply`], which is a `Result`
5//! wrapping a [`Response`].
6//!
7//! If you send a [`Request::EventStream`], niri will *stop* reading subsequent [`Request`]s, and
8//! will start continuously writing compositor [`Event`]s to the socket. If you'd like to read an
9//! event stream and write more requests at the same time, you need to use two IPC sockets.
10//!
11//! <div class="warning">
12//!
13//! Requests are *always* processed separately. Time passes between requests, even when sending
14//! multiple requests to the socket at once. For example, sending [`Request::Workspaces`] and
15//! [`Request::Windows`] together may not return consistent results (e.g. a window may open on a
16//! new workspace in-between the two responses). This goes for actions too: sending
17//! [`Action::FocusWindow`] and <code>[Action::CloseWindow] { id: None }</code> together may close
18//! the wrong window because a different window got focused in-between these requests.
19//!
20//! </div>
21//!
22//! You can use the [`socket::Socket`] helper if you're fine with blocking communication. However,
23//! it is a fairly simple helper, so if you need async, or if you're using a different language,
24//! you are encouraged to communicate with the socket manually.
25//!
26//! 1. Read the socket filesystem path from [`socket::SOCKET_PATH_ENV`] (`$NIRI_SOCKET`).
27//! 2. Connect to the socket and write a JSON-formatted [`Request`] on a single line. You can follow
28//! up with a line break and a flush, or just flush and shutdown the write end of the socket.
29//! 3. Niri will respond with a single line JSON-formatted [`Reply`].
30//! 4. You can keep writing [`Request`]s, each on a single line, and read [`Reply`]s, also each on a
31//! separate line.
32//! 5. After you request an event stream, niri will keep responding with JSON-formatted [`Event`]s,
33//! on a single line each.
34//!
35//! ## Backwards compatibility
36//!
37//! This crate follows the niri version. It is **not** API-stable in terms of the Rust semver. In
38//! particular, expect new struct fields and enum variants to be added in patch version bumps.
39//!
40//! Use an exact version requirement to avoid breaking changes:
41//!
42//! ```toml
43//! [dependencies]
44//! niri-ipc = "=25.8.0"
45//! ```
46//!
47//! ## Features
48//!
49//! This crate defines the following features:
50//! - `json-schema`: derives the [schemars](https://lib.rs/crates/schemars) `JsonSchema` trait for
51//! the types.
52//! - `clap`: derives the clap CLI parsing traits for some types. Used internally by niri itself.
53#![warn(missing_docs)]
54
55use std::collections::HashMap;
56use std::str::FromStr;
57
58use serde::{Deserialize, Serialize};
59
60pub mod socket;
61pub mod state;
62
63/// Request from client to niri.
64#[derive(Debug, Serialize, Deserialize, Clone)]
65#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
66pub enum Request {
67 /// Request the version string for the running niri instance.
68 Version,
69 /// Request information about connected outputs.
70 Outputs,
71 /// Request information about workspaces.
72 Workspaces,
73 /// Request information about open windows.
74 Windows,
75 /// Request information about layer-shell surfaces.
76 Layers,
77 /// Request information about the configured keyboard layouts.
78 KeyboardLayouts,
79 /// Request information about the focused output.
80 FocusedOutput,
81 /// Request information about the focused window.
82 FocusedWindow,
83 /// Request picking a window and get its information.
84 PickWindow,
85 /// Request picking a color from the screen.
86 PickColor,
87 /// Perform an action.
88 Action(Action),
89 /// Change output configuration temporarily.
90 ///
91 /// The configuration is changed temporarily and not saved into the config file. If the output
92 /// configuration subsequently changes in the config file, these temporary changes will be
93 /// forgotten.
94 Output {
95 /// Output name.
96 output: String,
97 /// Configuration to apply.
98 action: OutputAction,
99 },
100 /// Start continuously receiving events from the compositor.
101 ///
102 /// The compositor should reply with `Reply::Ok(Response::Handled)`, then continuously send
103 /// [`Event`]s, one per line.
104 ///
105 /// The event stream will always give you the full current state up-front. For example, the
106 /// first workspace-related event you will receive will be [`Event::WorkspacesChanged`]
107 /// containing the full current workspaces state. You *do not* need to separately send
108 /// [`Request::Workspaces`] when using the event stream.
109 ///
110 /// Where reasonable, event stream state updates are atomic, though this is not always the
111 /// case. For example, a window may end up with a workspace id for a workspace that had already
112 /// been removed. This can happen if the corresponding [`Event::WorkspacesChanged`] arrives
113 /// before the corresponding [`Event::WindowOpenedOrChanged`].
114 EventStream,
115 /// Respond with an error (for testing error handling).
116 ReturnError,
117 /// Request information about the overview.
118 OverviewState,
119}
120
121/// Reply from niri to client.
122///
123/// Every request gets one reply.
124///
125/// * If an error had occurred, it will be an `Reply::Err`.
126/// * If the request does not need any particular response, it will be
127/// `Reply::Ok(Response::Handled)`. Kind of like an `Ok(())`.
128/// * Otherwise, it will be `Reply::Ok(response)` with one of the other [`Response`] variants.
129pub type Reply = Result<Response, String>;
130
131/// Successful response from niri to client.
132#[derive(Debug, Serialize, Deserialize, Clone)]
133#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
134pub enum Response {
135 /// A request that does not need a response was handled successfully.
136 Handled,
137 /// The version string for the running niri instance.
138 Version(String),
139 /// Information about connected outputs.
140 ///
141 /// Map from output name to output info.
142 Outputs(HashMap<String, Output>),
143 /// Information about workspaces.
144 Workspaces(Vec<Workspace>),
145 /// Information about open windows.
146 Windows(Vec<Window>),
147 /// Information about layer-shell surfaces.
148 Layers(Vec<LayerSurface>),
149 /// Information about the keyboard layout.
150 KeyboardLayouts(KeyboardLayouts),
151 /// Information about the focused output.
152 FocusedOutput(Option<Output>),
153 /// Information about the focused window.
154 FocusedWindow(Option<Window>),
155 /// Information about the picked window.
156 PickedWindow(Option<Window>),
157 /// Information about the picked color.
158 PickedColor(Option<PickedColor>),
159 /// Output configuration change result.
160 OutputConfigChanged(OutputConfigChanged),
161 /// Information about the overview.
162 OverviewState(Overview),
163}
164
165/// Overview information.
166#[derive(Serialize, Deserialize, Debug, Clone)]
167#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
168pub struct Overview {
169 /// Whether the overview is currently open.
170 pub is_open: bool,
171}
172
173/// Color picked from the screen.
174#[derive(Serialize, Deserialize, Debug, Clone)]
175#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
176pub struct PickedColor {
177 /// Color values as red, green, blue, each ranging from 0.0 to 1.0.
178 pub rgb: [f64; 3],
179}
180
181/// Actions that niri can perform.
182// Variants in this enum should match the spelling of the ones in niri-config. Most, but not all,
183// variants from niri-config should be present here.
184#[derive(Serialize, Deserialize, Debug, Clone)]
185#[cfg_attr(feature = "clap", derive(clap::Parser))]
186#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
187#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
188#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
189pub enum Action {
190 /// Exit niri.
191 Quit {
192 /// Skip the "Press Enter to confirm" prompt.
193 #[cfg_attr(feature = "clap", arg(short, long))]
194 skip_confirmation: bool,
195 },
196 /// Power off all monitors via DPMS.
197 PowerOffMonitors {},
198 /// Power on all monitors via DPMS.
199 PowerOnMonitors {},
200 /// Spawn a command.
201 Spawn {
202 /// Command to spawn.
203 #[cfg_attr(feature = "clap", arg(last = true, required = true))]
204 command: Vec<String>,
205 },
206 /// Spawn a command through the shell.
207 SpawnSh {
208 /// Command to run.
209 #[cfg_attr(feature = "clap", arg(last = true, required = true))]
210 command: String,
211 },
212 /// Do a screen transition.
213 DoScreenTransition {
214 /// Delay in milliseconds for the screen to freeze before starting the transition.
215 #[cfg_attr(feature = "clap", arg(short, long))]
216 delay_ms: Option<u16>,
217 },
218 /// Open the screenshot UI.
219 Screenshot {
220 /// Whether to show the mouse pointer by default in the screenshot UI.
221 #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
222 show_pointer: bool,
223 },
224 /// Screenshot the focused screen.
225 ScreenshotScreen {
226 /// Write the screenshot to disk in addition to putting it in your clipboard.
227 ///
228 /// The screenshot is saved according to the `screenshot-path` config setting.
229 #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
230 write_to_disk: bool,
231
232 /// Whether to include the mouse pointer in the screenshot.
233 #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
234 show_pointer: bool,
235 },
236 /// Screenshot a window.
237 #[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
238 ScreenshotWindow {
239 /// Id of the window to screenshot.
240 ///
241 /// If `None`, uses the focused window.
242 #[cfg_attr(feature = "clap", arg(long))]
243 id: Option<u64>,
244 /// Write the screenshot to disk in addition to putting it in your clipboard.
245 ///
246 /// The screenshot is saved according to the `screenshot-path` config setting.
247 #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
248 write_to_disk: bool,
249 },
250 /// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface.
251 ToggleKeyboardShortcutsInhibit {},
252 /// Close a window.
253 #[cfg_attr(feature = "clap", clap(about = "Close the focused window"))]
254 CloseWindow {
255 /// Id of the window to close.
256 ///
257 /// If `None`, uses the focused window.
258 #[cfg_attr(feature = "clap", arg(long))]
259 id: Option<u64>,
260 },
261 /// Toggle fullscreen on a window.
262 #[cfg_attr(
263 feature = "clap",
264 clap(about = "Toggle fullscreen on the focused window")
265 )]
266 FullscreenWindow {
267 /// Id of the window to toggle fullscreen of.
268 ///
269 /// If `None`, uses the focused window.
270 #[cfg_attr(feature = "clap", arg(long))]
271 id: Option<u64>,
272 },
273 /// Toggle windowed (fake) fullscreen on a window.
274 #[cfg_attr(
275 feature = "clap",
276 clap(about = "Toggle windowed (fake) fullscreen on the focused window")
277 )]
278 ToggleWindowedFullscreen {
279 /// Id of the window to toggle windowed fullscreen of.
280 ///
281 /// If `None`, uses the focused window.
282 #[cfg_attr(feature = "clap", arg(long))]
283 id: Option<u64>,
284 },
285 /// Focus a window by id.
286 FocusWindow {
287 /// Id of the window to focus.
288 #[cfg_attr(feature = "clap", arg(long))]
289 id: u64,
290 },
291 /// Focus a window in the focused column by index.
292 FocusWindowInColumn {
293 /// Index of the window in the column.
294 ///
295 /// The index starts from 1 for the topmost window.
296 #[cfg_attr(feature = "clap", arg())]
297 index: u8,
298 },
299 /// Focus the previously focused window.
300 FocusWindowPrevious {},
301 /// Focus the column to the left.
302 FocusColumnLeft {},
303 /// Focus the column to the right.
304 FocusColumnRight {},
305 /// Focus the first column.
306 FocusColumnFirst {},
307 /// Focus the last column.
308 FocusColumnLast {},
309 /// Focus the next column to the right, looping if at end.
310 FocusColumnRightOrFirst {},
311 /// Focus the next column to the left, looping if at start.
312 FocusColumnLeftOrLast {},
313 /// Focus a column by index.
314 FocusColumn {
315 /// Index of the column to focus.
316 ///
317 /// The index starts from 1 for the first column.
318 #[cfg_attr(feature = "clap", arg())]
319 index: usize,
320 },
321 /// Focus the window or the monitor above.
322 FocusWindowOrMonitorUp {},
323 /// Focus the window or the monitor below.
324 FocusWindowOrMonitorDown {},
325 /// Focus the column or the monitor to the left.
326 FocusColumnOrMonitorLeft {},
327 /// Focus the column or the monitor to the right.
328 FocusColumnOrMonitorRight {},
329 /// Focus the window below.
330 FocusWindowDown {},
331 /// Focus the window above.
332 FocusWindowUp {},
333 /// Focus the window below or the column to the left.
334 FocusWindowDownOrColumnLeft {},
335 /// Focus the window below or the column to the right.
336 FocusWindowDownOrColumnRight {},
337 /// Focus the window above or the column to the left.
338 FocusWindowUpOrColumnLeft {},
339 /// Focus the window above or the column to the right.
340 FocusWindowUpOrColumnRight {},
341 /// Focus the window or the workspace below.
342 FocusWindowOrWorkspaceDown {},
343 /// Focus the window or the workspace above.
344 FocusWindowOrWorkspaceUp {},
345 /// Focus the topmost window.
346 FocusWindowTop {},
347 /// Focus the bottommost window.
348 FocusWindowBottom {},
349 /// Focus the window below or the topmost window.
350 FocusWindowDownOrTop {},
351 /// Focus the window above or the bottommost window.
352 FocusWindowUpOrBottom {},
353 /// Move the focused column to the left.
354 MoveColumnLeft {},
355 /// Move the focused column to the right.
356 MoveColumnRight {},
357 /// Move the focused column to the start of the workspace.
358 MoveColumnToFirst {},
359 /// Move the focused column to the end of the workspace.
360 MoveColumnToLast {},
361 /// Move the focused column to the left or to the monitor to the left.
362 MoveColumnLeftOrToMonitorLeft {},
363 /// Move the focused column to the right or to the monitor to the right.
364 MoveColumnRightOrToMonitorRight {},
365 /// Move the focused column to a specific index on its workspace.
366 MoveColumnToIndex {
367 /// New index for the column.
368 ///
369 /// The index starts from 1 for the first column.
370 #[cfg_attr(feature = "clap", arg())]
371 index: usize,
372 },
373 /// Move the focused window down in a column.
374 MoveWindowDown {},
375 /// Move the focused window up in a column.
376 MoveWindowUp {},
377 /// Move the focused window down in a column or to the workspace below.
378 MoveWindowDownOrToWorkspaceDown {},
379 /// Move the focused window up in a column or to the workspace above.
380 MoveWindowUpOrToWorkspaceUp {},
381 /// Consume or expel a window left.
382 #[cfg_attr(
383 feature = "clap",
384 clap(about = "Consume or expel the focused window left")
385 )]
386 ConsumeOrExpelWindowLeft {
387 /// Id of the window to consume or expel.
388 ///
389 /// If `None`, uses the focused window.
390 #[cfg_attr(feature = "clap", arg(long))]
391 id: Option<u64>,
392 },
393 /// Consume or expel a window right.
394 #[cfg_attr(
395 feature = "clap",
396 clap(about = "Consume or expel the focused window right")
397 )]
398 ConsumeOrExpelWindowRight {
399 /// Id of the window to consume or expel.
400 ///
401 /// If `None`, uses the focused window.
402 #[cfg_attr(feature = "clap", arg(long))]
403 id: Option<u64>,
404 },
405 /// Consume the window to the right into the focused column.
406 ConsumeWindowIntoColumn {},
407 /// Expel the focused window from the column.
408 ExpelWindowFromColumn {},
409 /// Swap focused window with one to the right.
410 SwapWindowRight {},
411 /// Swap focused window with one to the left.
412 SwapWindowLeft {},
413 /// Toggle the focused column between normal and tabbed display.
414 ToggleColumnTabbedDisplay {},
415 /// Set the display mode of the focused column.
416 SetColumnDisplay {
417 /// Display mode to set.
418 #[cfg_attr(feature = "clap", arg())]
419 display: ColumnDisplay,
420 },
421 /// Center the focused column on the screen.
422 CenterColumn {},
423 /// Center a window on the screen.
424 #[cfg_attr(
425 feature = "clap",
426 clap(about = "Center the focused window on the screen")
427 )]
428 CenterWindow {
429 /// Id of the window to center.
430 ///
431 /// If `None`, uses the focused window.
432 #[cfg_attr(feature = "clap", arg(long))]
433 id: Option<u64>,
434 },
435 /// Center all fully visible columns on the screen.
436 CenterVisibleColumns {},
437 /// Focus the workspace below.
438 FocusWorkspaceDown {},
439 /// Focus the workspace above.
440 FocusWorkspaceUp {},
441 /// Focus a workspace by reference (index or name).
442 FocusWorkspace {
443 /// Reference (index or name) of the workspace to focus.
444 #[cfg_attr(feature = "clap", arg())]
445 reference: WorkspaceReferenceArg,
446 },
447 /// Focus the previous workspace.
448 FocusWorkspacePrevious {},
449 /// Move the focused window to the workspace below.
450 MoveWindowToWorkspaceDown {
451 /// Whether the focus should follow the target workspace.
452 ///
453 /// If `true` (the default), the focus will follow the window to the new workspace. If
454 /// `false`, the focus will remain on the original workspace.
455 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
456 focus: bool,
457 },
458 /// Move the focused window to the workspace above.
459 MoveWindowToWorkspaceUp {
460 /// Whether the focus should follow the target workspace.
461 ///
462 /// If `true` (the default), the focus will follow the window to the new workspace. If
463 /// `false`, the focus will remain on the original workspace.
464 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
465 focus: bool,
466 },
467 /// Move a window to a workspace.
468 #[cfg_attr(
469 feature = "clap",
470 clap(about = "Move the focused window to a workspace by reference (index or name)")
471 )]
472 MoveWindowToWorkspace {
473 /// Id of the window to move.
474 ///
475 /// If `None`, uses the focused window.
476 #[cfg_attr(feature = "clap", arg(long))]
477 window_id: Option<u64>,
478
479 /// Reference (index or name) of the workspace to move the window to.
480 #[cfg_attr(feature = "clap", arg())]
481 reference: WorkspaceReferenceArg,
482
483 /// Whether the focus should follow the moved window.
484 ///
485 /// If `true` (the default) and the window to move is focused, the focus will follow the
486 /// window to the new workspace. If `false`, the focus will remain on the original
487 /// workspace.
488 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
489 focus: bool,
490 },
491 /// Move the focused column to the workspace below.
492 MoveColumnToWorkspaceDown {
493 /// Whether the focus should follow the target workspace.
494 ///
495 /// If `true` (the default), the focus will follow the column to the new workspace. If
496 /// `false`, the focus will remain on the original workspace.
497 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
498 focus: bool,
499 },
500 /// Move the focused column to the workspace above.
501 MoveColumnToWorkspaceUp {
502 /// Whether the focus should follow the target workspace.
503 ///
504 /// If `true` (the default), the focus will follow the column to the new workspace. If
505 /// `false`, the focus will remain on the original workspace.
506 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
507 focus: bool,
508 },
509 /// Move the focused column to a workspace by reference (index or name).
510 MoveColumnToWorkspace {
511 /// Reference (index or name) of the workspace to move the column to.
512 #[cfg_attr(feature = "clap", arg())]
513 reference: WorkspaceReferenceArg,
514
515 /// Whether the focus should follow the target workspace.
516 ///
517 /// If `true` (the default), the focus will follow the column to the new workspace. If
518 /// `false`, the focus will remain on the original workspace.
519 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
520 focus: bool,
521 },
522 /// Move the focused workspace down.
523 MoveWorkspaceDown {},
524 /// Move the focused workspace up.
525 MoveWorkspaceUp {},
526 /// Move a workspace to a specific index on its monitor.
527 #[cfg_attr(
528 feature = "clap",
529 clap(about = "Move the focused workspace to a specific index on its monitor")
530 )]
531 MoveWorkspaceToIndex {
532 /// New index for the workspace.
533 #[cfg_attr(feature = "clap", arg())]
534 index: usize,
535
536 /// Reference (index or name) of the workspace to move.
537 ///
538 /// If `None`, uses the focused workspace.
539 #[cfg_attr(feature = "clap", arg(long))]
540 reference: Option<WorkspaceReferenceArg>,
541 },
542 /// Set the name of a workspace.
543 #[cfg_attr(
544 feature = "clap",
545 clap(about = "Set the name of the focused workspace")
546 )]
547 SetWorkspaceName {
548 /// New name for the workspace.
549 #[cfg_attr(feature = "clap", arg())]
550 name: String,
551
552 /// Reference (index or name) of the workspace to name.
553 ///
554 /// If `None`, uses the focused workspace.
555 #[cfg_attr(feature = "clap", arg(long))]
556 workspace: Option<WorkspaceReferenceArg>,
557 },
558 /// Unset the name of a workspace.
559 #[cfg_attr(
560 feature = "clap",
561 clap(about = "Unset the name of the focused workspace")
562 )]
563 UnsetWorkspaceName {
564 /// Reference (index or name) of the workspace to unname.
565 ///
566 /// If `None`, uses the focused workspace.
567 #[cfg_attr(feature = "clap", arg())]
568 reference: Option<WorkspaceReferenceArg>,
569 },
570 /// Focus the monitor to the left.
571 FocusMonitorLeft {},
572 /// Focus the monitor to the right.
573 FocusMonitorRight {},
574 /// Focus the monitor below.
575 FocusMonitorDown {},
576 /// Focus the monitor above.
577 FocusMonitorUp {},
578 /// Focus the previous monitor.
579 FocusMonitorPrevious {},
580 /// Focus the next monitor.
581 FocusMonitorNext {},
582 /// Focus a monitor by name.
583 FocusMonitor {
584 /// Name of the output to focus.
585 #[cfg_attr(feature = "clap", arg())]
586 output: String,
587 },
588 /// Move the focused window to the monitor to the left.
589 MoveWindowToMonitorLeft {},
590 /// Move the focused window to the monitor to the right.
591 MoveWindowToMonitorRight {},
592 /// Move the focused window to the monitor below.
593 MoveWindowToMonitorDown {},
594 /// Move the focused window to the monitor above.
595 MoveWindowToMonitorUp {},
596 /// Move the focused window to the previous monitor.
597 MoveWindowToMonitorPrevious {},
598 /// Move the focused window to the next monitor.
599 MoveWindowToMonitorNext {},
600 /// Move a window to a specific monitor.
601 #[cfg_attr(
602 feature = "clap",
603 clap(about = "Move the focused window to a specific monitor")
604 )]
605 MoveWindowToMonitor {
606 /// Id of the window to move.
607 ///
608 /// If `None`, uses the focused window.
609 #[cfg_attr(feature = "clap", arg(long))]
610 id: Option<u64>,
611
612 /// The target output name.
613 #[cfg_attr(feature = "clap", arg())]
614 output: String,
615 },
616 /// Move the focused column to the monitor to the left.
617 MoveColumnToMonitorLeft {},
618 /// Move the focused column to the monitor to the right.
619 MoveColumnToMonitorRight {},
620 /// Move the focused column to the monitor below.
621 MoveColumnToMonitorDown {},
622 /// Move the focused column to the monitor above.
623 MoveColumnToMonitorUp {},
624 /// Move the focused column to the previous monitor.
625 MoveColumnToMonitorPrevious {},
626 /// Move the focused column to the next monitor.
627 MoveColumnToMonitorNext {},
628 /// Move the focused column to a specific monitor.
629 MoveColumnToMonitor {
630 /// The target output name.
631 #[cfg_attr(feature = "clap", arg())]
632 output: String,
633 },
634 /// Change the width of a window.
635 #[cfg_attr(
636 feature = "clap",
637 clap(about = "Change the width of the focused window")
638 )]
639 SetWindowWidth {
640 /// Id of the window whose width to set.
641 ///
642 /// If `None`, uses the focused window.
643 #[cfg_attr(feature = "clap", arg(long))]
644 id: Option<u64>,
645
646 /// How to change the width.
647 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
648 change: SizeChange,
649 },
650 /// Change the height of a window.
651 #[cfg_attr(
652 feature = "clap",
653 clap(about = "Change the height of the focused window")
654 )]
655 SetWindowHeight {
656 /// Id of the window whose height to set.
657 ///
658 /// If `None`, uses the focused window.
659 #[cfg_attr(feature = "clap", arg(long))]
660 id: Option<u64>,
661
662 /// How to change the height.
663 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
664 change: SizeChange,
665 },
666 /// Reset the height of a window back to automatic.
667 #[cfg_attr(
668 feature = "clap",
669 clap(about = "Reset the height of the focused window back to automatic")
670 )]
671 ResetWindowHeight {
672 /// Id of the window whose height to reset.
673 ///
674 /// If `None`, uses the focused window.
675 #[cfg_attr(feature = "clap", arg(long))]
676 id: Option<u64>,
677 },
678 /// Switch between preset column widths.
679 SwitchPresetColumnWidth {},
680 /// Switch between preset column widths backwards.
681 SwitchPresetColumnWidthBack {},
682 /// Switch between preset window widths.
683 SwitchPresetWindowWidth {
684 /// Id of the window whose width to switch.
685 ///
686 /// If `None`, uses the focused window.
687 #[cfg_attr(feature = "clap", arg(long))]
688 id: Option<u64>,
689 },
690 /// Switch between preset window widths backwards.
691 SwitchPresetWindowWidthBack {
692 /// Id of the window whose width to switch.
693 ///
694 /// If `None`, uses the focused window.
695 #[cfg_attr(feature = "clap", arg(long))]
696 id: Option<u64>,
697 },
698 /// Switch between preset window heights.
699 SwitchPresetWindowHeight {
700 /// Id of the window whose height to switch.
701 ///
702 /// If `None`, uses the focused window.
703 #[cfg_attr(feature = "clap", arg(long))]
704 id: Option<u64>,
705 },
706 /// Switch between preset window heights backwards.
707 SwitchPresetWindowHeightBack {
708 /// Id of the window whose height to switch.
709 ///
710 /// If `None`, uses the focused window.
711 #[cfg_attr(feature = "clap", arg(long))]
712 id: Option<u64>,
713 },
714 /// Toggle the maximized state of the focused column.
715 MaximizeColumn {},
716 /// Change the width of the focused column.
717 SetColumnWidth {
718 /// How to change the width.
719 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
720 change: SizeChange,
721 },
722 /// Expand the focused column to space not taken up by other fully visible columns.
723 ExpandColumnToAvailableWidth {},
724 /// Switch between keyboard layouts.
725 SwitchLayout {
726 /// Layout to switch to.
727 #[cfg_attr(feature = "clap", arg())]
728 layout: LayoutSwitchTarget,
729 },
730 /// Show the hotkey overlay.
731 ShowHotkeyOverlay {},
732 /// Move the focused workspace to the monitor to the left.
733 MoveWorkspaceToMonitorLeft {},
734 /// Move the focused workspace to the monitor to the right.
735 MoveWorkspaceToMonitorRight {},
736 /// Move the focused workspace to the monitor below.
737 MoveWorkspaceToMonitorDown {},
738 /// Move the focused workspace to the monitor above.
739 MoveWorkspaceToMonitorUp {},
740 /// Move the focused workspace to the previous monitor.
741 MoveWorkspaceToMonitorPrevious {},
742 /// Move the focused workspace to the next monitor.
743 MoveWorkspaceToMonitorNext {},
744 /// Move a workspace to a specific monitor.
745 #[cfg_attr(
746 feature = "clap",
747 clap(about = "Move the focused workspace to a specific monitor")
748 )]
749 MoveWorkspaceToMonitor {
750 /// The target output name.
751 #[cfg_attr(feature = "clap", arg())]
752 output: String,
753
754 // Reference (index or name) of the workspace to move.
755 ///
756 /// If `None`, uses the focused workspace.
757 #[cfg_attr(feature = "clap", arg(long))]
758 reference: Option<WorkspaceReferenceArg>,
759 },
760 /// Toggle a debug tint on windows.
761 ToggleDebugTint {},
762 /// Toggle visualization of render element opaque regions.
763 DebugToggleOpaqueRegions {},
764 /// Toggle visualization of output damage.
765 DebugToggleDamage {},
766 /// Move the focused window between the floating and the tiling layout.
767 ToggleWindowFloating {
768 /// Id of the window to move.
769 ///
770 /// If `None`, uses the focused window.
771 #[cfg_attr(feature = "clap", arg(long))]
772 id: Option<u64>,
773 },
774 /// Move the focused window to the floating layout.
775 MoveWindowToFloating {
776 /// Id of the window to move.
777 ///
778 /// If `None`, uses the focused window.
779 #[cfg_attr(feature = "clap", arg(long))]
780 id: Option<u64>,
781 },
782 /// Move the focused window to the tiling layout.
783 MoveWindowToTiling {
784 /// Id of the window to move.
785 ///
786 /// If `None`, uses the focused window.
787 #[cfg_attr(feature = "clap", arg(long))]
788 id: Option<u64>,
789 },
790 /// Switches focus to the floating layout.
791 FocusFloating {},
792 /// Switches focus to the tiling layout.
793 FocusTiling {},
794 /// Toggles the focus between the floating and the tiling layout.
795 SwitchFocusBetweenFloatingAndTiling {},
796 /// Move a floating window on screen.
797 #[cfg_attr(feature = "clap", clap(about = "Move the floating window on screen"))]
798 MoveFloatingWindow {
799 /// Id of the window to move.
800 ///
801 /// If `None`, uses the focused window.
802 #[cfg_attr(feature = "clap", arg(long))]
803 id: Option<u64>,
804
805 /// How to change the X position.
806 #[cfg_attr(
807 feature = "clap",
808 arg(short, long, default_value = "+0", allow_negative_numbers = true)
809 )]
810 x: PositionChange,
811
812 /// How to change the Y position.
813 #[cfg_attr(
814 feature = "clap",
815 arg(short, long, default_value = "+0", allow_negative_numbers = true)
816 )]
817 y: PositionChange,
818 },
819 /// Toggle the opacity of a window.
820 #[cfg_attr(
821 feature = "clap",
822 clap(about = "Toggle the opacity of the focused window")
823 )]
824 ToggleWindowRuleOpacity {
825 /// Id of the window.
826 ///
827 /// If `None`, uses the focused window.
828 #[cfg_attr(feature = "clap", arg(long))]
829 id: Option<u64>,
830 },
831 /// Set the dynamic cast target to a window.
832 #[cfg_attr(
833 feature = "clap",
834 clap(about = "Set the dynamic cast target to the focused window")
835 )]
836 SetDynamicCastWindow {
837 /// Id of the window to target.
838 ///
839 /// If `None`, uses the focused window.
840 #[cfg_attr(feature = "clap", arg(long))]
841 id: Option<u64>,
842 },
843 /// Set the dynamic cast target to a monitor.
844 #[cfg_attr(
845 feature = "clap",
846 clap(about = "Set the dynamic cast target to the focused monitor")
847 )]
848 SetDynamicCastMonitor {
849 /// Name of the output to target.
850 ///
851 /// If `None`, uses the focused output.
852 #[cfg_attr(feature = "clap", arg())]
853 output: Option<String>,
854 },
855 /// Clear the dynamic cast target, making it show nothing.
856 ClearDynamicCastTarget {},
857 /// Toggle (open/close) the Overview.
858 ToggleOverview {},
859 /// Open the Overview.
860 OpenOverview {},
861 /// Close the Overview.
862 CloseOverview {},
863 /// Toggle urgent status of a window.
864 ToggleWindowUrgent {
865 /// Id of the window to toggle urgent.
866 #[cfg_attr(feature = "clap", arg(long))]
867 id: u64,
868 },
869 /// Set urgent status of a window.
870 SetWindowUrgent {
871 /// Id of the window to set urgent.
872 #[cfg_attr(feature = "clap", arg(long))]
873 id: u64,
874 },
875 /// Unset urgent status of a window.
876 UnsetWindowUrgent {
877 /// Id of the window to unset urgent.
878 #[cfg_attr(feature = "clap", arg(long))]
879 id: u64,
880 },
881 /// Reload the config file.
882 ///
883 /// Can be useful for scripts changing the config file, to avoid waiting the small duration for
884 /// niri's config file watcher to notice the changes.
885 LoadConfigFile {},
886}
887
888/// Change in window or column size.
889#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
890#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
891pub enum SizeChange {
892 /// Set the size in logical pixels.
893 SetFixed(i32),
894 /// Set the size as a proportion of the working area.
895 SetProportion(f64),
896 /// Add or subtract to the current size in logical pixels.
897 AdjustFixed(i32),
898 /// Add or subtract to the current size as a proportion of the working area.
899 AdjustProportion(f64),
900}
901
902/// Change in floating window position.
903#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
904#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
905pub enum PositionChange {
906 /// Set the position in logical pixels.
907 SetFixed(f64),
908 /// Add or subtract to the current position in logical pixels.
909 AdjustFixed(f64),
910}
911
912/// Workspace reference (id, index or name) to operate on.
913#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
914#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
915pub enum WorkspaceReferenceArg {
916 /// Id of the workspace.
917 Id(u64),
918 /// Index of the workspace.
919 Index(u8),
920 /// Name of the workspace.
921 Name(String),
922}
923
924/// Layout to switch to.
925#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
926#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
927pub enum LayoutSwitchTarget {
928 /// The next configured layout.
929 Next,
930 /// The previous configured layout.
931 Prev,
932 /// The specific layout by index.
933 Index(u8),
934}
935
936/// How windows display in a column.
937#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
938#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
939pub enum ColumnDisplay {
940 /// Windows are tiled vertically across the working area height.
941 Normal,
942 /// Windows are in tabs.
943 Tabbed,
944}
945
946/// Output actions that niri can perform.
947// Variants in this enum should match the spelling of the ones in niri-config. Most thigs from
948// niri-config should be present here.
949#[derive(Serialize, Deserialize, Debug, Clone)]
950#[cfg_attr(feature = "clap", derive(clap::Parser))]
951#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
952#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
953#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
954pub enum OutputAction {
955 /// Turn off the output.
956 Off,
957 /// Turn on the output.
958 On,
959 /// Set the output mode.
960 Mode {
961 /// Mode to set, or "auto" for automatic selection.
962 ///
963 /// Run `niri msg outputs` to see the available modes.
964 #[cfg_attr(feature = "clap", arg())]
965 mode: ModeToSet,
966 },
967 /// Set the output scale.
968 Scale {
969 /// Scale factor to set, or "auto" for automatic selection.
970 #[cfg_attr(feature = "clap", arg())]
971 scale: ScaleToSet,
972 },
973 /// Set the output transform.
974 Transform {
975 /// Transform to set, counter-clockwise.
976 #[cfg_attr(feature = "clap", arg())]
977 transform: Transform,
978 },
979 /// Set the output position.
980 Position {
981 /// Position to set, or "auto" for automatic selection.
982 #[cfg_attr(feature = "clap", command(subcommand))]
983 position: PositionToSet,
984 },
985 /// Set the variable refresh rate mode.
986 Vrr {
987 /// Variable refresh rate mode to set.
988 #[cfg_attr(feature = "clap", command(flatten))]
989 vrr: VrrToSet,
990 },
991}
992
993/// Output mode to set.
994#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
995#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
996pub enum ModeToSet {
997 /// Niri will pick the mode automatically.
998 Automatic,
999 /// Specific mode.
1000 Specific(ConfiguredMode),
1001}
1002
1003/// Output mode as set in the config file.
1004#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1005#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1006pub struct ConfiguredMode {
1007 /// Width in physical pixels.
1008 pub width: u16,
1009 /// Height in physical pixels.
1010 pub height: u16,
1011 /// Refresh rate.
1012 pub refresh: Option<f64>,
1013}
1014
1015/// Output scale to set.
1016#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1017#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1018pub enum ScaleToSet {
1019 /// Niri will pick the scale automatically.
1020 Automatic,
1021 /// Specific scale.
1022 Specific(f64),
1023}
1024
1025/// Output position to set.
1026#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1027#[cfg_attr(feature = "clap", derive(clap::Subcommand))]
1028#[cfg_attr(feature = "clap", command(subcommand_value_name = "POSITION"))]
1029#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Position Values"))]
1030#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1031pub enum PositionToSet {
1032 /// Position the output automatically.
1033 #[cfg_attr(feature = "clap", command(name = "auto"))]
1034 Automatic,
1035 /// Set a specific position.
1036 #[cfg_attr(feature = "clap", command(name = "set"))]
1037 Specific(ConfiguredPosition),
1038}
1039
1040/// Output position as set in the config file.
1041#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1042#[cfg_attr(feature = "clap", derive(clap::Args))]
1043#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1044pub struct ConfiguredPosition {
1045 /// Logical X position.
1046 pub x: i32,
1047 /// Logical Y position.
1048 pub y: i32,
1049}
1050
1051/// Output VRR to set.
1052#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1053#[cfg_attr(feature = "clap", derive(clap::Args))]
1054#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1055pub struct VrrToSet {
1056 /// Whether to enable variable refresh rate.
1057 #[cfg_attr(
1058 feature = "clap",
1059 arg(
1060 value_name = "ON|OFF",
1061 action = clap::ArgAction::Set,
1062 value_parser = clap::builder::BoolishValueParser::new(),
1063 hide_possible_values = true,
1064 ),
1065 )]
1066 pub vrr: bool,
1067 /// Only enable when the output shows a window matching the variable-refresh-rate window rule.
1068 #[cfg_attr(feature = "clap", arg(long))]
1069 pub on_demand: bool,
1070}
1071
1072/// Connected output.
1073#[derive(Debug, Serialize, Deserialize, Clone)]
1074#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1075pub struct Output {
1076 /// Name of the output.
1077 pub name: String,
1078 /// Textual description of the manufacturer.
1079 pub make: String,
1080 /// Textual description of the model.
1081 pub model: String,
1082 /// Serial of the output, if known.
1083 pub serial: Option<String>,
1084 /// Physical width and height of the output in millimeters, if known.
1085 pub physical_size: Option<(u32, u32)>,
1086 /// Available modes for the output.
1087 pub modes: Vec<Mode>,
1088 /// Index of the current mode in [`Self::modes`].
1089 ///
1090 /// `None` if the output is disabled.
1091 pub current_mode: Option<usize>,
1092 /// Whether the output supports variable refresh rate.
1093 pub vrr_supported: bool,
1094 /// Whether variable refresh rate is enabled on the output.
1095 pub vrr_enabled: bool,
1096 /// Logical output information.
1097 ///
1098 /// `None` if the output is not mapped to any logical output (for example, if it is disabled).
1099 pub logical: Option<LogicalOutput>,
1100}
1101
1102/// Output mode.
1103#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
1104#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1105pub struct Mode {
1106 /// Width in physical pixels.
1107 pub width: u16,
1108 /// Height in physical pixels.
1109 pub height: u16,
1110 /// Refresh rate in millihertz.
1111 pub refresh_rate: u32,
1112 /// Whether this mode is preferred by the monitor.
1113 pub is_preferred: bool,
1114}
1115
1116/// Logical output in the compositor's coordinate space.
1117#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
1118#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1119pub struct LogicalOutput {
1120 /// Logical X position.
1121 pub x: i32,
1122 /// Logical Y position.
1123 pub y: i32,
1124 /// Width in logical pixels.
1125 pub width: u32,
1126 /// Height in logical pixels.
1127 pub height: u32,
1128 /// Scale factor.
1129 pub scale: f64,
1130 /// Transform.
1131 pub transform: Transform,
1132}
1133
1134/// Output transform, which goes counter-clockwise.
1135#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1136#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
1137#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1138pub enum Transform {
1139 /// Untransformed.
1140 Normal,
1141 /// Rotated by 90°.
1142 #[serde(rename = "90")]
1143 _90,
1144 /// Rotated by 180°.
1145 #[serde(rename = "180")]
1146 _180,
1147 /// Rotated by 270°.
1148 #[serde(rename = "270")]
1149 _270,
1150 /// Flipped horizontally.
1151 Flipped,
1152 /// Rotated by 90° and flipped horizontally.
1153 #[cfg_attr(feature = "clap", value(name("flipped-90")))]
1154 Flipped90,
1155 /// Flipped vertically.
1156 #[cfg_attr(feature = "clap", value(name("flipped-180")))]
1157 Flipped180,
1158 /// Rotated by 270° and flipped horizontally.
1159 #[cfg_attr(feature = "clap", value(name("flipped-270")))]
1160 Flipped270,
1161}
1162
1163/// Toplevel window.
1164#[derive(Serialize, Deserialize, Debug, Clone)]
1165#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1166pub struct Window {
1167 /// Unique id of this window.
1168 ///
1169 /// This id remains constant while this window is open.
1170 ///
1171 /// Do not assume that window ids will always increase without wrapping, or start at 1. That is
1172 /// an implementation detail subject to change. For example, ids may change to be randomly
1173 /// generated for each new window.
1174 pub id: u64,
1175 /// Title, if set.
1176 pub title: Option<String>,
1177 /// Application ID, if set.
1178 pub app_id: Option<String>,
1179 /// Process ID that created the Wayland connection for this window, if known.
1180 ///
1181 /// Currently, windows created by xdg-desktop-portal-gnome will have a `None` PID, but this may
1182 /// change in the future.
1183 pub pid: Option<i32>,
1184 /// Id of the workspace this window is on, if any.
1185 pub workspace_id: Option<u64>,
1186 /// Whether this window is currently focused.
1187 ///
1188 /// There can be either one focused window or zero (e.g. when a layer-shell surface has focus).
1189 pub is_focused: bool,
1190 /// Whether this window is currently floating.
1191 ///
1192 /// If the window isn't floating then it is in the tiling layout.
1193 pub is_floating: bool,
1194 /// Whether this window requests your attention.
1195 pub is_urgent: bool,
1196 /// Position- and size-related properties of the window.
1197 pub layout: WindowLayout,
1198}
1199
1200/// Position- and size-related properties of a [`Window`].
1201///
1202/// Optional properties will be unset for some windows, do not rely on them being present. Whether
1203/// some optional properties are present or absent for certain window types may change across niri
1204/// releases.
1205///
1206/// All sizes and positions are in *logical pixels* unless stated otherwise. Logical sizes may be
1207/// fractional. For example, at 1.25 monitor scale, a 2-physical-pixel-wide window border is 1.6
1208/// logical pixels wide.
1209///
1210/// This struct contains positions and sizes both for full tiles ([`Self::tile_size`],
1211/// [`Self::tile_pos_in_workspace_view`]) and the window geometry ([`Self::window_size`],
1212/// [`Self::window_offset_in_tile`]). For visual displays, use the tile properties, as they
1213/// correspond to what the user visually considers "window". The window properties on the other
1214/// hand are mainly useful when you need to know the underlying Wayland window sizes, e.g. for
1215/// application debugging.
1216#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1217#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1218pub struct WindowLayout {
1219 /// Location of a tiled window within a workspace: (column index, tile index in column).
1220 ///
1221 /// The indices are 1-based, i.e. the leftmost column is at index 1 and the topmost tile in a
1222 /// column is at index 1. This is consistent with [`Action::FocusColumn`] and
1223 /// [`Action::FocusWindowInColumn`].
1224 pub pos_in_scrolling_layout: Option<(usize, usize)>,
1225 /// Size of the tile this window is in, including decorations like borders.
1226 pub tile_size: (f64, f64),
1227 /// Size of the window's visual geometry itself.
1228 ///
1229 /// Does not include niri decorations like borders.
1230 ///
1231 /// Currently, Wayland toplevel windows can only be integer-sized in logical pixels, even
1232 /// though it doesn't necessarily align to physical pixels.
1233 pub window_size: (i32, i32),
1234 /// Tile position within the current view of the workspace.
1235 ///
1236 /// This is the same "workspace view" as in gradients' `relative-to` in the niri config.
1237 pub tile_pos_in_workspace_view: Option<(f64, f64)>,
1238 /// Location of the window's visual geometry within its tile.
1239 ///
1240 /// This includes things like border sizes. For fullscreened fixed-size windows this includes
1241 /// the distance from the corner of the black backdrop to the corner of the (centered) window
1242 /// contents.
1243 pub window_offset_in_tile: (f64, f64),
1244}
1245
1246/// Output configuration change result.
1247#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1248#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1249pub enum OutputConfigChanged {
1250 /// The target output was connected and the change was applied.
1251 Applied,
1252 /// The target output was not found, the change will be applied when it is connected.
1253 OutputWasMissing,
1254}
1255
1256/// A workspace.
1257#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1258#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1259pub struct Workspace {
1260 /// Unique id of this workspace.
1261 ///
1262 /// This id remains constant regardless of the workspace moving around and across monitors.
1263 ///
1264 /// Do not assume that workspace ids will always increase without wrapping, or start at 1. That
1265 /// is an implementation detail subject to change. For example, ids may change to be randomly
1266 /// generated for each new workspace.
1267 pub id: u64,
1268 /// Index of the workspace on its monitor.
1269 ///
1270 /// This is the same index you can use for requests like `niri msg action focus-workspace`.
1271 ///
1272 /// This index *will change* as you move and re-order workspace. It is merely the workspace's
1273 /// current position on its monitor. Workspaces on different monitors can have the same index.
1274 ///
1275 /// If you need a unique workspace id that doesn't change, see [`Self::id`].
1276 pub idx: u8,
1277 /// Optional name of the workspace.
1278 pub name: Option<String>,
1279 /// Name of the output that the workspace is on.
1280 ///
1281 /// Can be `None` if no outputs are currently connected.
1282 pub output: Option<String>,
1283 /// Whether the workspace currently has an urgent window in its output.
1284 pub is_urgent: bool,
1285 /// Whether the workspace is currently active on its output.
1286 ///
1287 /// Every output has one active workspace, the one that is currently visible on that output.
1288 pub is_active: bool,
1289 /// Whether the workspace is currently focused.
1290 ///
1291 /// There's only one focused workspace across all outputs.
1292 pub is_focused: bool,
1293 /// Id of the active window on this workspace, if any.
1294 pub active_window_id: Option<u64>,
1295}
1296
1297/// Configured keyboard layouts.
1298#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1299#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1300pub struct KeyboardLayouts {
1301 /// XKB names of the configured layouts.
1302 pub names: Vec<String>,
1303 /// Index of the currently active layout in `names`.
1304 pub current_idx: u8,
1305}
1306
1307/// A layer-shell layer.
1308#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1309#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1310pub enum Layer {
1311 /// The background layer.
1312 Background,
1313 /// The bottom layer.
1314 Bottom,
1315 /// The top layer.
1316 Top,
1317 /// The overlay layer.
1318 Overlay,
1319}
1320
1321/// Keyboard interactivity modes for a layer-shell surface.
1322#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1323#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1324pub enum LayerSurfaceKeyboardInteractivity {
1325 /// Surface cannot receive keyboard focus.
1326 None,
1327 /// Surface receives keyboard focus whenever possible.
1328 Exclusive,
1329 /// Surface receives keyboard focus on demand, e.g. when clicked.
1330 OnDemand,
1331}
1332
1333/// A layer-shell surface.
1334#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1335#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1336pub struct LayerSurface {
1337 /// Namespace provided by the layer-shell client.
1338 pub namespace: String,
1339 /// Name of the output the surface is on.
1340 pub output: String,
1341 /// Layer that the surface is on.
1342 pub layer: Layer,
1343 /// The surface's keyboard interactivity mode.
1344 pub keyboard_interactivity: LayerSurfaceKeyboardInteractivity,
1345}
1346
1347/// A compositor event.
1348#[derive(Serialize, Deserialize, Debug, Clone)]
1349#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1350pub enum Event {
1351 /// The workspace configuration has changed.
1352 WorkspacesChanged {
1353 /// The new workspace configuration.
1354 ///
1355 /// This configuration completely replaces the previous configuration. I.e. if any
1356 /// workspaces are missing from here, then they were deleted.
1357 workspaces: Vec<Workspace>,
1358 },
1359 /// The workspace urgency changed.
1360 WorkspaceUrgencyChanged {
1361 /// Id of the workspace.
1362 id: u64,
1363 /// Whether this workspace has an urgent window.
1364 urgent: bool,
1365 },
1366 /// A workspace was activated on an output.
1367 ///
1368 /// This doesn't always mean the workspace became focused, just that it's now the active
1369 /// workspace on its output. All other workspaces on the same output become inactive.
1370 WorkspaceActivated {
1371 /// Id of the newly active workspace.
1372 id: u64,
1373 /// Whether this workspace also became focused.
1374 ///
1375 /// If `true`, this is now the single focused workspace. All other workspaces are no longer
1376 /// focused, but they may remain active on their respective outputs.
1377 focused: bool,
1378 },
1379 /// An active window changed on a workspace.
1380 WorkspaceActiveWindowChanged {
1381 /// Id of the workspace on which the active window changed.
1382 workspace_id: u64,
1383 /// Id of the new active window, if any.
1384 active_window_id: Option<u64>,
1385 },
1386 /// The window configuration has changed.
1387 WindowsChanged {
1388 /// The new window configuration.
1389 ///
1390 /// This configuration completely replaces the previous configuration. I.e. if any windows
1391 /// are missing from here, then they were closed.
1392 windows: Vec<Window>,
1393 },
1394 /// A new toplevel window was opened, or an existing toplevel window changed.
1395 WindowOpenedOrChanged {
1396 /// The new or updated window.
1397 ///
1398 /// If the window is focused, all other windows are no longer focused.
1399 window: Window,
1400 },
1401 /// A toplevel window was closed.
1402 WindowClosed {
1403 /// Id of the removed window.
1404 id: u64,
1405 },
1406 /// Window focus changed.
1407 ///
1408 /// All other windows are no longer focused.
1409 WindowFocusChanged {
1410 /// Id of the newly focused window, or `None` if no window is now focused.
1411 id: Option<u64>,
1412 },
1413 /// Window urgency changed.
1414 WindowUrgencyChanged {
1415 /// Id of the window.
1416 id: u64,
1417 /// The new urgency state of the window.
1418 urgent: bool,
1419 },
1420 /// The layout of one or more windows has changed.
1421 WindowLayoutsChanged {
1422 /// Pairs consisting of a window id and new layout information for the window.
1423 changes: Vec<(u64, WindowLayout)>,
1424 },
1425 /// The configured keyboard layouts have changed.
1426 KeyboardLayoutsChanged {
1427 /// The new keyboard layout configuration.
1428 keyboard_layouts: KeyboardLayouts,
1429 },
1430 /// The keyboard layout switched.
1431 KeyboardLayoutSwitched {
1432 /// Index of the newly active layout.
1433 idx: u8,
1434 },
1435 /// The overview was opened or closed.
1436 OverviewOpenedOrClosed {
1437 /// The new state of the overview.
1438 is_open: bool,
1439 },
1440 /// The configuration was reloaded.
1441 ///
1442 /// You will always receive this event when connecting to the event stream, indicating the last
1443 /// config load attempt.
1444 ConfigLoaded {
1445 /// Whether the loading failed.
1446 ///
1447 /// For example, the config file couldn't be parsed.
1448 failed: bool,
1449 },
1450}
1451
1452impl FromStr for WorkspaceReferenceArg {
1453 type Err = &'static str;
1454
1455 fn from_str(s: &str) -> Result<Self, Self::Err> {
1456 let reference = if let Ok(index) = s.parse::<i32>() {
1457 if let Ok(idx) = u8::try_from(index) {
1458 Self::Index(idx)
1459 } else {
1460 return Err("workspace index must be between 0 and 255");
1461 }
1462 } else {
1463 Self::Name(s.to_string())
1464 };
1465
1466 Ok(reference)
1467 }
1468}
1469
1470impl FromStr for SizeChange {
1471 type Err = &'static str;
1472
1473 fn from_str(s: &str) -> Result<Self, Self::Err> {
1474 match s.split_once('%') {
1475 Some((value, empty)) => {
1476 if !empty.is_empty() {
1477 return Err("trailing characters after '%' are not allowed");
1478 }
1479
1480 match value.bytes().next() {
1481 Some(b'-' | b'+') => {
1482 let value = value.parse().map_err(|_| "error parsing value")?;
1483 Ok(Self::AdjustProportion(value))
1484 }
1485 Some(_) => {
1486 let value = value.parse().map_err(|_| "error parsing value")?;
1487 Ok(Self::SetProportion(value))
1488 }
1489 None => Err("value is missing"),
1490 }
1491 }
1492 None => {
1493 let value = s;
1494 match value.bytes().next() {
1495 Some(b'-' | b'+') => {
1496 let value = value.parse().map_err(|_| "error parsing value")?;
1497 Ok(Self::AdjustFixed(value))
1498 }
1499 Some(_) => {
1500 let value = value.parse().map_err(|_| "error parsing value")?;
1501 Ok(Self::SetFixed(value))
1502 }
1503 None => Err("value is missing"),
1504 }
1505 }
1506 }
1507 }
1508}
1509
1510impl FromStr for PositionChange {
1511 type Err = &'static str;
1512
1513 fn from_str(s: &str) -> Result<Self, Self::Err> {
1514 let value = s;
1515 match value.bytes().next() {
1516 Some(b'-' | b'+') => {
1517 let value = value.parse().map_err(|_| "error parsing value")?;
1518 Ok(Self::AdjustFixed(value))
1519 }
1520 Some(_) => {
1521 let value = value.parse().map_err(|_| "error parsing value")?;
1522 Ok(Self::SetFixed(value))
1523 }
1524 None => Err("value is missing"),
1525 }
1526 }
1527}
1528
1529impl FromStr for LayoutSwitchTarget {
1530 type Err = &'static str;
1531
1532 fn from_str(s: &str) -> Result<Self, Self::Err> {
1533 match s {
1534 "next" => Ok(Self::Next),
1535 "prev" => Ok(Self::Prev),
1536 other => match other.parse() {
1537 Ok(layout) => Ok(Self::Index(layout)),
1538 _ => Err(r#"invalid layout action, can be "next", "prev" or a layout index"#),
1539 },
1540 }
1541 }
1542}
1543
1544impl FromStr for ColumnDisplay {
1545 type Err = &'static str;
1546
1547 fn from_str(s: &str) -> Result<Self, Self::Err> {
1548 match s {
1549 "normal" => Ok(Self::Normal),
1550 "tabbed" => Ok(Self::Tabbed),
1551 _ => Err(r#"invalid column display, can be "normal" or "tabbed""#),
1552 }
1553 }
1554}
1555
1556impl FromStr for Transform {
1557 type Err = &'static str;
1558
1559 fn from_str(s: &str) -> Result<Self, Self::Err> {
1560 match s {
1561 "normal" => Ok(Self::Normal),
1562 "90" => Ok(Self::_90),
1563 "180" => Ok(Self::_180),
1564 "270" => Ok(Self::_270),
1565 "flipped" => Ok(Self::Flipped),
1566 "flipped-90" => Ok(Self::Flipped90),
1567 "flipped-180" => Ok(Self::Flipped180),
1568 "flipped-270" => Ok(Self::Flipped270),
1569 _ => Err(concat!(
1570 r#"invalid transform, can be "90", "180", "270", "#,
1571 r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
1572 )),
1573 }
1574 }
1575}
1576
1577impl FromStr for ModeToSet {
1578 type Err = &'static str;
1579
1580 fn from_str(s: &str) -> Result<Self, Self::Err> {
1581 if s.eq_ignore_ascii_case("auto") {
1582 return Ok(Self::Automatic);
1583 }
1584
1585 let mode = s.parse()?;
1586 Ok(Self::Specific(mode))
1587 }
1588}
1589
1590impl FromStr for ConfiguredMode {
1591 type Err = &'static str;
1592
1593 fn from_str(s: &str) -> Result<Self, Self::Err> {
1594 let Some((width, rest)) = s.split_once('x') else {
1595 return Err("no 'x' separator found");
1596 };
1597
1598 let (height, refresh) = match rest.split_once('@') {
1599 Some((height, refresh)) => (height, Some(refresh)),
1600 None => (rest, None),
1601 };
1602
1603 let width = width.parse().map_err(|_| "error parsing width")?;
1604 let height = height.parse().map_err(|_| "error parsing height")?;
1605 let refresh = refresh
1606 .map(str::parse)
1607 .transpose()
1608 .map_err(|_| "error parsing refresh rate")?;
1609
1610 Ok(Self {
1611 width,
1612 height,
1613 refresh,
1614 })
1615 }
1616}
1617
1618impl FromStr for ScaleToSet {
1619 type Err = &'static str;
1620
1621 fn from_str(s: &str) -> Result<Self, Self::Err> {
1622 if s.eq_ignore_ascii_case("auto") {
1623 return Ok(Self::Automatic);
1624 }
1625
1626 let scale = s.parse().map_err(|_| "error parsing scale")?;
1627 Ok(Self::Specific(scale))
1628 }
1629}
1630
1631#[cfg(test)]
1632mod tests {
1633 use super::*;
1634
1635 #[test]
1636 fn parse_size_change() {
1637 assert_eq!(
1638 "10".parse::<SizeChange>().unwrap(),
1639 SizeChange::SetFixed(10),
1640 );
1641 assert_eq!(
1642 "+10".parse::<SizeChange>().unwrap(),
1643 SizeChange::AdjustFixed(10),
1644 );
1645 assert_eq!(
1646 "-10".parse::<SizeChange>().unwrap(),
1647 SizeChange::AdjustFixed(-10),
1648 );
1649 assert_eq!(
1650 "10%".parse::<SizeChange>().unwrap(),
1651 SizeChange::SetProportion(10.),
1652 );
1653 assert_eq!(
1654 "+10%".parse::<SizeChange>().unwrap(),
1655 SizeChange::AdjustProportion(10.),
1656 );
1657 assert_eq!(
1658 "-10%".parse::<SizeChange>().unwrap(),
1659 SizeChange::AdjustProportion(-10.),
1660 );
1661
1662 assert!("-".parse::<SizeChange>().is_err());
1663 assert!("10% ".parse::<SizeChange>().is_err());
1664 }
1665
1666 #[test]
1667 fn parse_position_change() {
1668 assert_eq!(
1669 "10".parse::<PositionChange>().unwrap(),
1670 PositionChange::SetFixed(10.),
1671 );
1672 assert_eq!(
1673 "+10".parse::<PositionChange>().unwrap(),
1674 PositionChange::AdjustFixed(10.),
1675 );
1676 assert_eq!(
1677 "-10".parse::<PositionChange>().unwrap(),
1678 PositionChange::AdjustFixed(-10.),
1679 );
1680
1681 assert!("10%".parse::<PositionChange>().is_err());
1682 assert!("+10%".parse::<PositionChange>().is_err());
1683 assert!("-10%".parse::<PositionChange>().is_err());
1684 assert!("-".parse::<PositionChange>().is_err());
1685 assert!("10% ".parse::<PositionChange>().is_err());
1686 }
1687}