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.11.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;
57use std::time::Duration;
58
59use serde::{Deserialize, Serialize};
60
61pub mod socket;
62pub mod state;
63
64/// Request from client to niri.
65#[derive(Debug, Serialize, Deserialize, Clone)]
66#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
67pub enum Request {
68 /// Request the version string for the running niri instance.
69 Version,
70 /// Request information about connected outputs.
71 Outputs,
72 /// Request information about workspaces.
73 Workspaces,
74 /// Request information about open windows.
75 Windows,
76 /// Request information about layer-shell surfaces.
77 Layers,
78 /// Request information about the configured keyboard layouts.
79 KeyboardLayouts,
80 /// Request information about the focused output.
81 FocusedOutput,
82 /// Request information about the focused window.
83 FocusedWindow,
84 /// Request picking a window and get its information.
85 PickWindow,
86 /// Request picking a color from the screen.
87 PickColor,
88 /// Perform an action.
89 Action(Action),
90 /// Change output configuration temporarily.
91 ///
92 /// The configuration is changed temporarily and not saved into the config file. If the output
93 /// configuration subsequently changes in the config file, these temporary changes will be
94 /// forgotten.
95 Output {
96 /// Output name.
97 output: String,
98 /// Configuration to apply.
99 action: OutputAction,
100 },
101 /// Start continuously receiving events from the compositor.
102 ///
103 /// The compositor should reply with `Reply::Ok(Response::Handled)`, then continuously send
104 /// [`Event`]s, one per line.
105 ///
106 /// The event stream will always give you the full current state up-front. For example, the
107 /// first workspace-related event you will receive will be [`Event::WorkspacesChanged`]
108 /// containing the full current workspaces state. You *do not* need to separately send
109 /// [`Request::Workspaces`] when using the event stream.
110 ///
111 /// Where reasonable, event stream state updates are atomic, though this is not always the
112 /// case. For example, a window may end up with a workspace id for a workspace that had already
113 /// been removed. This can happen if the corresponding [`Event::WorkspacesChanged`] arrives
114 /// before the corresponding [`Event::WindowOpenedOrChanged`].
115 EventStream,
116 /// Respond with an error (for testing error handling).
117 ReturnError,
118 /// Request information about the overview.
119 OverviewState,
120 /// Request information about screencasts.
121 Casts,
122}
123
124/// Reply from niri to client.
125///
126/// Every request gets one reply.
127///
128/// * If an error had occurred, it will be an `Reply::Err`.
129/// * If the request does not need any particular response, it will be
130/// `Reply::Ok(Response::Handled)`. Kind of like an `Ok(())`.
131/// * Otherwise, it will be `Reply::Ok(response)` with one of the other [`Response`] variants.
132pub type Reply = Result<Response, String>;
133
134/// Successful response from niri to client.
135#[derive(Debug, Serialize, Deserialize, Clone)]
136#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
137pub enum Response {
138 /// A request that does not need a response was handled successfully.
139 Handled,
140 /// The version string for the running niri instance.
141 Version(String),
142 /// Information about connected outputs.
143 ///
144 /// Map from output name to output info.
145 Outputs(HashMap<String, Output>),
146 /// Information about workspaces.
147 Workspaces(Vec<Workspace>),
148 /// Information about open windows.
149 Windows(Vec<Window>),
150 /// Information about layer-shell surfaces.
151 Layers(Vec<LayerSurface>),
152 /// Information about the keyboard layout.
153 KeyboardLayouts(KeyboardLayouts),
154 /// Information about the focused output.
155 FocusedOutput(Option<Output>),
156 /// Information about the focused window.
157 FocusedWindow(Option<Window>),
158 /// Information about the picked window.
159 PickedWindow(Option<Window>),
160 /// Information about the picked color.
161 PickedColor(Option<PickedColor>),
162 /// Output configuration change result.
163 OutputConfigChanged(OutputConfigChanged),
164 /// Information about the overview.
165 OverviewState(Overview),
166 /// Information about screencasts.
167 Casts(Vec<Cast>),
168}
169
170/// Overview information.
171#[derive(Serialize, Deserialize, Debug, Clone)]
172#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
173pub struct Overview {
174 /// Whether the overview is currently open.
175 pub is_open: bool,
176}
177
178/// Color picked from the screen.
179#[derive(Serialize, Deserialize, Debug, Clone)]
180#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
181pub struct PickedColor {
182 /// Color values as red, green, blue, each ranging from 0.0 to 1.0.
183 pub rgb: [f64; 3],
184}
185
186/// Actions that niri can perform.
187// Variants in this enum should match the spelling of the ones in niri-config. Most, but not all,
188// variants from niri-config should be present here.
189#[derive(Serialize, Deserialize, Debug, Clone)]
190#[cfg_attr(feature = "clap", derive(clap::Parser))]
191#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
192#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
193#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
194pub enum Action {
195 /// Exit niri.
196 Quit {
197 /// Skip the "Press Enter to confirm" prompt.
198 #[cfg_attr(feature = "clap", arg(short, long))]
199 skip_confirmation: bool,
200 },
201 /// Power off all monitors via DPMS.
202 PowerOffMonitors {},
203 /// Power on all monitors via DPMS.
204 PowerOnMonitors {},
205 /// Spawn a command.
206 Spawn {
207 /// Command to spawn.
208 #[cfg_attr(feature = "clap", arg(last = true, required = true))]
209 command: Vec<String>,
210 },
211 /// Spawn a command through the shell.
212 SpawnSh {
213 /// Command to run.
214 #[cfg_attr(feature = "clap", arg(last = true, required = true))]
215 command: String,
216 },
217 /// Do a screen transition.
218 DoScreenTransition {
219 /// Delay in milliseconds for the screen to freeze before starting the transition.
220 #[cfg_attr(feature = "clap", arg(short, long))]
221 delay_ms: Option<u16>,
222 },
223 /// Open the screenshot UI.
224 Screenshot {
225 /// Whether to show the mouse pointer by default in the screenshot UI.
226 #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
227 show_pointer: bool,
228
229 /// Path to save the screenshot to.
230 ///
231 /// The path must be absolute, otherwise an error is returned.
232 ///
233 /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
234 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
235 path: Option<String>,
236 },
237 /// Screenshot the focused screen.
238 ScreenshotScreen {
239 /// Write the screenshot to disk in addition to putting it in your clipboard.
240 ///
241 /// The screenshot is saved according to the `screenshot-path` config setting.
242 #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
243 write_to_disk: bool,
244
245 /// Whether to include the mouse pointer in the screenshot.
246 #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))]
247 show_pointer: bool,
248
249 /// Path to save the screenshot to.
250 ///
251 /// The path must be absolute, otherwise an error is returned.
252 ///
253 /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
254 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
255 path: Option<String>,
256 },
257 /// Screenshot a window.
258 #[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
259 ScreenshotWindow {
260 /// Id of the window to screenshot.
261 ///
262 /// If `None`, uses the focused window.
263 #[cfg_attr(feature = "clap", arg(long))]
264 id: Option<u64>,
265 /// Write the screenshot to disk in addition to putting it in your clipboard.
266 ///
267 /// The screenshot is saved according to the `screenshot-path` config setting.
268 #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))]
269 write_to_disk: bool,
270
271 /// Whether to include the mouse pointer in the screenshot.
272 ///
273 /// The pointer will be included only if the window is currently receiving pointer input
274 /// (usually this means the pointer is on top of the window).
275 #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = false))]
276 show_pointer: bool,
277
278 /// Path to save the screenshot to.
279 ///
280 /// The path must be absolute, otherwise an error is returned.
281 ///
282 /// If `None`, the screenshot is saved according to the `screenshot-path` config setting.
283 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set))]
284 path: Option<String>,
285 },
286 /// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface.
287 ToggleKeyboardShortcutsInhibit {},
288 /// Close a window.
289 #[cfg_attr(feature = "clap", clap(about = "Close the focused window"))]
290 CloseWindow {
291 /// Id of the window to close.
292 ///
293 /// If `None`, uses the focused window.
294 #[cfg_attr(feature = "clap", arg(long))]
295 id: Option<u64>,
296 },
297 /// Toggle fullscreen on a window.
298 #[cfg_attr(
299 feature = "clap",
300 clap(about = "Toggle fullscreen on the focused window")
301 )]
302 FullscreenWindow {
303 /// Id of the window to toggle fullscreen of.
304 ///
305 /// If `None`, uses the focused window.
306 #[cfg_attr(feature = "clap", arg(long))]
307 id: Option<u64>,
308 },
309 /// Toggle windowed (fake) fullscreen on a window.
310 #[cfg_attr(
311 feature = "clap",
312 clap(about = "Toggle windowed (fake) fullscreen on the focused window")
313 )]
314 ToggleWindowedFullscreen {
315 /// Id of the window to toggle windowed fullscreen of.
316 ///
317 /// If `None`, uses the focused window.
318 #[cfg_attr(feature = "clap", arg(long))]
319 id: Option<u64>,
320 },
321 /// Focus a window by id.
322 FocusWindow {
323 /// Id of the window to focus.
324 #[cfg_attr(feature = "clap", arg(long))]
325 id: u64,
326 },
327 /// Focus a window in the focused column by index.
328 FocusWindowInColumn {
329 /// Index of the window in the column.
330 ///
331 /// The index starts from 1 for the topmost window.
332 #[cfg_attr(feature = "clap", arg())]
333 index: u8,
334 },
335 /// Focus the previously focused window.
336 FocusWindowPrevious {},
337 /// Focus the column to the left.
338 FocusColumnLeft {},
339 /// Focus the column to the right.
340 FocusColumnRight {},
341 /// Focus the first column.
342 FocusColumnFirst {},
343 /// Focus the last column.
344 FocusColumnLast {},
345 /// Focus the next column to the right, looping if at end.
346 FocusColumnRightOrFirst {},
347 /// Focus the next column to the left, looping if at start.
348 FocusColumnLeftOrLast {},
349 /// Focus a column by index.
350 FocusColumn {
351 /// Index of the column to focus.
352 ///
353 /// The index starts from 1 for the first column.
354 #[cfg_attr(feature = "clap", arg())]
355 index: usize,
356 },
357 /// Focus the window or the monitor above.
358 FocusWindowOrMonitorUp {},
359 /// Focus the window or the monitor below.
360 FocusWindowOrMonitorDown {},
361 /// Focus the column or the monitor to the left.
362 FocusColumnOrMonitorLeft {},
363 /// Focus the column or the monitor to the right.
364 FocusColumnOrMonitorRight {},
365 /// Focus the window below.
366 FocusWindowDown {},
367 /// Focus the window above.
368 FocusWindowUp {},
369 /// Focus the window below or the column to the left.
370 FocusWindowDownOrColumnLeft {},
371 /// Focus the window below or the column to the right.
372 FocusWindowDownOrColumnRight {},
373 /// Focus the window above or the column to the left.
374 FocusWindowUpOrColumnLeft {},
375 /// Focus the window above or the column to the right.
376 FocusWindowUpOrColumnRight {},
377 /// Focus the window or the workspace below.
378 FocusWindowOrWorkspaceDown {},
379 /// Focus the window or the workspace above.
380 FocusWindowOrWorkspaceUp {},
381 /// Focus the topmost window.
382 FocusWindowTop {},
383 /// Focus the bottommost window.
384 FocusWindowBottom {},
385 /// Focus the window below or the topmost window.
386 FocusWindowDownOrTop {},
387 /// Focus the window above or the bottommost window.
388 FocusWindowUpOrBottom {},
389 /// Move the focused column to the left.
390 MoveColumnLeft {},
391 /// Move the focused column to the right.
392 MoveColumnRight {},
393 /// Move the focused column to the start of the workspace.
394 MoveColumnToFirst {},
395 /// Move the focused column to the end of the workspace.
396 MoveColumnToLast {},
397 /// Move the focused column to the left or to the monitor to the left.
398 MoveColumnLeftOrToMonitorLeft {},
399 /// Move the focused column to the right or to the monitor to the right.
400 MoveColumnRightOrToMonitorRight {},
401 /// Move the focused column to a specific index on its workspace.
402 MoveColumnToIndex {
403 /// New index for the column.
404 ///
405 /// The index starts from 1 for the first column.
406 #[cfg_attr(feature = "clap", arg())]
407 index: usize,
408 },
409 /// Move the focused window down in a column.
410 MoveWindowDown {},
411 /// Move the focused window up in a column.
412 MoveWindowUp {},
413 /// Move the focused window down in a column or to the workspace below.
414 MoveWindowDownOrToWorkspaceDown {},
415 /// Move the focused window up in a column or to the workspace above.
416 MoveWindowUpOrToWorkspaceUp {},
417 /// Consume or expel a window left.
418 #[cfg_attr(
419 feature = "clap",
420 clap(about = "Consume or expel the focused window left")
421 )]
422 ConsumeOrExpelWindowLeft {
423 /// Id of the window to consume or expel.
424 ///
425 /// If `None`, uses the focused window.
426 #[cfg_attr(feature = "clap", arg(long))]
427 id: Option<u64>,
428 },
429 /// Consume or expel a window right.
430 #[cfg_attr(
431 feature = "clap",
432 clap(about = "Consume or expel the focused window right")
433 )]
434 ConsumeOrExpelWindowRight {
435 /// Id of the window to consume or expel.
436 ///
437 /// If `None`, uses the focused window.
438 #[cfg_attr(feature = "clap", arg(long))]
439 id: Option<u64>,
440 },
441 /// Consume the window to the right into the focused column.
442 ConsumeWindowIntoColumn {},
443 /// Expel the bottom window from the focused column.
444 ExpelWindowFromColumn {},
445 /// Swap focused window with one to the right.
446 SwapWindowRight {},
447 /// Swap focused window with one to the left.
448 SwapWindowLeft {},
449 /// Toggle the focused column between normal and tabbed display.
450 ToggleColumnTabbedDisplay {},
451 /// Set the display mode of the focused column.
452 SetColumnDisplay {
453 /// Display mode to set.
454 #[cfg_attr(feature = "clap", arg())]
455 display: ColumnDisplay,
456 },
457 /// Center the focused column on the screen.
458 CenterColumn {},
459 /// Center a window on the screen.
460 #[cfg_attr(
461 feature = "clap",
462 clap(about = "Center the focused window on the screen")
463 )]
464 CenterWindow {
465 /// Id of the window to center.
466 ///
467 /// If `None`, uses the focused window.
468 #[cfg_attr(feature = "clap", arg(long))]
469 id: Option<u64>,
470 },
471 /// Center all fully visible columns on the screen.
472 CenterVisibleColumns {},
473 /// Focus the workspace below.
474 FocusWorkspaceDown {},
475 /// Focus the workspace above.
476 FocusWorkspaceUp {},
477 /// Focus a workspace by reference (index or name).
478 FocusWorkspace {
479 /// Reference (index or name) of the workspace to focus.
480 #[cfg_attr(feature = "clap", arg())]
481 reference: WorkspaceReferenceArg,
482 },
483 /// Focus the previous workspace.
484 FocusWorkspacePrevious {},
485 /// Move the focused window to the workspace below.
486 MoveWindowToWorkspaceDown {
487 /// Whether the focus should follow the target workspace.
488 ///
489 /// If `true` (the default), the focus will follow the window to the new workspace. If
490 /// `false`, the focus will remain on the original workspace.
491 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
492 focus: bool,
493 },
494 /// Move the focused window to the workspace above.
495 MoveWindowToWorkspaceUp {
496 /// Whether the focus should follow the target workspace.
497 ///
498 /// If `true` (the default), the focus will follow the window to the new workspace. If
499 /// `false`, the focus will remain on the original workspace.
500 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
501 focus: bool,
502 },
503 /// Move a window to a workspace.
504 #[cfg_attr(
505 feature = "clap",
506 clap(about = "Move the focused window to a workspace by reference (index or name)")
507 )]
508 MoveWindowToWorkspace {
509 /// Id of the window to move.
510 ///
511 /// If `None`, uses the focused window.
512 #[cfg_attr(feature = "clap", arg(long))]
513 window_id: Option<u64>,
514
515 /// Reference (index or name) of the workspace to move the window to.
516 #[cfg_attr(feature = "clap", arg())]
517 reference: WorkspaceReferenceArg,
518
519 /// Whether the focus should follow the moved window.
520 ///
521 /// If `true` (the default) and the window to move is focused, the focus will follow the
522 /// window to the new workspace. If `false`, the focus will remain on the original
523 /// workspace.
524 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
525 focus: bool,
526 },
527 /// Move the focused column to the workspace below.
528 MoveColumnToWorkspaceDown {
529 /// Whether the focus should follow the target workspace.
530 ///
531 /// If `true` (the default), the focus will follow the column to the new workspace. If
532 /// `false`, the focus will remain on the original workspace.
533 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
534 focus: bool,
535 },
536 /// Move the focused column to the workspace above.
537 MoveColumnToWorkspaceUp {
538 /// Whether the focus should follow the target workspace.
539 ///
540 /// If `true` (the default), the focus will follow the column to the new workspace. If
541 /// `false`, the focus will remain on the original workspace.
542 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
543 focus: bool,
544 },
545 /// Move the focused column to a workspace by reference (index or name).
546 MoveColumnToWorkspace {
547 /// Reference (index or name) of the workspace to move the column to.
548 #[cfg_attr(feature = "clap", arg())]
549 reference: WorkspaceReferenceArg,
550
551 /// Whether the focus should follow the target workspace.
552 ///
553 /// If `true` (the default), the focus will follow the column to the new workspace. If
554 /// `false`, the focus will remain on the original workspace.
555 #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))]
556 focus: bool,
557 },
558 /// Move the focused workspace down.
559 MoveWorkspaceDown {},
560 /// Move the focused workspace up.
561 MoveWorkspaceUp {},
562 /// Move a workspace to a specific index on its monitor.
563 #[cfg_attr(
564 feature = "clap",
565 clap(about = "Move the focused workspace to a specific index on its monitor")
566 )]
567 MoveWorkspaceToIndex {
568 /// New index for the workspace.
569 #[cfg_attr(feature = "clap", arg())]
570 index: usize,
571
572 /// Reference (index or name) of the workspace to move.
573 ///
574 /// If `None`, uses the focused workspace.
575 #[cfg_attr(feature = "clap", arg(long))]
576 reference: Option<WorkspaceReferenceArg>,
577 },
578 /// Set the name of a workspace.
579 #[cfg_attr(
580 feature = "clap",
581 clap(about = "Set the name of the focused workspace")
582 )]
583 SetWorkspaceName {
584 /// New name for the workspace.
585 #[cfg_attr(feature = "clap", arg())]
586 name: String,
587
588 /// Reference (index or name) of the workspace to name.
589 ///
590 /// If `None`, uses the focused workspace.
591 #[cfg_attr(feature = "clap", arg(long))]
592 workspace: Option<WorkspaceReferenceArg>,
593 },
594 /// Unset the name of a workspace.
595 #[cfg_attr(
596 feature = "clap",
597 clap(about = "Unset the name of the focused workspace")
598 )]
599 UnsetWorkspaceName {
600 /// Reference (index or name) of the workspace to unname.
601 ///
602 /// If `None`, uses the focused workspace.
603 #[cfg_attr(feature = "clap", arg())]
604 reference: Option<WorkspaceReferenceArg>,
605 },
606 /// Focus the monitor to the left.
607 FocusMonitorLeft {},
608 /// Focus the monitor to the right.
609 FocusMonitorRight {},
610 /// Focus the monitor below.
611 FocusMonitorDown {},
612 /// Focus the monitor above.
613 FocusMonitorUp {},
614 /// Focus the previous monitor.
615 FocusMonitorPrevious {},
616 /// Focus the next monitor.
617 FocusMonitorNext {},
618 /// Focus a monitor by name.
619 FocusMonitor {
620 /// Name of the output to focus.
621 #[cfg_attr(feature = "clap", arg())]
622 output: String,
623 },
624 /// Move the focused window to the monitor to the left.
625 MoveWindowToMonitorLeft {},
626 /// Move the focused window to the monitor to the right.
627 MoveWindowToMonitorRight {},
628 /// Move the focused window to the monitor below.
629 MoveWindowToMonitorDown {},
630 /// Move the focused window to the monitor above.
631 MoveWindowToMonitorUp {},
632 /// Move the focused window to the previous monitor.
633 MoveWindowToMonitorPrevious {},
634 /// Move the focused window to the next monitor.
635 MoveWindowToMonitorNext {},
636 /// Move a window to a specific monitor.
637 #[cfg_attr(
638 feature = "clap",
639 clap(about = "Move the focused window to a specific monitor")
640 )]
641 MoveWindowToMonitor {
642 /// Id of the window to move.
643 ///
644 /// If `None`, uses the focused window.
645 #[cfg_attr(feature = "clap", arg(long))]
646 id: Option<u64>,
647
648 /// The target output name.
649 #[cfg_attr(feature = "clap", arg())]
650 output: String,
651 },
652 /// Move the focused column to the monitor to the left.
653 MoveColumnToMonitorLeft {},
654 /// Move the focused column to the monitor to the right.
655 MoveColumnToMonitorRight {},
656 /// Move the focused column to the monitor below.
657 MoveColumnToMonitorDown {},
658 /// Move the focused column to the monitor above.
659 MoveColumnToMonitorUp {},
660 /// Move the focused column to the previous monitor.
661 MoveColumnToMonitorPrevious {},
662 /// Move the focused column to the next monitor.
663 MoveColumnToMonitorNext {},
664 /// Move the focused column to a specific monitor.
665 MoveColumnToMonitor {
666 /// The target output name.
667 #[cfg_attr(feature = "clap", arg())]
668 output: String,
669 },
670 /// Change the width of a window.
671 #[cfg_attr(
672 feature = "clap",
673 clap(about = "Change the width of the focused window")
674 )]
675 SetWindowWidth {
676 /// Id of the window whose width to set.
677 ///
678 /// If `None`, uses the focused window.
679 #[cfg_attr(feature = "clap", arg(long))]
680 id: Option<u64>,
681
682 /// How to change the width.
683 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
684 change: SizeChange,
685 },
686 /// Change the height of a window.
687 #[cfg_attr(
688 feature = "clap",
689 clap(about = "Change the height of the focused window")
690 )]
691 SetWindowHeight {
692 /// Id of the window whose height to set.
693 ///
694 /// If `None`, uses the focused window.
695 #[cfg_attr(feature = "clap", arg(long))]
696 id: Option<u64>,
697
698 /// How to change the height.
699 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
700 change: SizeChange,
701 },
702 /// Reset the height of a window back to automatic.
703 #[cfg_attr(
704 feature = "clap",
705 clap(about = "Reset the height of the focused window back to automatic")
706 )]
707 ResetWindowHeight {
708 /// Id of the window whose height to reset.
709 ///
710 /// If `None`, uses the focused window.
711 #[cfg_attr(feature = "clap", arg(long))]
712 id: Option<u64>,
713 },
714 /// Switch between preset column widths.
715 SwitchPresetColumnWidth {},
716 /// Switch between preset column widths backwards.
717 SwitchPresetColumnWidthBack {},
718 /// Switch between preset window widths.
719 SwitchPresetWindowWidth {
720 /// Id of the window whose width to switch.
721 ///
722 /// If `None`, uses the focused window.
723 #[cfg_attr(feature = "clap", arg(long))]
724 id: Option<u64>,
725 },
726 /// Switch between preset window widths backwards.
727 SwitchPresetWindowWidthBack {
728 /// Id of the window whose width to switch.
729 ///
730 /// If `None`, uses the focused window.
731 #[cfg_attr(feature = "clap", arg(long))]
732 id: Option<u64>,
733 },
734 /// Switch between preset window heights.
735 SwitchPresetWindowHeight {
736 /// Id of the window whose height to switch.
737 ///
738 /// If `None`, uses the focused window.
739 #[cfg_attr(feature = "clap", arg(long))]
740 id: Option<u64>,
741 },
742 /// Switch between preset window heights backwards.
743 SwitchPresetWindowHeightBack {
744 /// Id of the window whose height to switch.
745 ///
746 /// If `None`, uses the focused window.
747 #[cfg_attr(feature = "clap", arg(long))]
748 id: Option<u64>,
749 },
750 /// Toggle the maximized state of the focused column.
751 MaximizeColumn {},
752 /// Toggle the maximized-to-edges state of the focused window.
753 MaximizeWindowToEdges {
754 /// Id of the window to maximize.
755 ///
756 /// If `None`, uses the focused window.
757 #[cfg_attr(feature = "clap", arg(long))]
758 id: Option<u64>,
759 },
760 /// Change the width of the focused column.
761 SetColumnWidth {
762 /// How to change the width.
763 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
764 change: SizeChange,
765 },
766 /// Expand the focused column to space not taken up by other fully visible columns.
767 ExpandColumnToAvailableWidth {},
768 /// Switch between keyboard layouts.
769 SwitchLayout {
770 /// Layout to switch to.
771 #[cfg_attr(feature = "clap", arg())]
772 layout: LayoutSwitchTarget,
773 },
774 /// Show the hotkey overlay.
775 ShowHotkeyOverlay {},
776 /// Move the focused workspace to the monitor to the left.
777 MoveWorkspaceToMonitorLeft {},
778 /// Move the focused workspace to the monitor to the right.
779 MoveWorkspaceToMonitorRight {},
780 /// Move the focused workspace to the monitor below.
781 MoveWorkspaceToMonitorDown {},
782 /// Move the focused workspace to the monitor above.
783 MoveWorkspaceToMonitorUp {},
784 /// Move the focused workspace to the previous monitor.
785 MoveWorkspaceToMonitorPrevious {},
786 /// Move the focused workspace to the next monitor.
787 MoveWorkspaceToMonitorNext {},
788 /// Move a workspace to a specific monitor.
789 #[cfg_attr(
790 feature = "clap",
791 clap(about = "Move the focused workspace to a specific monitor")
792 )]
793 MoveWorkspaceToMonitor {
794 /// The target output name.
795 #[cfg_attr(feature = "clap", arg())]
796 output: String,
797
798 // Reference (index or name) of the workspace to move.
799 ///
800 /// If `None`, uses the focused workspace.
801 #[cfg_attr(feature = "clap", arg(long))]
802 reference: Option<WorkspaceReferenceArg>,
803 },
804 /// Toggle a debug tint on windows.
805 ToggleDebugTint {},
806 /// Toggle visualization of render element opaque regions.
807 DebugToggleOpaqueRegions {},
808 /// Toggle visualization of output damage.
809 DebugToggleDamage {},
810 /// Move the focused window between the floating and the tiling layout.
811 ToggleWindowFloating {
812 /// Id of the window to move.
813 ///
814 /// If `None`, uses the focused window.
815 #[cfg_attr(feature = "clap", arg(long))]
816 id: Option<u64>,
817 },
818 /// Move the focused window to the floating layout.
819 MoveWindowToFloating {
820 /// Id of the window to move.
821 ///
822 /// If `None`, uses the focused window.
823 #[cfg_attr(feature = "clap", arg(long))]
824 id: Option<u64>,
825 },
826 /// Move the focused window to the tiling layout.
827 MoveWindowToTiling {
828 /// Id of the window to move.
829 ///
830 /// If `None`, uses the focused window.
831 #[cfg_attr(feature = "clap", arg(long))]
832 id: Option<u64>,
833 },
834 /// Switches focus to the floating layout.
835 FocusFloating {},
836 /// Switches focus to the tiling layout.
837 FocusTiling {},
838 /// Toggles the focus between the floating and the tiling layout.
839 SwitchFocusBetweenFloatingAndTiling {},
840 /// Move a floating window on screen.
841 #[cfg_attr(feature = "clap", clap(about = "Move the floating window on screen"))]
842 MoveFloatingWindow {
843 /// Id of the window to move.
844 ///
845 /// If `None`, uses the focused window.
846 #[cfg_attr(feature = "clap", arg(long))]
847 id: Option<u64>,
848
849 /// How to change the X position.
850 #[cfg_attr(
851 feature = "clap",
852 arg(short, long, default_value = "+0", allow_hyphen_values = true)
853 )]
854 x: PositionChange,
855
856 /// How to change the Y position.
857 #[cfg_attr(
858 feature = "clap",
859 arg(short, long, default_value = "+0", allow_hyphen_values = true)
860 )]
861 y: PositionChange,
862 },
863 /// Toggle the opacity of a window.
864 #[cfg_attr(
865 feature = "clap",
866 clap(about = "Toggle the opacity of the focused window")
867 )]
868 ToggleWindowRuleOpacity {
869 /// Id of the window.
870 ///
871 /// If `None`, uses the focused window.
872 #[cfg_attr(feature = "clap", arg(long))]
873 id: Option<u64>,
874 },
875 /// Set the dynamic cast target to a window.
876 #[cfg_attr(
877 feature = "clap",
878 clap(about = "Set the dynamic cast target to the focused window")
879 )]
880 SetDynamicCastWindow {
881 /// Id of the window to target.
882 ///
883 /// If `None`, uses the focused window.
884 #[cfg_attr(feature = "clap", arg(long))]
885 id: Option<u64>,
886 },
887 /// Set the dynamic cast target to a monitor.
888 #[cfg_attr(
889 feature = "clap",
890 clap(about = "Set the dynamic cast target to the focused monitor")
891 )]
892 SetDynamicCastMonitor {
893 /// Name of the output to target.
894 ///
895 /// If `None`, uses the focused output.
896 #[cfg_attr(feature = "clap", arg())]
897 output: Option<String>,
898 },
899 /// Clear the dynamic cast target, making it show nothing.
900 ClearDynamicCastTarget {},
901 /// Stop a PipeWire screencast.
902 ///
903 /// wlr-screencopy screencasts cannot currently be stopped via IPC.
904 StopCast {
905 /// Session ID of the screencast to stop.
906 ///
907 /// If the session has multiple screencast streams, this will stop all of them.
908 #[cfg_attr(feature = "clap", arg(long))]
909 session_id: u64,
910 },
911 /// Toggle (open/close) the Overview.
912 ToggleOverview {},
913 /// Open the Overview.
914 OpenOverview {},
915 /// Close the Overview.
916 CloseOverview {},
917 /// Toggle urgent status of a window.
918 ToggleWindowUrgent {
919 /// Id of the window to toggle urgent.
920 #[cfg_attr(feature = "clap", arg(long))]
921 id: u64,
922 },
923 /// Set urgent status of a window.
924 SetWindowUrgent {
925 /// Id of the window to set urgent.
926 #[cfg_attr(feature = "clap", arg(long))]
927 id: u64,
928 },
929 /// Unset urgent status of a window.
930 UnsetWindowUrgent {
931 /// Id of the window to unset urgent.
932 #[cfg_attr(feature = "clap", arg(long))]
933 id: u64,
934 },
935 /// Reload the config file.
936 ///
937 /// Can be useful for scripts changing the config file, to avoid waiting the small duration for
938 /// niri's config file watcher to notice the changes.
939 LoadConfigFile {
940 /// Path of a new config file to load.
941 ///
942 /// If unset, reloads the current config file.
943 #[cfg_attr(feature = "clap", arg(long))]
944 path: Option<String>,
945 },
946}
947
948/// Change in window or column size.
949#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
950#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
951pub enum SizeChange {
952 /// Set the size in logical pixels.
953 SetFixed(i32),
954 /// Set the size as a proportion of the working area.
955 SetProportion(f64),
956 /// Add or subtract to the current size in logical pixels.
957 AdjustFixed(i32),
958 /// Add or subtract to the current size as a proportion of the working area.
959 AdjustProportion(f64),
960}
961
962/// Change in floating window position.
963#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
964#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
965pub enum PositionChange {
966 /// Set the position in logical pixels.
967 SetFixed(f64),
968 /// Set the position as a proportion of the working area.
969 SetProportion(f64),
970 /// Add or subtract to the current position in logical pixels.
971 AdjustFixed(f64),
972 /// Add or subtract to the current position as a proportion of the working area.
973 AdjustProportion(f64),
974}
975
976/// Workspace reference (id, index or name) to operate on.
977#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
978#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
979pub enum WorkspaceReferenceArg {
980 /// Id of the workspace.
981 Id(u64),
982 /// Index of the workspace.
983 Index(u8),
984 /// Name of the workspace.
985 Name(String),
986}
987
988/// Layout to switch to.
989#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
990#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
991pub enum LayoutSwitchTarget {
992 /// The next configured layout.
993 Next,
994 /// The previous configured layout.
995 Prev,
996 /// The specific layout by index.
997 Index(u8),
998}
999
1000/// How windows display in a column.
1001#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1002#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1003pub enum ColumnDisplay {
1004 /// Windows are tiled vertically across the working area height.
1005 Normal,
1006 /// Windows are in tabs.
1007 Tabbed,
1008}
1009
1010/// Output actions that niri can perform.
1011// Variants in this enum should match the spelling of the ones in niri-config. Most thigs from
1012// niri-config should be present here.
1013#[derive(Serialize, Deserialize, Debug, Clone)]
1014#[cfg_attr(feature = "clap", derive(clap::Parser))]
1015#[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))]
1016#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))]
1017#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1018pub enum OutputAction {
1019 /// Turn off the output.
1020 Off,
1021 /// Turn on the output.
1022 On,
1023 /// Set the output mode.
1024 Mode {
1025 /// Mode to set, or "auto" for automatic selection.
1026 ///
1027 /// Run `niri msg outputs` to see the available modes.
1028 #[cfg_attr(feature = "clap", arg())]
1029 mode: ModeToSet,
1030 },
1031 /// Set a custom output mode.
1032 CustomMode {
1033 /// Custom mode to set.
1034 #[cfg_attr(feature = "clap", arg())]
1035 mode: ConfiguredMode,
1036 },
1037 /// Set a custom VESA CVT modeline.
1038 #[cfg_attr(feature = "clap", arg())]
1039 Modeline {
1040 /// The rate at which pixels are drawn in MHz.
1041 #[cfg_attr(feature = "clap", arg())]
1042 clock: f64,
1043 /// Horizontal active pixels.
1044 #[cfg_attr(feature = "clap", arg())]
1045 hdisplay: u16,
1046 /// Horizontal sync pulse start position in pixels.
1047 #[cfg_attr(feature = "clap", arg())]
1048 hsync_start: u16,
1049 /// Horizontal sync pulse end position in pixels.
1050 #[cfg_attr(feature = "clap", arg())]
1051 hsync_end: u16,
1052 /// Total horizontal number of pixels before resetting the horizontal drawing position to
1053 /// zero.
1054 #[cfg_attr(feature = "clap", arg())]
1055 htotal: u16,
1056
1057 /// Vertical active pixels.
1058 #[cfg_attr(feature = "clap", arg())]
1059 vdisplay: u16,
1060 /// Vertical sync pulse start position in pixels.
1061 #[cfg_attr(feature = "clap", arg())]
1062 vsync_start: u16,
1063 /// Vertical sync pulse end position in pixels.
1064 #[cfg_attr(feature = "clap", arg())]
1065 vsync_end: u16,
1066 /// Total vertical number of pixels before resetting the vertical drawing position to zero.
1067 #[cfg_attr(feature = "clap", arg())]
1068 vtotal: u16,
1069 /// Horizontal sync polarity: "+hsync" or "-hsync".
1070 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
1071 hsync_polarity: HSyncPolarity,
1072 /// Vertical sync polarity: "+vsync" or "-vsync".
1073 #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))]
1074 vsync_polarity: VSyncPolarity,
1075 },
1076 /// Set the output scale.
1077 Scale {
1078 /// Scale factor to set, or "auto" for automatic selection.
1079 #[cfg_attr(feature = "clap", arg())]
1080 scale: ScaleToSet,
1081 },
1082 /// Set the output transform.
1083 Transform {
1084 /// Transform to set, counter-clockwise.
1085 #[cfg_attr(feature = "clap", arg())]
1086 transform: Transform,
1087 },
1088 /// Set the output position.
1089 Position {
1090 /// Position to set, or "auto" for automatic selection.
1091 #[cfg_attr(feature = "clap", command(subcommand))]
1092 position: PositionToSet,
1093 },
1094 /// Set the variable refresh rate mode.
1095 Vrr {
1096 /// Variable refresh rate mode to set.
1097 #[cfg_attr(feature = "clap", command(flatten))]
1098 vrr: VrrToSet,
1099 },
1100}
1101
1102/// Output mode to set.
1103#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1104#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1105pub enum ModeToSet {
1106 /// Niri will pick the mode automatically.
1107 Automatic,
1108 /// Specific mode.
1109 Specific(ConfiguredMode),
1110}
1111
1112/// Output mode as set in the config file.
1113#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1114#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1115pub struct ConfiguredMode {
1116 /// Width in physical pixels.
1117 pub width: u16,
1118 /// Height in physical pixels.
1119 pub height: u16,
1120 /// Refresh rate.
1121 pub refresh: Option<f64>,
1122}
1123
1124/// Modeline horizontal syncing polarity.
1125#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1126#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1127pub enum HSyncPolarity {
1128 /// Positive polarity.
1129 PHSync,
1130 /// Negative polarity.
1131 NHSync,
1132}
1133
1134/// Modeline vertical syncing polarity.
1135#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1136#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1137pub enum VSyncPolarity {
1138 /// Positive polarity.
1139 PVSync,
1140 /// Negative polarity.
1141 NVSync,
1142}
1143
1144/// Output scale to set.
1145#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1146#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1147pub enum ScaleToSet {
1148 /// Niri will pick the scale automatically.
1149 Automatic,
1150 /// Specific scale.
1151 Specific(f64),
1152}
1153
1154/// Output position to set.
1155#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1156#[cfg_attr(feature = "clap", derive(clap::Subcommand))]
1157#[cfg_attr(feature = "clap", command(subcommand_value_name = "POSITION"))]
1158#[cfg_attr(feature = "clap", command(subcommand_help_heading = "Position Values"))]
1159#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1160pub enum PositionToSet {
1161 /// Position the output automatically.
1162 #[cfg_attr(feature = "clap", command(name = "auto"))]
1163 Automatic,
1164 /// Set a specific position.
1165 #[cfg_attr(feature = "clap", command(name = "set"))]
1166 Specific(ConfiguredPosition),
1167}
1168
1169/// Output position as set in the config file.
1170#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1171#[cfg_attr(feature = "clap", derive(clap::Args))]
1172#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1173pub struct ConfiguredPosition {
1174 /// Logical X position.
1175 pub x: i32,
1176 /// Logical Y position.
1177 pub y: i32,
1178}
1179
1180/// Output VRR to set.
1181#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
1182#[cfg_attr(feature = "clap", derive(clap::Args))]
1183#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1184pub struct VrrToSet {
1185 /// Whether to enable variable refresh rate.
1186 #[cfg_attr(
1187 feature = "clap",
1188 arg(
1189 value_name = "ON|OFF",
1190 action = clap::ArgAction::Set,
1191 value_parser = clap::builder::BoolishValueParser::new(),
1192 hide_possible_values = true,
1193 ),
1194 )]
1195 pub vrr: bool,
1196 /// Only enable when the output shows a window matching the variable-refresh-rate window rule.
1197 #[cfg_attr(feature = "clap", arg(long))]
1198 pub on_demand: bool,
1199}
1200
1201/// Connected output.
1202#[derive(Debug, Serialize, Deserialize, Clone)]
1203#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1204pub struct Output {
1205 /// Name of the output.
1206 pub name: String,
1207 /// Textual description of the manufacturer.
1208 pub make: String,
1209 /// Textual description of the model.
1210 pub model: String,
1211 /// Serial of the output, if known.
1212 pub serial: Option<String>,
1213 /// Physical width and height of the output in millimeters, if known.
1214 pub physical_size: Option<(u32, u32)>,
1215 /// Available modes for the output.
1216 pub modes: Vec<Mode>,
1217 /// Index of the current mode in [`Self::modes`].
1218 ///
1219 /// `None` if the output is disabled.
1220 pub current_mode: Option<usize>,
1221 /// Whether the current_mode is a custom mode.
1222 pub is_custom_mode: bool,
1223 /// Whether the output supports variable refresh rate.
1224 pub vrr_supported: bool,
1225 /// Whether variable refresh rate is enabled on the output.
1226 pub vrr_enabled: bool,
1227 /// Logical output information.
1228 ///
1229 /// `None` if the output is not mapped to any logical output (for example, if it is disabled).
1230 pub logical: Option<LogicalOutput>,
1231}
1232
1233/// Output mode.
1234#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
1235#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1236pub struct Mode {
1237 /// Width in physical pixels.
1238 pub width: u16,
1239 /// Height in physical pixels.
1240 pub height: u16,
1241 /// Refresh rate in millihertz.
1242 pub refresh_rate: u32,
1243 /// Whether this mode is preferred by the monitor.
1244 pub is_preferred: bool,
1245}
1246
1247/// Logical output in the compositor's coordinate space.
1248#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
1249#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1250pub struct LogicalOutput {
1251 /// Logical X position.
1252 pub x: i32,
1253 /// Logical Y position.
1254 pub y: i32,
1255 /// Width in logical pixels.
1256 pub width: u32,
1257 /// Height in logical pixels.
1258 pub height: u32,
1259 /// Scale factor.
1260 pub scale: f64,
1261 /// Transform.
1262 pub transform: Transform,
1263}
1264
1265/// Output transform, which goes counter-clockwise.
1266#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1267#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
1268#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1269pub enum Transform {
1270 /// Untransformed.
1271 Normal,
1272 /// Rotated by 90°.
1273 #[serde(rename = "90")]
1274 _90,
1275 /// Rotated by 180°.
1276 #[serde(rename = "180")]
1277 _180,
1278 /// Rotated by 270°.
1279 #[serde(rename = "270")]
1280 _270,
1281 /// Flipped horizontally.
1282 Flipped,
1283 /// Rotated by 90° and flipped horizontally.
1284 #[cfg_attr(feature = "clap", value(name("flipped-90")))]
1285 Flipped90,
1286 /// Flipped vertically.
1287 #[cfg_attr(feature = "clap", value(name("flipped-180")))]
1288 Flipped180,
1289 /// Rotated by 270° and flipped horizontally.
1290 #[cfg_attr(feature = "clap", value(name("flipped-270")))]
1291 Flipped270,
1292}
1293
1294/// Toplevel window.
1295#[derive(Serialize, Deserialize, Debug, Clone)]
1296#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1297pub struct Window {
1298 /// Unique id of this window.
1299 ///
1300 /// This id remains constant while this window is open.
1301 ///
1302 /// Do not assume that window ids will always increase without wrapping, or start at 1. That is
1303 /// an implementation detail subject to change. For example, ids may change to be randomly
1304 /// generated for each new window.
1305 pub id: u64,
1306 /// Title, if set.
1307 pub title: Option<String>,
1308 /// Application ID, if set.
1309 pub app_id: Option<String>,
1310 /// Process ID that created the Wayland connection for this window, if known.
1311 ///
1312 /// Currently, windows created by xdg-desktop-portal-gnome will have a `None` PID, but this may
1313 /// change in the future.
1314 pub pid: Option<i32>,
1315 /// Id of the workspace this window is on, if any.
1316 pub workspace_id: Option<u64>,
1317 /// Whether this window is currently focused.
1318 ///
1319 /// There can be either one focused window or zero (e.g. when a layer-shell surface has focus).
1320 pub is_focused: bool,
1321 /// Whether this window is currently floating.
1322 ///
1323 /// If the window isn't floating then it is in the tiling layout.
1324 pub is_floating: bool,
1325 /// Whether this window requests your attention.
1326 pub is_urgent: bool,
1327 /// Position- and size-related properties of the window.
1328 pub layout: WindowLayout,
1329 /// Timestamp when the window was most recently focused.
1330 ///
1331 /// This timestamp is intended for most-recently-used window switchers, i.e. Alt-Tab. It only
1332 /// updates after some debounce time so that quick window switching doesn't mark intermediate
1333 /// windows as recently focused.
1334 ///
1335 /// The timestamp comes from the monotonic clock.
1336 pub focus_timestamp: Option<Timestamp>,
1337}
1338
1339/// A moment in time.
1340#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1341#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1342pub struct Timestamp {
1343 /// Number of whole seconds.
1344 pub secs: u64,
1345 /// Fractional part of the timestamp in nanoseconds (10<sup>-9</sup> seconds).
1346 pub nanos: u32,
1347}
1348
1349/// Position- and size-related properties of a [`Window`].
1350///
1351/// Optional properties will be unset for some windows, do not rely on them being present. Whether
1352/// some optional properties are present or absent for certain window types may change across niri
1353/// releases.
1354///
1355/// All sizes and positions are in *logical pixels* unless stated otherwise. Logical sizes may be
1356/// fractional. For example, at 1.25 monitor scale, a 2-physical-pixel-wide window border is 1.6
1357/// logical pixels wide.
1358///
1359/// This struct contains positions and sizes both for full tiles ([`Self::tile_size`],
1360/// [`Self::tile_pos_in_workspace_view`]) and the window geometry ([`Self::window_size`],
1361/// [`Self::window_offset_in_tile`]). For visual displays, use the tile properties, as they
1362/// correspond to what the user visually considers "window". The window properties on the other
1363/// hand are mainly useful when you need to know the underlying Wayland window sizes, e.g. for
1364/// application debugging.
1365#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1366#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1367pub struct WindowLayout {
1368 /// Location of a tiled window within a workspace: (column index, tile index in column).
1369 ///
1370 /// The indices are 1-based, i.e. the leftmost column is at index 1 and the topmost tile in a
1371 /// column is at index 1. This is consistent with [`Action::FocusColumn`] and
1372 /// [`Action::FocusWindowInColumn`].
1373 pub pos_in_scrolling_layout: Option<(usize, usize)>,
1374 /// Size of the tile this window is in, including decorations like borders.
1375 pub tile_size: (f64, f64),
1376 /// Size of the window's visual geometry itself.
1377 ///
1378 /// Does not include niri decorations like borders.
1379 ///
1380 /// Currently, Wayland toplevel windows can only be integer-sized in logical pixels, even
1381 /// though it doesn't necessarily align to physical pixels.
1382 pub window_size: (i32, i32),
1383 /// Tile position within the current view of the workspace.
1384 ///
1385 /// This is the same "workspace view" as in gradients' `relative-to` in the niri config.
1386 pub tile_pos_in_workspace_view: Option<(f64, f64)>,
1387 /// Location of the window's visual geometry within its tile.
1388 ///
1389 /// This includes things like border sizes. For fullscreened fixed-size windows this includes
1390 /// the distance from the corner of the black backdrop to the corner of the (centered) window
1391 /// contents.
1392 pub window_offset_in_tile: (f64, f64),
1393}
1394
1395/// Output configuration change result.
1396#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1397#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1398pub enum OutputConfigChanged {
1399 /// The target output was connected and the change was applied.
1400 Applied,
1401 /// The target output was not found, the change will be applied when it is connected.
1402 OutputWasMissing,
1403}
1404
1405/// A workspace.
1406#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1407#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1408pub struct Workspace {
1409 /// Unique id of this workspace.
1410 ///
1411 /// This id remains constant regardless of the workspace moving around and across monitors.
1412 ///
1413 /// Do not assume that workspace ids will always increase without wrapping, or start at 1. That
1414 /// is an implementation detail subject to change. For example, ids may change to be randomly
1415 /// generated for each new workspace.
1416 pub id: u64,
1417 /// Index of the workspace on its monitor.
1418 ///
1419 /// This is the same index you can use for requests like `niri msg action focus-workspace`.
1420 ///
1421 /// This index *will change* as you move and re-order workspace. It is merely the workspace's
1422 /// current position on its monitor. Workspaces on different monitors can have the same index.
1423 ///
1424 /// If you need a unique workspace id that doesn't change, see [`Self::id`].
1425 pub idx: u8,
1426 /// Optional name of the workspace.
1427 pub name: Option<String>,
1428 /// Name of the output that the workspace is on.
1429 ///
1430 /// Can be `None` if no outputs are currently connected.
1431 pub output: Option<String>,
1432 /// Whether the workspace currently has an urgent window in its output.
1433 pub is_urgent: bool,
1434 /// Whether the workspace is currently active on its output.
1435 ///
1436 /// Every output has one active workspace, the one that is currently visible on that output.
1437 pub is_active: bool,
1438 /// Whether the workspace is currently focused.
1439 ///
1440 /// There's only one focused workspace across all outputs.
1441 pub is_focused: bool,
1442 /// Id of the active window on this workspace, if any.
1443 pub active_window_id: Option<u64>,
1444}
1445
1446/// Configured keyboard layouts.
1447#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1448#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1449pub struct KeyboardLayouts {
1450 /// XKB names of the configured layouts.
1451 pub names: Vec<String>,
1452 /// Index of the currently active layout in `names`.
1453 pub current_idx: u8,
1454}
1455
1456/// A layer-shell layer.
1457#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1458#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1459pub enum Layer {
1460 /// The background layer.
1461 Background,
1462 /// The bottom layer.
1463 Bottom,
1464 /// The top layer.
1465 Top,
1466 /// The overlay layer.
1467 Overlay,
1468}
1469
1470/// Keyboard interactivity modes for a layer-shell surface.
1471#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1472#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1473pub enum LayerSurfaceKeyboardInteractivity {
1474 /// Surface cannot receive keyboard focus.
1475 None,
1476 /// Surface receives keyboard focus whenever possible.
1477 Exclusive,
1478 /// Surface receives keyboard focus on demand, e.g. when clicked.
1479 OnDemand,
1480}
1481
1482/// A layer-shell surface.
1483#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1484#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1485pub struct LayerSurface {
1486 /// Namespace provided by the layer-shell client.
1487 pub namespace: String,
1488 /// Name of the output the surface is on.
1489 pub output: String,
1490 /// Layer that the surface is on.
1491 pub layer: Layer,
1492 /// The surface's keyboard interactivity mode.
1493 pub keyboard_interactivity: LayerSurfaceKeyboardInteractivity,
1494}
1495
1496/// A screencast.
1497#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1498#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1499pub struct Cast {
1500 /// Stream ID of the screencast that uniquely identifies it.
1501 pub stream_id: u64,
1502 /// Session ID of the screencast.
1503 ///
1504 /// A session can have multiple screencast streams. Then multiple `Cast`s will have the same
1505 /// `session_id`. Though, usually there's only one stream per session.
1506 ///
1507 /// Do not confuse `session_id` with [`stream_id`](Self::stream_id).
1508 pub session_id: u64,
1509 /// Kind of this screencast.
1510 pub kind: CastKind,
1511 /// Target being captured.
1512 pub target: CastTarget,
1513 /// Whether this is a Dynamic Cast Target screencast.
1514 ///
1515 /// Meaning that actions like `SetDynamicCastWindow` will act on this screencast.
1516 ///
1517 /// Keep in mind that the target can change even if this is `false`.
1518 pub is_dynamic_target: bool,
1519 /// Whether the cast is currently streaming frames.
1520 ///
1521 /// This can be `false` for example when switching away to a different scene in OBS, which
1522 /// pauses the stream.
1523 pub is_active: bool,
1524 /// Process ID of the screencast consumer, if known.
1525 ///
1526 /// Currently, only wlr-screencopy screencasts can have a pid.
1527 pub pid: Option<i32>,
1528 /// PipeWire node ID of the screencast stream.
1529 ///
1530 /// This is `None` for wlr-screencopy casts, and also for PipeWire casts before the node is
1531 /// created (when the cast is just starting up).
1532 pub pw_node_id: Option<u32>,
1533}
1534
1535/// Kind of screencast.
1536#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
1537#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1538pub enum CastKind {
1539 /// PipeWire screencast, typically via xdg-desktop-portal-gnome.
1540 PipeWire,
1541 /// wlr-screencopy protocol screencast.
1542 ///
1543 /// Tools like wf-recorder, and the xdg-desktop-portal-wlr portal.
1544 ///
1545 /// Only wlr-screencopy with damage tracking is reported here. Screencopy without damage is
1546 /// treated as a regular screenshot and not reported as a screencast.
1547 WlrScreencopy,
1548}
1549
1550/// Target of a screencast.
1551#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
1552#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1553pub enum CastTarget {
1554 /// The target is not yet set, or was cleared.
1555 Nothing {},
1556 /// Casting an output.
1557 Output {
1558 /// Name of the screencasted output.
1559 name: String,
1560 },
1561 /// Casting a window.
1562 Window {
1563 /// ID of the screencasted window.
1564 id: u64,
1565 },
1566}
1567
1568/// A compositor event.
1569#[derive(Serialize, Deserialize, Debug, Clone)]
1570#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
1571pub enum Event {
1572 /// The workspace configuration has changed.
1573 WorkspacesChanged {
1574 /// The new workspace configuration.
1575 ///
1576 /// This configuration completely replaces the previous configuration. I.e. if any
1577 /// workspaces are missing from here, then they were deleted.
1578 workspaces: Vec<Workspace>,
1579 },
1580 /// The workspace urgency changed.
1581 WorkspaceUrgencyChanged {
1582 /// Id of the workspace.
1583 id: u64,
1584 /// Whether this workspace has an urgent window.
1585 urgent: bool,
1586 },
1587 /// A workspace was activated on an output.
1588 ///
1589 /// This doesn't always mean the workspace became focused, just that it's now the active
1590 /// workspace on its output. All other workspaces on the same output become inactive.
1591 WorkspaceActivated {
1592 /// Id of the newly active workspace.
1593 id: u64,
1594 /// Whether this workspace also became focused.
1595 ///
1596 /// If `true`, this is now the single focused workspace. All other workspaces are no longer
1597 /// focused, but they may remain active on their respective outputs.
1598 focused: bool,
1599 },
1600 /// An active window changed on a workspace.
1601 WorkspaceActiveWindowChanged {
1602 /// Id of the workspace on which the active window changed.
1603 workspace_id: u64,
1604 /// Id of the new active window, if any.
1605 active_window_id: Option<u64>,
1606 },
1607 /// The window configuration has changed.
1608 WindowsChanged {
1609 /// The new window configuration.
1610 ///
1611 /// This configuration completely replaces the previous configuration. I.e. if any windows
1612 /// are missing from here, then they were closed.
1613 windows: Vec<Window>,
1614 },
1615 /// A new toplevel window was opened, or an existing toplevel window changed.
1616 WindowOpenedOrChanged {
1617 /// The new or updated window.
1618 ///
1619 /// If the window is focused, all other windows are no longer focused.
1620 window: Window,
1621 },
1622 /// A toplevel window was closed.
1623 WindowClosed {
1624 /// Id of the removed window.
1625 id: u64,
1626 },
1627 /// Window focus changed.
1628 ///
1629 /// All other windows are no longer focused.
1630 WindowFocusChanged {
1631 /// Id of the newly focused window, or `None` if no window is now focused.
1632 id: Option<u64>,
1633 },
1634 /// Window focus timestamp changed.
1635 ///
1636 /// This event is separate from [`Event::WindowFocusChanged`] because the focus timestamp only
1637 /// updates after some debounce time so that quick window switching doesn't mark intermediate
1638 /// windows as recently focused.
1639 WindowFocusTimestampChanged {
1640 /// Id of the window.
1641 id: u64,
1642 /// The new focus timestamp.
1643 focus_timestamp: Option<Timestamp>,
1644 },
1645 /// Window urgency changed.
1646 WindowUrgencyChanged {
1647 /// Id of the window.
1648 id: u64,
1649 /// The new urgency state of the window.
1650 urgent: bool,
1651 },
1652 /// The layout of one or more windows has changed.
1653 WindowLayoutsChanged {
1654 /// Pairs consisting of a window id and new layout information for the window.
1655 changes: Vec<(u64, WindowLayout)>,
1656 },
1657 /// The configured keyboard layouts have changed.
1658 KeyboardLayoutsChanged {
1659 /// The new keyboard layout configuration.
1660 keyboard_layouts: KeyboardLayouts,
1661 },
1662 /// The keyboard layout switched.
1663 KeyboardLayoutSwitched {
1664 /// Index of the newly active layout.
1665 idx: u8,
1666 },
1667 /// The overview was opened or closed.
1668 OverviewOpenedOrClosed {
1669 /// The new state of the overview.
1670 is_open: bool,
1671 },
1672 /// The configuration was reloaded.
1673 ///
1674 /// You will always receive this event when connecting to the event stream, indicating the last
1675 /// config load attempt.
1676 ConfigLoaded {
1677 /// Whether the loading failed.
1678 ///
1679 /// For example, the config file couldn't be parsed.
1680 failed: bool,
1681 },
1682 /// A screenshot was captured.
1683 ScreenshotCaptured {
1684 /// The file path where the screenshot was saved, if it was written to disk.
1685 ///
1686 /// If `None`, the screenshot was either only copied to the clipboard, or the path couldn't
1687 /// be converted to a `String` (e.g. contained invalid UTF-8 bytes).
1688 path: Option<String>,
1689 },
1690 /// The screencasts have changed.
1691 CastsChanged {
1692 /// The new screencast information.
1693 ///
1694 /// This configuration completely replaces the previous configuration. I.e. if any casts
1695 /// are missing from here, then they were stopped.
1696 casts: Vec<Cast>,
1697 },
1698 /// A screencast started, or an existing cast changed.
1699 CastStartedOrChanged {
1700 /// The cast that started or changed.
1701 cast: Cast,
1702 },
1703 /// A screencast stopped.
1704 CastStopped {
1705 /// Stream ID of the stopped screencast.
1706 stream_id: u64,
1707 },
1708}
1709
1710impl From<Duration> for Timestamp {
1711 fn from(value: Duration) -> Self {
1712 Timestamp {
1713 secs: value.as_secs(),
1714 nanos: value.subsec_nanos(),
1715 }
1716 }
1717}
1718
1719impl From<Timestamp> for Duration {
1720 fn from(value: Timestamp) -> Self {
1721 Duration::new(value.secs, value.nanos)
1722 }
1723}
1724
1725impl FromStr for WorkspaceReferenceArg {
1726 type Err = &'static str;
1727
1728 fn from_str(s: &str) -> Result<Self, Self::Err> {
1729 let reference = if let Ok(index) = s.parse::<i32>() {
1730 if let Ok(idx) = u8::try_from(index) {
1731 Self::Index(idx)
1732 } else {
1733 return Err("workspace index must be between 0 and 255");
1734 }
1735 } else {
1736 Self::Name(s.to_string())
1737 };
1738
1739 Ok(reference)
1740 }
1741}
1742
1743impl FromStr for SizeChange {
1744 type Err = &'static str;
1745
1746 fn from_str(s: &str) -> Result<Self, Self::Err> {
1747 match s.split_once('%') {
1748 Some((value, empty)) => {
1749 if !empty.is_empty() {
1750 return Err("trailing characters after '%' are not allowed");
1751 }
1752
1753 match value.bytes().next() {
1754 Some(b'-' | b'+') => {
1755 let value = value.parse().map_err(|_| "error parsing value")?;
1756 Ok(Self::AdjustProportion(value))
1757 }
1758 Some(_) => {
1759 let value = value.parse().map_err(|_| "error parsing value")?;
1760 Ok(Self::SetProportion(value))
1761 }
1762 None => Err("value is missing"),
1763 }
1764 }
1765 None => {
1766 let value = s;
1767 match value.bytes().next() {
1768 Some(b'-' | b'+') => {
1769 let value = value.parse().map_err(|_| "error parsing value")?;
1770 Ok(Self::AdjustFixed(value))
1771 }
1772 Some(_) => {
1773 let value = value.parse().map_err(|_| "error parsing value")?;
1774 Ok(Self::SetFixed(value))
1775 }
1776 None => Err("value is missing"),
1777 }
1778 }
1779 }
1780 }
1781}
1782
1783impl FromStr for PositionChange {
1784 type Err = &'static str;
1785
1786 fn from_str(s: &str) -> Result<Self, Self::Err> {
1787 match s.split_once('%') {
1788 Some((value, empty)) => {
1789 if !empty.is_empty() {
1790 return Err("trailing characters after '%' are not allowed");
1791 }
1792
1793 match value.bytes().next() {
1794 Some(b'-' | b'+') => {
1795 let value = value.parse().map_err(|_| "error parsing value")?;
1796 Ok(Self::AdjustProportion(value))
1797 }
1798 Some(_) => {
1799 let value = value.parse().map_err(|_| "error parsing value")?;
1800 Ok(Self::SetProportion(value))
1801 }
1802 None => Err("value is missing"),
1803 }
1804 }
1805 None => {
1806 let value = s;
1807 match value.bytes().next() {
1808 Some(b'-' | b'+') => {
1809 let value = value.parse().map_err(|_| "error parsing value")?;
1810 Ok(Self::AdjustFixed(value))
1811 }
1812 Some(_) => {
1813 let value = value.parse().map_err(|_| "error parsing value")?;
1814 Ok(Self::SetFixed(value))
1815 }
1816 None => Err("value is missing"),
1817 }
1818 }
1819 }
1820 }
1821}
1822
1823impl FromStr for LayoutSwitchTarget {
1824 type Err = &'static str;
1825
1826 fn from_str(s: &str) -> Result<Self, Self::Err> {
1827 match s {
1828 "next" => Ok(Self::Next),
1829 "prev" => Ok(Self::Prev),
1830 other => match other.parse() {
1831 Ok(layout) => Ok(Self::Index(layout)),
1832 _ => Err(r#"invalid layout action, can be "next", "prev" or a layout index"#),
1833 },
1834 }
1835 }
1836}
1837
1838impl FromStr for ColumnDisplay {
1839 type Err = &'static str;
1840
1841 fn from_str(s: &str) -> Result<Self, Self::Err> {
1842 match s {
1843 "normal" => Ok(Self::Normal),
1844 "tabbed" => Ok(Self::Tabbed),
1845 _ => Err(r#"invalid column display, can be "normal" or "tabbed""#),
1846 }
1847 }
1848}
1849
1850impl FromStr for Transform {
1851 type Err = &'static str;
1852
1853 fn from_str(s: &str) -> Result<Self, Self::Err> {
1854 match s {
1855 "normal" => Ok(Self::Normal),
1856 "90" => Ok(Self::_90),
1857 "180" => Ok(Self::_180),
1858 "270" => Ok(Self::_270),
1859 "flipped" => Ok(Self::Flipped),
1860 "flipped-90" => Ok(Self::Flipped90),
1861 "flipped-180" => Ok(Self::Flipped180),
1862 "flipped-270" => Ok(Self::Flipped270),
1863 _ => Err(concat!(
1864 r#"invalid transform, can be "90", "180", "270", "#,
1865 r#""flipped", "flipped-90", "flipped-180" or "flipped-270""#
1866 )),
1867 }
1868 }
1869}
1870
1871impl FromStr for ModeToSet {
1872 type Err = &'static str;
1873
1874 fn from_str(s: &str) -> Result<Self, Self::Err> {
1875 if s.eq_ignore_ascii_case("auto") {
1876 return Ok(Self::Automatic);
1877 }
1878
1879 let mode = s.parse()?;
1880 Ok(Self::Specific(mode))
1881 }
1882}
1883
1884impl FromStr for ConfiguredMode {
1885 type Err = &'static str;
1886
1887 fn from_str(s: &str) -> Result<Self, Self::Err> {
1888 let Some((width, rest)) = s.split_once('x') else {
1889 return Err("no 'x' separator found");
1890 };
1891
1892 let (height, refresh) = match rest.split_once('@') {
1893 Some((height, refresh)) => (height, Some(refresh)),
1894 None => (rest, None),
1895 };
1896
1897 let width = width.parse().map_err(|_| "error parsing width")?;
1898 let height = height.parse().map_err(|_| "error parsing height")?;
1899 let refresh = refresh
1900 .map(str::parse)
1901 .transpose()
1902 .map_err(|_| "error parsing refresh rate")?;
1903
1904 Ok(Self {
1905 width,
1906 height,
1907 refresh,
1908 })
1909 }
1910}
1911
1912impl FromStr for HSyncPolarity {
1913 type Err = &'static str;
1914
1915 fn from_str(s: &str) -> Result<Self, Self::Err> {
1916 match s {
1917 "+hsync" => Ok(Self::PHSync),
1918 "-hsync" => Ok(Self::NHSync),
1919 _ => Err(r#"invalid horizontal sync polarity, can be "+hsync" or "-hsync"#),
1920 }
1921 }
1922}
1923
1924impl FromStr for VSyncPolarity {
1925 type Err = &'static str;
1926
1927 fn from_str(s: &str) -> Result<Self, Self::Err> {
1928 match s {
1929 "+vsync" => Ok(Self::PVSync),
1930 "-vsync" => Ok(Self::NVSync),
1931 _ => Err(r#"invalid vertical sync polarity, can be "+vsync" or "-vsync"#),
1932 }
1933 }
1934}
1935
1936impl FromStr for ScaleToSet {
1937 type Err = &'static str;
1938
1939 fn from_str(s: &str) -> Result<Self, Self::Err> {
1940 if s.eq_ignore_ascii_case("auto") {
1941 return Ok(Self::Automatic);
1942 }
1943
1944 let scale = s.parse().map_err(|_| "error parsing scale")?;
1945 Ok(Self::Specific(scale))
1946 }
1947}
1948
1949macro_rules! ensure {
1950 ($cond:expr, $fmt:literal $($arg:tt)* ) => {
1951 if !$cond {
1952 return Err(format!($fmt $($arg)*));
1953 }
1954 };
1955}
1956
1957impl OutputAction {
1958 /// Validates some required constraints on the modeline and custom mode.
1959 pub fn validate(&self) -> Result<(), String> {
1960 match self {
1961 OutputAction::Modeline {
1962 hdisplay,
1963 hsync_start,
1964 hsync_end,
1965 htotal,
1966 vdisplay,
1967 vsync_start,
1968 vsync_end,
1969 vtotal,
1970 ..
1971 } => {
1972 ensure!(
1973 hdisplay < hsync_start,
1974 "hdisplay {} must be < hsync_start {}",
1975 hdisplay,
1976 hsync_start
1977 );
1978 ensure!(
1979 hsync_start < hsync_end,
1980 "hsync_start {} must be < hsync_end {}",
1981 hsync_start,
1982 hsync_end
1983 );
1984 ensure!(
1985 hsync_end < htotal,
1986 "hsync_end {} must be < htotal {}",
1987 hsync_end,
1988 htotal
1989 );
1990 ensure!(0 < *htotal, "htotal {} must be > 0", htotal);
1991 ensure!(
1992 vdisplay < vsync_start,
1993 "vdisplay {} must be < vsync_start {}",
1994 vdisplay,
1995 vsync_start
1996 );
1997 ensure!(
1998 vsync_start < vsync_end,
1999 "vsync_start {} must be < vsync_end {}",
2000 vsync_start,
2001 vsync_end
2002 );
2003 ensure!(
2004 vsync_end < vtotal,
2005 "vsync_end {} must be < vtotal {}",
2006 vsync_end,
2007 vtotal
2008 );
2009 ensure!(0 < *vtotal, "vtotal {} must be > 0", vtotal);
2010 Ok(())
2011 }
2012 OutputAction::CustomMode {
2013 mode: ConfiguredMode { refresh, .. },
2014 } => {
2015 if refresh.is_none() {
2016 return Err("refresh rate is required for custom modes".to_string());
2017 }
2018 if let Some(refresh) = refresh {
2019 if *refresh <= 0. {
2020 return Err(format!("custom mode refresh rate {refresh} must be > 0"));
2021 }
2022 }
2023 Ok(())
2024 }
2025 _ => Ok(()),
2026 }
2027 }
2028}
2029
2030#[cfg(test)]
2031mod tests {
2032 use super::*;
2033
2034 #[test]
2035 fn parse_size_change() {
2036 assert_eq!(
2037 "10".parse::<SizeChange>().unwrap(),
2038 SizeChange::SetFixed(10),
2039 );
2040 assert_eq!(
2041 "+10".parse::<SizeChange>().unwrap(),
2042 SizeChange::AdjustFixed(10),
2043 );
2044 assert_eq!(
2045 "-10".parse::<SizeChange>().unwrap(),
2046 SizeChange::AdjustFixed(-10),
2047 );
2048 assert_eq!(
2049 "10%".parse::<SizeChange>().unwrap(),
2050 SizeChange::SetProportion(10.),
2051 );
2052 assert_eq!(
2053 "+10%".parse::<SizeChange>().unwrap(),
2054 SizeChange::AdjustProportion(10.),
2055 );
2056 assert_eq!(
2057 "-10%".parse::<SizeChange>().unwrap(),
2058 SizeChange::AdjustProportion(-10.),
2059 );
2060
2061 assert!("-".parse::<SizeChange>().is_err());
2062 assert!("10% ".parse::<SizeChange>().is_err());
2063 }
2064
2065 #[test]
2066 fn parse_position_change() {
2067 assert_eq!(
2068 "10".parse::<PositionChange>().unwrap(),
2069 PositionChange::SetFixed(10.),
2070 );
2071 assert_eq!(
2072 "+10".parse::<PositionChange>().unwrap(),
2073 PositionChange::AdjustFixed(10.),
2074 );
2075 assert_eq!(
2076 "-10".parse::<PositionChange>().unwrap(),
2077 PositionChange::AdjustFixed(-10.),
2078 );
2079
2080 assert_eq!(
2081 "10%".parse::<PositionChange>().unwrap(),
2082 PositionChange::SetProportion(10.)
2083 );
2084 assert_eq!(
2085 "+10%".parse::<PositionChange>().unwrap(),
2086 PositionChange::AdjustProportion(10.)
2087 );
2088 assert_eq!(
2089 "-10%".parse::<PositionChange>().unwrap(),
2090 PositionChange::AdjustProportion(-10.)
2091 );
2092 assert!("-".parse::<PositionChange>().is_err());
2093 assert!("10% ".parse::<PositionChange>().is_err());
2094 }
2095}