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}