niri-ipc-25.8.0/.cargo_vcs_info.json0000644000000001460000000000100126430ustar { "git": { "sha1": "db419b4fc7dbfb32a5c954502839c2331bcb4ecc" }, "path_in_vcs": "niri-ipc" }niri-ipc-25.8.0/Cargo.lock0000644000000231200000000000100106130ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys", ] [[package]] name = "clap" version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "niri-ipc" version = "25.8.0" dependencies = [ "clap", "schemars", "serde", "serde_json", ] [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "ref-cast" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schemars" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", "schemars_derive", "serde", "serde_json", ] [[package]] name = "schemars_derive" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", "syn", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_derive_internals" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" niri-ipc-25.8.0/Cargo.toml0000644000000024170000000000100106440ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "niri-ipc" version = "25.8.0" authors = ["Ivan Molodetskikh "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Types and helpers for interfacing with the niri Wayland compositor." readme = "README.md" keywords = ["wayland"] categories = [ "api-bindings", "os", ] license = "GPL-3.0-or-later" repository = "https://github.com/YaLTeR/niri" [features] clap = ["dep:clap"] json-schema = ["dep:schemars"] [lib] name = "niri_ipc" path = "src/lib.rs" [dependencies.clap] version = "4.5.46" features = ["derive"] optional = true [dependencies.schemars] version = "1.0.4" optional = true [dependencies.serde] version = "1.0.219" features = ["derive"] [dependencies.serde_json] version = "1.0.143" niri-ipc-25.8.0/Cargo.toml.orig000064400000000000000000000010421046102023000143160ustar 00000000000000[package] name = "niri-ipc" version.workspace = true authors.workspace = true license.workspace = true edition.workspace = true repository.workspace = true description = "Types and helpers for interfacing with the niri Wayland compositor." keywords = ["wayland"] categories = ["api-bindings", "os"] readme = "README.md" [dependencies] clap = { workspace = true, optional = true } schemars = { version = "1.0.4", optional = true } serde.workspace = true serde_json.workspace = true [features] clap = ["dep:clap"] json-schema = ["dep:schemars"] niri-ipc-25.8.0/README.md000064400000000000000000000006661046102023000127210ustar 00000000000000# niri-ipc Types and helpers for interfacing with the [niri](https://github.com/YaLTeR/niri) Wayland compositor. ## Backwards compatibility This crate follows the niri version. It is **not** API-stable in terms of the Rust semver. In particular, expect new struct fields and enum variants to be added in patch version bumps. Use an exact version requirement to avoid breaking changes: ```toml [dependencies] niri-ipc = "=25.8.0" ``` niri-ipc-25.8.0/src/lib.rs000064400000000000000000001676341046102023000133560ustar 00000000000000//! Types for communicating with niri via IPC. //! //! After connecting to the niri socket, you can send [`Request`]s. Niri will process them one by //! one, in order, and to each request it will respond with a single [`Reply`], which is a `Result` //! wrapping a [`Response`]. //! //! If you send a [`Request::EventStream`], niri will *stop* reading subsequent [`Request`]s, and //! will start continuously writing compositor [`Event`]s to the socket. If you'd like to read an //! event stream and write more requests at the same time, you need to use two IPC sockets. //! //!
//! //! Requests are *always* processed separately. Time passes between requests, even when sending //! multiple requests to the socket at once. For example, sending [`Request::Workspaces`] and //! [`Request::Windows`] together may not return consistent results (e.g. a window may open on a //! new workspace in-between the two responses). This goes for actions too: sending //! [`Action::FocusWindow`] and [Action::CloseWindow] { id: None } together may close //! the wrong window because a different window got focused in-between these requests. //! //!
//! //! You can use the [`socket::Socket`] helper if you're fine with blocking communication. However, //! it is a fairly simple helper, so if you need async, or if you're using a different language, //! you are encouraged to communicate with the socket manually. //! //! 1. Read the socket filesystem path from [`socket::SOCKET_PATH_ENV`] (`$NIRI_SOCKET`). //! 2. Connect to the socket and write a JSON-formatted [`Request`] on a single line. You can follow //! up with a line break and a flush, or just flush and shutdown the write end of the socket. //! 3. Niri will respond with a single line JSON-formatted [`Reply`]. //! 4. You can keep writing [`Request`]s, each on a single line, and read [`Reply`]s, also each on a //! separate line. //! 5. After you request an event stream, niri will keep responding with JSON-formatted [`Event`]s, //! on a single line each. //! //! ## Backwards compatibility //! //! This crate follows the niri version. It is **not** API-stable in terms of the Rust semver. In //! particular, expect new struct fields and enum variants to be added in patch version bumps. //! //! Use an exact version requirement to avoid breaking changes: //! //! ```toml //! [dependencies] //! niri-ipc = "=25.8.0" //! ``` //! //! ## Features //! //! This crate defines the following features: //! - `json-schema`: derives the [schemars](https://lib.rs/crates/schemars) `JsonSchema` trait for //! the types. //! - `clap`: derives the clap CLI parsing traits for some types. Used internally by niri itself. #![warn(missing_docs)] use std::collections::HashMap; use std::str::FromStr; use serde::{Deserialize, Serialize}; pub mod socket; pub mod state; /// Request from client to niri. #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Request { /// Request the version string for the running niri instance. Version, /// Request information about connected outputs. Outputs, /// Request information about workspaces. Workspaces, /// Request information about open windows. Windows, /// Request information about layer-shell surfaces. Layers, /// Request information about the configured keyboard layouts. KeyboardLayouts, /// Request information about the focused output. FocusedOutput, /// Request information about the focused window. FocusedWindow, /// Request picking a window and get its information. PickWindow, /// Request picking a color from the screen. PickColor, /// Perform an action. Action(Action), /// Change output configuration temporarily. /// /// The configuration is changed temporarily and not saved into the config file. If the output /// configuration subsequently changes in the config file, these temporary changes will be /// forgotten. Output { /// Output name. output: String, /// Configuration to apply. action: OutputAction, }, /// Start continuously receiving events from the compositor. /// /// The compositor should reply with `Reply::Ok(Response::Handled)`, then continuously send /// [`Event`]s, one per line. /// /// The event stream will always give you the full current state up-front. For example, the /// first workspace-related event you will receive will be [`Event::WorkspacesChanged`] /// containing the full current workspaces state. You *do not* need to separately send /// [`Request::Workspaces`] when using the event stream. /// /// Where reasonable, event stream state updates are atomic, though this is not always the /// case. For example, a window may end up with a workspace id for a workspace that had already /// been removed. This can happen if the corresponding [`Event::WorkspacesChanged`] arrives /// before the corresponding [`Event::WindowOpenedOrChanged`]. EventStream, /// Respond with an error (for testing error handling). ReturnError, /// Request information about the overview. OverviewState, } /// Reply from niri to client. /// /// Every request gets one reply. /// /// * If an error had occurred, it will be an `Reply::Err`. /// * If the request does not need any particular response, it will be /// `Reply::Ok(Response::Handled)`. Kind of like an `Ok(())`. /// * Otherwise, it will be `Reply::Ok(response)` with one of the other [`Response`] variants. pub type Reply = Result; /// Successful response from niri to client. #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Response { /// A request that does not need a response was handled successfully. Handled, /// The version string for the running niri instance. Version(String), /// Information about connected outputs. /// /// Map from output name to output info. Outputs(HashMap), /// Information about workspaces. Workspaces(Vec), /// Information about open windows. Windows(Vec), /// Information about layer-shell surfaces. Layers(Vec), /// Information about the keyboard layout. KeyboardLayouts(KeyboardLayouts), /// Information about the focused output. FocusedOutput(Option), /// Information about the focused window. FocusedWindow(Option), /// Information about the picked window. PickedWindow(Option), /// Information about the picked color. PickedColor(Option), /// Output configuration change result. OutputConfigChanged(OutputConfigChanged), /// Information about the overview. OverviewState(Overview), } /// Overview information. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Overview { /// Whether the overview is currently open. pub is_open: bool, } /// Color picked from the screen. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct PickedColor { /// Color values as red, green, blue, each ranging from 0.0 to 1.0. pub rgb: [f64; 3], } /// Actions that niri can perform. // Variants in this enum should match the spelling of the ones in niri-config. Most, but not all, // variants from niri-config should be present here. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Parser))] #[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))] #[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Action { /// Exit niri. Quit { /// Skip the "Press Enter to confirm" prompt. #[cfg_attr(feature = "clap", arg(short, long))] skip_confirmation: bool, }, /// Power off all monitors via DPMS. PowerOffMonitors {}, /// Power on all monitors via DPMS. PowerOnMonitors {}, /// Spawn a command. Spawn { /// Command to spawn. #[cfg_attr(feature = "clap", arg(last = true, required = true))] command: Vec, }, /// Spawn a command through the shell. SpawnSh { /// Command to run. #[cfg_attr(feature = "clap", arg(last = true, required = true))] command: String, }, /// Do a screen transition. DoScreenTransition { /// Delay in milliseconds for the screen to freeze before starting the transition. #[cfg_attr(feature = "clap", arg(short, long))] delay_ms: Option, }, /// Open the screenshot UI. Screenshot { /// Whether to show the mouse pointer by default in the screenshot UI. #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))] show_pointer: bool, }, /// Screenshot the focused screen. ScreenshotScreen { /// Write the screenshot to disk in addition to putting it in your clipboard. /// /// The screenshot is saved according to the `screenshot-path` config setting. #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))] write_to_disk: bool, /// Whether to include the mouse pointer in the screenshot. #[cfg_attr(feature = "clap", arg(short = 'p', long, action = clap::ArgAction::Set, default_value_t = true))] show_pointer: bool, }, /// Screenshot a window. #[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))] ScreenshotWindow { /// Id of the window to screenshot. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, /// Write the screenshot to disk in addition to putting it in your clipboard. /// /// The screenshot is saved according to the `screenshot-path` config setting. #[cfg_attr(feature = "clap", arg(short = 'd', long, action = clap::ArgAction::Set, default_value_t = true))] write_to_disk: bool, }, /// Enable or disable the keyboard shortcuts inhibitor (if any) for the focused surface. ToggleKeyboardShortcutsInhibit {}, /// Close a window. #[cfg_attr(feature = "clap", clap(about = "Close the focused window"))] CloseWindow { /// Id of the window to close. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Toggle fullscreen on a window. #[cfg_attr( feature = "clap", clap(about = "Toggle fullscreen on the focused window") )] FullscreenWindow { /// Id of the window to toggle fullscreen of. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Toggle windowed (fake) fullscreen on a window. #[cfg_attr( feature = "clap", clap(about = "Toggle windowed (fake) fullscreen on the focused window") )] ToggleWindowedFullscreen { /// Id of the window to toggle windowed fullscreen of. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Focus a window by id. FocusWindow { /// Id of the window to focus. #[cfg_attr(feature = "clap", arg(long))] id: u64, }, /// Focus a window in the focused column by index. FocusWindowInColumn { /// Index of the window in the column. /// /// The index starts from 1 for the topmost window. #[cfg_attr(feature = "clap", arg())] index: u8, }, /// Focus the previously focused window. FocusWindowPrevious {}, /// Focus the column to the left. FocusColumnLeft {}, /// Focus the column to the right. FocusColumnRight {}, /// Focus the first column. FocusColumnFirst {}, /// Focus the last column. FocusColumnLast {}, /// Focus the next column to the right, looping if at end. FocusColumnRightOrFirst {}, /// Focus the next column to the left, looping if at start. FocusColumnLeftOrLast {}, /// Focus a column by index. FocusColumn { /// Index of the column to focus. /// /// The index starts from 1 for the first column. #[cfg_attr(feature = "clap", arg())] index: usize, }, /// Focus the window or the monitor above. FocusWindowOrMonitorUp {}, /// Focus the window or the monitor below. FocusWindowOrMonitorDown {}, /// Focus the column or the monitor to the left. FocusColumnOrMonitorLeft {}, /// Focus the column or the monitor to the right. FocusColumnOrMonitorRight {}, /// Focus the window below. FocusWindowDown {}, /// Focus the window above. FocusWindowUp {}, /// Focus the window below or the column to the left. FocusWindowDownOrColumnLeft {}, /// Focus the window below or the column to the right. FocusWindowDownOrColumnRight {}, /// Focus the window above or the column to the left. FocusWindowUpOrColumnLeft {}, /// Focus the window above or the column to the right. FocusWindowUpOrColumnRight {}, /// Focus the window or the workspace below. FocusWindowOrWorkspaceDown {}, /// Focus the window or the workspace above. FocusWindowOrWorkspaceUp {}, /// Focus the topmost window. FocusWindowTop {}, /// Focus the bottommost window. FocusWindowBottom {}, /// Focus the window below or the topmost window. FocusWindowDownOrTop {}, /// Focus the window above or the bottommost window. FocusWindowUpOrBottom {}, /// Move the focused column to the left. MoveColumnLeft {}, /// Move the focused column to the right. MoveColumnRight {}, /// Move the focused column to the start of the workspace. MoveColumnToFirst {}, /// Move the focused column to the end of the workspace. MoveColumnToLast {}, /// Move the focused column to the left or to the monitor to the left. MoveColumnLeftOrToMonitorLeft {}, /// Move the focused column to the right or to the monitor to the right. MoveColumnRightOrToMonitorRight {}, /// Move the focused column to a specific index on its workspace. MoveColumnToIndex { /// New index for the column. /// /// The index starts from 1 for the first column. #[cfg_attr(feature = "clap", arg())] index: usize, }, /// Move the focused window down in a column. MoveWindowDown {}, /// Move the focused window up in a column. MoveWindowUp {}, /// Move the focused window down in a column or to the workspace below. MoveWindowDownOrToWorkspaceDown {}, /// Move the focused window up in a column or to the workspace above. MoveWindowUpOrToWorkspaceUp {}, /// Consume or expel a window left. #[cfg_attr( feature = "clap", clap(about = "Consume or expel the focused window left") )] ConsumeOrExpelWindowLeft { /// Id of the window to consume or expel. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Consume or expel a window right. #[cfg_attr( feature = "clap", clap(about = "Consume or expel the focused window right") )] ConsumeOrExpelWindowRight { /// Id of the window to consume or expel. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Consume the window to the right into the focused column. ConsumeWindowIntoColumn {}, /// Expel the focused window from the column. ExpelWindowFromColumn {}, /// Swap focused window with one to the right. SwapWindowRight {}, /// Swap focused window with one to the left. SwapWindowLeft {}, /// Toggle the focused column between normal and tabbed display. ToggleColumnTabbedDisplay {}, /// Set the display mode of the focused column. SetColumnDisplay { /// Display mode to set. #[cfg_attr(feature = "clap", arg())] display: ColumnDisplay, }, /// Center the focused column on the screen. CenterColumn {}, /// Center a window on the screen. #[cfg_attr( feature = "clap", clap(about = "Center the focused window on the screen") )] CenterWindow { /// Id of the window to center. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Center all fully visible columns on the screen. CenterVisibleColumns {}, /// Focus the workspace below. FocusWorkspaceDown {}, /// Focus the workspace above. FocusWorkspaceUp {}, /// Focus a workspace by reference (index or name). FocusWorkspace { /// Reference (index or name) of the workspace to focus. #[cfg_attr(feature = "clap", arg())] reference: WorkspaceReferenceArg, }, /// Focus the previous workspace. FocusWorkspacePrevious {}, /// Move the focused window to the workspace below. MoveWindowToWorkspaceDown { /// Whether the focus should follow the target workspace. /// /// If `true` (the default), the focus will follow the window to the new workspace. If /// `false`, the focus will remain on the original workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move the focused window to the workspace above. MoveWindowToWorkspaceUp { /// Whether the focus should follow the target workspace. /// /// If `true` (the default), the focus will follow the window to the new workspace. If /// `false`, the focus will remain on the original workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move a window to a workspace. #[cfg_attr( feature = "clap", clap(about = "Move the focused window to a workspace by reference (index or name)") )] MoveWindowToWorkspace { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] window_id: Option, /// Reference (index or name) of the workspace to move the window to. #[cfg_attr(feature = "clap", arg())] reference: WorkspaceReferenceArg, /// Whether the focus should follow the moved window. /// /// If `true` (the default) and the window to move is focused, the focus will follow the /// window to the new workspace. If `false`, the focus will remain on the original /// workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move the focused column to the workspace below. MoveColumnToWorkspaceDown { /// Whether the focus should follow the target workspace. /// /// If `true` (the default), the focus will follow the column to the new workspace. If /// `false`, the focus will remain on the original workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move the focused column to the workspace above. MoveColumnToWorkspaceUp { /// Whether the focus should follow the target workspace. /// /// If `true` (the default), the focus will follow the column to the new workspace. If /// `false`, the focus will remain on the original workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move the focused column to a workspace by reference (index or name). MoveColumnToWorkspace { /// Reference (index or name) of the workspace to move the column to. #[cfg_attr(feature = "clap", arg())] reference: WorkspaceReferenceArg, /// Whether the focus should follow the target workspace. /// /// If `true` (the default), the focus will follow the column to the new workspace. If /// `false`, the focus will remain on the original workspace. #[cfg_attr(feature = "clap", arg(long, action = clap::ArgAction::Set, default_value_t = true))] focus: bool, }, /// Move the focused workspace down. MoveWorkspaceDown {}, /// Move the focused workspace up. MoveWorkspaceUp {}, /// Move a workspace to a specific index on its monitor. #[cfg_attr( feature = "clap", clap(about = "Move the focused workspace to a specific index on its monitor") )] MoveWorkspaceToIndex { /// New index for the workspace. #[cfg_attr(feature = "clap", arg())] index: usize, /// Reference (index or name) of the workspace to move. /// /// If `None`, uses the focused workspace. #[cfg_attr(feature = "clap", arg(long))] reference: Option, }, /// Set the name of a workspace. #[cfg_attr( feature = "clap", clap(about = "Set the name of the focused workspace") )] SetWorkspaceName { /// New name for the workspace. #[cfg_attr(feature = "clap", arg())] name: String, /// Reference (index or name) of the workspace to name. /// /// If `None`, uses the focused workspace. #[cfg_attr(feature = "clap", arg(long))] workspace: Option, }, /// Unset the name of a workspace. #[cfg_attr( feature = "clap", clap(about = "Unset the name of the focused workspace") )] UnsetWorkspaceName { /// Reference (index or name) of the workspace to unname. /// /// If `None`, uses the focused workspace. #[cfg_attr(feature = "clap", arg())] reference: Option, }, /// Focus the monitor to the left. FocusMonitorLeft {}, /// Focus the monitor to the right. FocusMonitorRight {}, /// Focus the monitor below. FocusMonitorDown {}, /// Focus the monitor above. FocusMonitorUp {}, /// Focus the previous monitor. FocusMonitorPrevious {}, /// Focus the next monitor. FocusMonitorNext {}, /// Focus a monitor by name. FocusMonitor { /// Name of the output to focus. #[cfg_attr(feature = "clap", arg())] output: String, }, /// Move the focused window to the monitor to the left. MoveWindowToMonitorLeft {}, /// Move the focused window to the monitor to the right. MoveWindowToMonitorRight {}, /// Move the focused window to the monitor below. MoveWindowToMonitorDown {}, /// Move the focused window to the monitor above. MoveWindowToMonitorUp {}, /// Move the focused window to the previous monitor. MoveWindowToMonitorPrevious {}, /// Move the focused window to the next monitor. MoveWindowToMonitorNext {}, /// Move a window to a specific monitor. #[cfg_attr( feature = "clap", clap(about = "Move the focused window to a specific monitor") )] MoveWindowToMonitor { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, /// The target output name. #[cfg_attr(feature = "clap", arg())] output: String, }, /// Move the focused column to the monitor to the left. MoveColumnToMonitorLeft {}, /// Move the focused column to the monitor to the right. MoveColumnToMonitorRight {}, /// Move the focused column to the monitor below. MoveColumnToMonitorDown {}, /// Move the focused column to the monitor above. MoveColumnToMonitorUp {}, /// Move the focused column to the previous monitor. MoveColumnToMonitorPrevious {}, /// Move the focused column to the next monitor. MoveColumnToMonitorNext {}, /// Move the focused column to a specific monitor. MoveColumnToMonitor { /// The target output name. #[cfg_attr(feature = "clap", arg())] output: String, }, /// Change the width of a window. #[cfg_attr( feature = "clap", clap(about = "Change the width of the focused window") )] SetWindowWidth { /// Id of the window whose width to set. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, /// How to change the width. #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))] change: SizeChange, }, /// Change the height of a window. #[cfg_attr( feature = "clap", clap(about = "Change the height of the focused window") )] SetWindowHeight { /// Id of the window whose height to set. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, /// How to change the height. #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))] change: SizeChange, }, /// Reset the height of a window back to automatic. #[cfg_attr( feature = "clap", clap(about = "Reset the height of the focused window back to automatic") )] ResetWindowHeight { /// Id of the window whose height to reset. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Switch between preset column widths. SwitchPresetColumnWidth {}, /// Switch between preset column widths backwards. SwitchPresetColumnWidthBack {}, /// Switch between preset window widths. SwitchPresetWindowWidth { /// Id of the window whose width to switch. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Switch between preset window widths backwards. SwitchPresetWindowWidthBack { /// Id of the window whose width to switch. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Switch between preset window heights. SwitchPresetWindowHeight { /// Id of the window whose height to switch. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Switch between preset window heights backwards. SwitchPresetWindowHeightBack { /// Id of the window whose height to switch. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Toggle the maximized state of the focused column. MaximizeColumn {}, /// Change the width of the focused column. SetColumnWidth { /// How to change the width. #[cfg_attr(feature = "clap", arg(allow_hyphen_values = true))] change: SizeChange, }, /// Expand the focused column to space not taken up by other fully visible columns. ExpandColumnToAvailableWidth {}, /// Switch between keyboard layouts. SwitchLayout { /// Layout to switch to. #[cfg_attr(feature = "clap", arg())] layout: LayoutSwitchTarget, }, /// Show the hotkey overlay. ShowHotkeyOverlay {}, /// Move the focused workspace to the monitor to the left. MoveWorkspaceToMonitorLeft {}, /// Move the focused workspace to the monitor to the right. MoveWorkspaceToMonitorRight {}, /// Move the focused workspace to the monitor below. MoveWorkspaceToMonitorDown {}, /// Move the focused workspace to the monitor above. MoveWorkspaceToMonitorUp {}, /// Move the focused workspace to the previous monitor. MoveWorkspaceToMonitorPrevious {}, /// Move the focused workspace to the next monitor. MoveWorkspaceToMonitorNext {}, /// Move a workspace to a specific monitor. #[cfg_attr( feature = "clap", clap(about = "Move the focused workspace to a specific monitor") )] MoveWorkspaceToMonitor { /// The target output name. #[cfg_attr(feature = "clap", arg())] output: String, // Reference (index or name) of the workspace to move. /// /// If `None`, uses the focused workspace. #[cfg_attr(feature = "clap", arg(long))] reference: Option, }, /// Toggle a debug tint on windows. ToggleDebugTint {}, /// Toggle visualization of render element opaque regions. DebugToggleOpaqueRegions {}, /// Toggle visualization of output damage. DebugToggleDamage {}, /// Move the focused window between the floating and the tiling layout. ToggleWindowFloating { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Move the focused window to the floating layout. MoveWindowToFloating { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Move the focused window to the tiling layout. MoveWindowToTiling { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Switches focus to the floating layout. FocusFloating {}, /// Switches focus to the tiling layout. FocusTiling {}, /// Toggles the focus between the floating and the tiling layout. SwitchFocusBetweenFloatingAndTiling {}, /// Move a floating window on screen. #[cfg_attr(feature = "clap", clap(about = "Move the floating window on screen"))] MoveFloatingWindow { /// Id of the window to move. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, /// How to change the X position. #[cfg_attr( feature = "clap", arg(short, long, default_value = "+0", allow_negative_numbers = true) )] x: PositionChange, /// How to change the Y position. #[cfg_attr( feature = "clap", arg(short, long, default_value = "+0", allow_negative_numbers = true) )] y: PositionChange, }, /// Toggle the opacity of a window. #[cfg_attr( feature = "clap", clap(about = "Toggle the opacity of the focused window") )] ToggleWindowRuleOpacity { /// Id of the window. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Set the dynamic cast target to a window. #[cfg_attr( feature = "clap", clap(about = "Set the dynamic cast target to the focused window") )] SetDynamicCastWindow { /// Id of the window to target. /// /// If `None`, uses the focused window. #[cfg_attr(feature = "clap", arg(long))] id: Option, }, /// Set the dynamic cast target to a monitor. #[cfg_attr( feature = "clap", clap(about = "Set the dynamic cast target to the focused monitor") )] SetDynamicCastMonitor { /// Name of the output to target. /// /// If `None`, uses the focused output. #[cfg_attr(feature = "clap", arg())] output: Option, }, /// Clear the dynamic cast target, making it show nothing. ClearDynamicCastTarget {}, /// Toggle (open/close) the Overview. ToggleOverview {}, /// Open the Overview. OpenOverview {}, /// Close the Overview. CloseOverview {}, /// Toggle urgent status of a window. ToggleWindowUrgent { /// Id of the window to toggle urgent. #[cfg_attr(feature = "clap", arg(long))] id: u64, }, /// Set urgent status of a window. SetWindowUrgent { /// Id of the window to set urgent. #[cfg_attr(feature = "clap", arg(long))] id: u64, }, /// Unset urgent status of a window. UnsetWindowUrgent { /// Id of the window to unset urgent. #[cfg_attr(feature = "clap", arg(long))] id: u64, }, /// Reload the config file. /// /// Can be useful for scripts changing the config file, to avoid waiting the small duration for /// niri's config file watcher to notice the changes. LoadConfigFile {}, } /// Change in window or column size. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum SizeChange { /// Set the size in logical pixels. SetFixed(i32), /// Set the size as a proportion of the working area. SetProportion(f64), /// Add or subtract to the current size in logical pixels. AdjustFixed(i32), /// Add or subtract to the current size as a proportion of the working area. AdjustProportion(f64), } /// Change in floating window position. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum PositionChange { /// Set the position in logical pixels. SetFixed(f64), /// Add or subtract to the current position in logical pixels. AdjustFixed(f64), } /// Workspace reference (id, index or name) to operate on. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum WorkspaceReferenceArg { /// Id of the workspace. Id(u64), /// Index of the workspace. Index(u8), /// Name of the workspace. Name(String), } /// Layout to switch to. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum LayoutSwitchTarget { /// The next configured layout. Next, /// The previous configured layout. Prev, /// The specific layout by index. Index(u8), } /// How windows display in a column. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum ColumnDisplay { /// Windows are tiled vertically across the working area height. Normal, /// Windows are in tabs. Tabbed, } /// Output actions that niri can perform. // Variants in this enum should match the spelling of the ones in niri-config. Most thigs from // niri-config should be present here. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Parser))] #[cfg_attr(feature = "clap", command(subcommand_value_name = "ACTION"))] #[cfg_attr(feature = "clap", command(subcommand_help_heading = "Actions"))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum OutputAction { /// Turn off the output. Off, /// Turn on the output. On, /// Set the output mode. Mode { /// Mode to set, or "auto" for automatic selection. /// /// Run `niri msg outputs` to see the available modes. #[cfg_attr(feature = "clap", arg())] mode: ModeToSet, }, /// Set the output scale. Scale { /// Scale factor to set, or "auto" for automatic selection. #[cfg_attr(feature = "clap", arg())] scale: ScaleToSet, }, /// Set the output transform. Transform { /// Transform to set, counter-clockwise. #[cfg_attr(feature = "clap", arg())] transform: Transform, }, /// Set the output position. Position { /// Position to set, or "auto" for automatic selection. #[cfg_attr(feature = "clap", command(subcommand))] position: PositionToSet, }, /// Set the variable refresh rate mode. Vrr { /// Variable refresh rate mode to set. #[cfg_attr(feature = "clap", command(flatten))] vrr: VrrToSet, }, } /// Output mode to set. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum ModeToSet { /// Niri will pick the mode automatically. Automatic, /// Specific mode. Specific(ConfiguredMode), } /// Output mode as set in the config file. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct ConfiguredMode { /// Width in physical pixels. pub width: u16, /// Height in physical pixels. pub height: u16, /// Refresh rate. pub refresh: Option, } /// Output scale to set. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum ScaleToSet { /// Niri will pick the scale automatically. Automatic, /// Specific scale. Specific(f64), } /// Output position to set. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "clap", derive(clap::Subcommand))] #[cfg_attr(feature = "clap", command(subcommand_value_name = "POSITION"))] #[cfg_attr(feature = "clap", command(subcommand_help_heading = "Position Values"))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum PositionToSet { /// Position the output automatically. #[cfg_attr(feature = "clap", command(name = "auto"))] Automatic, /// Set a specific position. #[cfg_attr(feature = "clap", command(name = "set"))] Specific(ConfiguredPosition), } /// Output position as set in the config file. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "clap", derive(clap::Args))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct ConfiguredPosition { /// Logical X position. pub x: i32, /// Logical Y position. pub y: i32, } /// Output VRR to set. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "clap", derive(clap::Args))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct VrrToSet { /// Whether to enable variable refresh rate. #[cfg_attr( feature = "clap", arg( value_name = "ON|OFF", action = clap::ArgAction::Set, value_parser = clap::builder::BoolishValueParser::new(), hide_possible_values = true, ), )] pub vrr: bool, /// Only enable when the output shows a window matching the variable-refresh-rate window rule. #[cfg_attr(feature = "clap", arg(long))] pub on_demand: bool, } /// Connected output. #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Output { /// Name of the output. pub name: String, /// Textual description of the manufacturer. pub make: String, /// Textual description of the model. pub model: String, /// Serial of the output, if known. pub serial: Option, /// Physical width and height of the output in millimeters, if known. pub physical_size: Option<(u32, u32)>, /// Available modes for the output. pub modes: Vec, /// Index of the current mode in [`Self::modes`]. /// /// `None` if the output is disabled. pub current_mode: Option, /// Whether the output supports variable refresh rate. pub vrr_supported: bool, /// Whether variable refresh rate is enabled on the output. pub vrr_enabled: bool, /// Logical output information. /// /// `None` if the output is not mapped to any logical output (for example, if it is disabled). pub logical: Option, } /// Output mode. #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Mode { /// Width in physical pixels. pub width: u16, /// Height in physical pixels. pub height: u16, /// Refresh rate in millihertz. pub refresh_rate: u32, /// Whether this mode is preferred by the monitor. pub is_preferred: bool, } /// Logical output in the compositor's coordinate space. #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct LogicalOutput { /// Logical X position. pub x: i32, /// Logical Y position. pub y: i32, /// Width in logical pixels. pub width: u32, /// Height in logical pixels. pub height: u32, /// Scale factor. pub scale: f64, /// Transform. pub transform: Transform, } /// Output transform, which goes counter-clockwise. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Transform { /// Untransformed. Normal, /// Rotated by 90°. #[serde(rename = "90")] _90, /// Rotated by 180°. #[serde(rename = "180")] _180, /// Rotated by 270°. #[serde(rename = "270")] _270, /// Flipped horizontally. Flipped, /// Rotated by 90° and flipped horizontally. #[cfg_attr(feature = "clap", value(name("flipped-90")))] Flipped90, /// Flipped vertically. #[cfg_attr(feature = "clap", value(name("flipped-180")))] Flipped180, /// Rotated by 270° and flipped horizontally. #[cfg_attr(feature = "clap", value(name("flipped-270")))] Flipped270, } /// Toplevel window. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Window { /// Unique id of this window. /// /// This id remains constant while this window is open. /// /// Do not assume that window ids will always increase without wrapping, or start at 1. That is /// an implementation detail subject to change. For example, ids may change to be randomly /// generated for each new window. pub id: u64, /// Title, if set. pub title: Option, /// Application ID, if set. pub app_id: Option, /// Process ID that created the Wayland connection for this window, if known. /// /// Currently, windows created by xdg-desktop-portal-gnome will have a `None` PID, but this may /// change in the future. pub pid: Option, /// Id of the workspace this window is on, if any. pub workspace_id: Option, /// Whether this window is currently focused. /// /// There can be either one focused window or zero (e.g. when a layer-shell surface has focus). pub is_focused: bool, /// Whether this window is currently floating. /// /// If the window isn't floating then it is in the tiling layout. pub is_floating: bool, /// Whether this window requests your attention. pub is_urgent: bool, /// Position- and size-related properties of the window. pub layout: WindowLayout, } /// Position- and size-related properties of a [`Window`]. /// /// Optional properties will be unset for some windows, do not rely on them being present. Whether /// some optional properties are present or absent for certain window types may change across niri /// releases. /// /// All sizes and positions are in *logical pixels* unless stated otherwise. Logical sizes may be /// fractional. For example, at 1.25 monitor scale, a 2-physical-pixel-wide window border is 1.6 /// logical pixels wide. /// /// This struct contains positions and sizes both for full tiles ([`Self::tile_size`], /// [`Self::tile_pos_in_workspace_view`]) and the window geometry ([`Self::window_size`], /// [`Self::window_offset_in_tile`]). For visual displays, use the tile properties, as they /// correspond to what the user visually considers "window". The window properties on the other /// hand are mainly useful when you need to know the underlying Wayland window sizes, e.g. for /// application debugging. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct WindowLayout { /// Location of a tiled window within a workspace: (column index, tile index in column). /// /// The indices are 1-based, i.e. the leftmost column is at index 1 and the topmost tile in a /// column is at index 1. This is consistent with [`Action::FocusColumn`] and /// [`Action::FocusWindowInColumn`]. pub pos_in_scrolling_layout: Option<(usize, usize)>, /// Size of the tile this window is in, including decorations like borders. pub tile_size: (f64, f64), /// Size of the window's visual geometry itself. /// /// Does not include niri decorations like borders. /// /// Currently, Wayland toplevel windows can only be integer-sized in logical pixels, even /// though it doesn't necessarily align to physical pixels. pub window_size: (i32, i32), /// Tile position within the current view of the workspace. /// /// This is the same "workspace view" as in gradients' `relative-to` in the niri config. pub tile_pos_in_workspace_view: Option<(f64, f64)>, /// Location of the window's visual geometry within its tile. /// /// This includes things like border sizes. For fullscreened fixed-size windows this includes /// the distance from the corner of the black backdrop to the corner of the (centered) window /// contents. pub window_offset_in_tile: (f64, f64), } /// Output configuration change result. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum OutputConfigChanged { /// The target output was connected and the change was applied. Applied, /// The target output was not found, the change will be applied when it is connected. OutputWasMissing, } /// A workspace. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Workspace { /// Unique id of this workspace. /// /// This id remains constant regardless of the workspace moving around and across monitors. /// /// Do not assume that workspace ids will always increase without wrapping, or start at 1. That /// is an implementation detail subject to change. For example, ids may change to be randomly /// generated for each new workspace. pub id: u64, /// Index of the workspace on its monitor. /// /// This is the same index you can use for requests like `niri msg action focus-workspace`. /// /// This index *will change* as you move and re-order workspace. It is merely the workspace's /// current position on its monitor. Workspaces on different monitors can have the same index. /// /// If you need a unique workspace id that doesn't change, see [`Self::id`]. pub idx: u8, /// Optional name of the workspace. pub name: Option, /// Name of the output that the workspace is on. /// /// Can be `None` if no outputs are currently connected. pub output: Option, /// Whether the workspace currently has an urgent window in its output. pub is_urgent: bool, /// Whether the workspace is currently active on its output. /// /// Every output has one active workspace, the one that is currently visible on that output. pub is_active: bool, /// Whether the workspace is currently focused. /// /// There's only one focused workspace across all outputs. pub is_focused: bool, /// Id of the active window on this workspace, if any. pub active_window_id: Option, } /// Configured keyboard layouts. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct KeyboardLayouts { /// XKB names of the configured layouts. pub names: Vec, /// Index of the currently active layout in `names`. pub current_idx: u8, } /// A layer-shell layer. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Layer { /// The background layer. Background, /// The bottom layer. Bottom, /// The top layer. Top, /// The overlay layer. Overlay, } /// Keyboard interactivity modes for a layer-shell surface. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum LayerSurfaceKeyboardInteractivity { /// Surface cannot receive keyboard focus. None, /// Surface receives keyboard focus whenever possible. Exclusive, /// Surface receives keyboard focus on demand, e.g. when clicked. OnDemand, } /// A layer-shell surface. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct LayerSurface { /// Namespace provided by the layer-shell client. pub namespace: String, /// Name of the output the surface is on. pub output: String, /// Layer that the surface is on. pub layer: Layer, /// The surface's keyboard interactivity mode. pub keyboard_interactivity: LayerSurfaceKeyboardInteractivity, } /// A compositor event. #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Event { /// The workspace configuration has changed. WorkspacesChanged { /// The new workspace configuration. /// /// This configuration completely replaces the previous configuration. I.e. if any /// workspaces are missing from here, then they were deleted. workspaces: Vec, }, /// The workspace urgency changed. WorkspaceUrgencyChanged { /// Id of the workspace. id: u64, /// Whether this workspace has an urgent window. urgent: bool, }, /// A workspace was activated on an output. /// /// This doesn't always mean the workspace became focused, just that it's now the active /// workspace on its output. All other workspaces on the same output become inactive. WorkspaceActivated { /// Id of the newly active workspace. id: u64, /// Whether this workspace also became focused. /// /// If `true`, this is now the single focused workspace. All other workspaces are no longer /// focused, but they may remain active on their respective outputs. focused: bool, }, /// An active window changed on a workspace. WorkspaceActiveWindowChanged { /// Id of the workspace on which the active window changed. workspace_id: u64, /// Id of the new active window, if any. active_window_id: Option, }, /// The window configuration has changed. WindowsChanged { /// The new window configuration. /// /// This configuration completely replaces the previous configuration. I.e. if any windows /// are missing from here, then they were closed. windows: Vec, }, /// A new toplevel window was opened, or an existing toplevel window changed. WindowOpenedOrChanged { /// The new or updated window. /// /// If the window is focused, all other windows are no longer focused. window: Window, }, /// A toplevel window was closed. WindowClosed { /// Id of the removed window. id: u64, }, /// Window focus changed. /// /// All other windows are no longer focused. WindowFocusChanged { /// Id of the newly focused window, or `None` if no window is now focused. id: Option, }, /// Window urgency changed. WindowUrgencyChanged { /// Id of the window. id: u64, /// The new urgency state of the window. urgent: bool, }, /// The layout of one or more windows has changed. WindowLayoutsChanged { /// Pairs consisting of a window id and new layout information for the window. changes: Vec<(u64, WindowLayout)>, }, /// The configured keyboard layouts have changed. KeyboardLayoutsChanged { /// The new keyboard layout configuration. keyboard_layouts: KeyboardLayouts, }, /// The keyboard layout switched. KeyboardLayoutSwitched { /// Index of the newly active layout. idx: u8, }, /// The overview was opened or closed. OverviewOpenedOrClosed { /// The new state of the overview. is_open: bool, }, /// The configuration was reloaded. /// /// You will always receive this event when connecting to the event stream, indicating the last /// config load attempt. ConfigLoaded { /// Whether the loading failed. /// /// For example, the config file couldn't be parsed. failed: bool, }, } impl FromStr for WorkspaceReferenceArg { type Err = &'static str; fn from_str(s: &str) -> Result { let reference = if let Ok(index) = s.parse::() { if let Ok(idx) = u8::try_from(index) { Self::Index(idx) } else { return Err("workspace index must be between 0 and 255"); } } else { Self::Name(s.to_string()) }; Ok(reference) } } impl FromStr for SizeChange { type Err = &'static str; fn from_str(s: &str) -> Result { match s.split_once('%') { Some((value, empty)) => { if !empty.is_empty() { return Err("trailing characters after '%' are not allowed"); } match value.bytes().next() { Some(b'-' | b'+') => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::AdjustProportion(value)) } Some(_) => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::SetProportion(value)) } None => Err("value is missing"), } } None => { let value = s; match value.bytes().next() { Some(b'-' | b'+') => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::AdjustFixed(value)) } Some(_) => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::SetFixed(value)) } None => Err("value is missing"), } } } } } impl FromStr for PositionChange { type Err = &'static str; fn from_str(s: &str) -> Result { let value = s; match value.bytes().next() { Some(b'-' | b'+') => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::AdjustFixed(value)) } Some(_) => { let value = value.parse().map_err(|_| "error parsing value")?; Ok(Self::SetFixed(value)) } None => Err("value is missing"), } } } impl FromStr for LayoutSwitchTarget { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "next" => Ok(Self::Next), "prev" => Ok(Self::Prev), other => match other.parse() { Ok(layout) => Ok(Self::Index(layout)), _ => Err(r#"invalid layout action, can be "next", "prev" or a layout index"#), }, } } } impl FromStr for ColumnDisplay { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "normal" => Ok(Self::Normal), "tabbed" => Ok(Self::Tabbed), _ => Err(r#"invalid column display, can be "normal" or "tabbed""#), } } } impl FromStr for Transform { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "normal" => Ok(Self::Normal), "90" => Ok(Self::_90), "180" => Ok(Self::_180), "270" => Ok(Self::_270), "flipped" => Ok(Self::Flipped), "flipped-90" => Ok(Self::Flipped90), "flipped-180" => Ok(Self::Flipped180), "flipped-270" => Ok(Self::Flipped270), _ => Err(concat!( r#"invalid transform, can be "90", "180", "270", "#, r#""flipped", "flipped-90", "flipped-180" or "flipped-270""# )), } } } impl FromStr for ModeToSet { type Err = &'static str; fn from_str(s: &str) -> Result { if s.eq_ignore_ascii_case("auto") { return Ok(Self::Automatic); } let mode = s.parse()?; Ok(Self::Specific(mode)) } } impl FromStr for ConfiguredMode { type Err = &'static str; fn from_str(s: &str) -> Result { let Some((width, rest)) = s.split_once('x') else { return Err("no 'x' separator found"); }; let (height, refresh) = match rest.split_once('@') { Some((height, refresh)) => (height, Some(refresh)), None => (rest, None), }; let width = width.parse().map_err(|_| "error parsing width")?; let height = height.parse().map_err(|_| "error parsing height")?; let refresh = refresh .map(str::parse) .transpose() .map_err(|_| "error parsing refresh rate")?; Ok(Self { width, height, refresh, }) } } impl FromStr for ScaleToSet { type Err = &'static str; fn from_str(s: &str) -> Result { if s.eq_ignore_ascii_case("auto") { return Ok(Self::Automatic); } let scale = s.parse().map_err(|_| "error parsing scale")?; Ok(Self::Specific(scale)) } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_size_change() { assert_eq!( "10".parse::().unwrap(), SizeChange::SetFixed(10), ); assert_eq!( "+10".parse::().unwrap(), SizeChange::AdjustFixed(10), ); assert_eq!( "-10".parse::().unwrap(), SizeChange::AdjustFixed(-10), ); assert_eq!( "10%".parse::().unwrap(), SizeChange::SetProportion(10.), ); assert_eq!( "+10%".parse::().unwrap(), SizeChange::AdjustProportion(10.), ); assert_eq!( "-10%".parse::().unwrap(), SizeChange::AdjustProportion(-10.), ); assert!("-".parse::().is_err()); assert!("10% ".parse::().is_err()); } #[test] fn parse_position_change() { assert_eq!( "10".parse::().unwrap(), PositionChange::SetFixed(10.), ); assert_eq!( "+10".parse::().unwrap(), PositionChange::AdjustFixed(10.), ); assert_eq!( "-10".parse::().unwrap(), PositionChange::AdjustFixed(-10.), ); assert!("10%".parse::().is_err()); assert!("+10%".parse::().is_err()); assert!("-10%".parse::().is_err()); assert!("-".parse::().is_err()); assert!("10% ".parse::().is_err()); } } niri-ipc-25.8.0/src/socket.rs000064400000000000000000000064161046102023000140660ustar 00000000000000//! Helper for blocking communication over the niri socket. use std::env; use std::io::{self, BufRead, BufReader, Write}; use std::net::Shutdown; use std::os::unix::net::UnixStream; use std::path::Path; use crate::{Event, Reply, Request}; /// Name of the environment variable containing the niri IPC socket path. pub const SOCKET_PATH_ENV: &str = "NIRI_SOCKET"; /// Helper for blocking communication over the niri socket. /// /// This struct is used to communicate with the niri IPC server. It handles the socket connection /// and serialization/deserialization of messages. pub struct Socket { stream: BufReader, } impl Socket { /// Connects to the default niri IPC socket. /// /// This is equivalent to calling [`Self::connect_to`] with the path taken from the /// [`SOCKET_PATH_ENV`] environment variable. pub fn connect() -> io::Result { let socket_path = env::var_os(SOCKET_PATH_ENV).ok_or_else(|| { io::Error::new( io::ErrorKind::NotFound, format!("{SOCKET_PATH_ENV} is not set, are you running this within niri?"), ) })?; Self::connect_to(socket_path) } /// Connects to the niri IPC socket at the given path. pub fn connect_to(path: impl AsRef) -> io::Result { let stream = UnixStream::connect(path.as_ref())?; let stream = BufReader::new(stream); Ok(Self { stream }) } /// Sends a request to niri and returns the response. /// /// Return values: /// /// * `Ok(Ok(response))`: successful [`Response`](crate::Response) from niri /// * `Ok(Err(message))`: error message from niri /// * `Err(error)`: error communicating with niri pub fn send(&mut self, request: Request) -> io::Result { let mut buf = serde_json::to_string(&request).unwrap(); buf.push('\n'); self.stream.get_mut().write_all(buf.as_bytes())?; buf.clear(); self.stream.read_line(&mut buf)?; let reply = serde_json::from_str(&buf)?; Ok(reply) } /// Starts reading event stream [`Event`]s from the socket. /// /// The returned function will block until the next [`Event`] arrives, then return it. /// /// Use this only after requesting an [`EventStream`][Request::EventStream]. /// /// # Examples /// /// ```no_run /// use niri_ipc::{Request, Response}; /// use niri_ipc::socket::Socket; /// /// fn main() -> std::io::Result<()> { /// let mut socket = Socket::connect()?; /// /// let reply = socket.send(Request::EventStream)?; /// if matches!(reply, Ok(Response::Handled)) { /// let mut read_event = socket.read_events(); /// while let Ok(event) = read_event() { /// println!("Received event: {event:?}"); /// } /// } /// /// Ok(()) /// } /// ``` pub fn read_events(self) -> impl FnMut() -> io::Result { let Self { mut stream } = self; let _ = stream.get_mut().shutdown(Shutdown::Write); let mut buf = String::new(); move || { buf.clear(); stream.read_line(&mut buf)?; let event = serde_json::from_str(&buf)?; Ok(event) } } } niri-ipc-25.8.0/src/state.rs000064400000000000000000000222441046102023000137130ustar 00000000000000//! Helpers for keeping track of the event stream state. //! //! 1. Create an [`EventStreamState`] using `Default::default()`, or any individual state part if //! you only care about part of the state. //! 2. Connect to the niri socket and request an event stream. //! 3. Pass every [`Event`] to [`EventStreamStatePart::apply`] on your state. //! 4. Read the fields of the state as needed. use std::collections::hash_map::Entry; use std::collections::HashMap; use crate::{Event, KeyboardLayouts, Window, Workspace}; /// Part of the state communicated via the event stream. pub trait EventStreamStatePart { /// Returns a sequence of events that replicates this state from default initialization. fn replicate(&self) -> Vec; /// Applies the event to this state. /// /// Returns `None` after applying the event, and `Some(event)` if the event is ignored by this /// part of the state. fn apply(&mut self, event: Event) -> Option; } /// The full state communicated over the event stream. /// /// Different parts of the state are not guaranteed to be consistent across every single event /// sent by niri. For example, you may receive the first [`Event::WindowOpenedOrChanged`] for a /// just-opened window *after* an [`Event::WorkspaceActiveWindowChanged`] for that window. Between /// these two events, the workspace active window id refers to a window that does not yet exist in /// the windows state part. #[derive(Debug, Default)] pub struct EventStreamState { /// State of workspaces. pub workspaces: WorkspacesState, /// State of workspaces. pub windows: WindowsState, /// State of the keyboard layouts. pub keyboard_layouts: KeyboardLayoutsState, /// State of the overview. pub overview: OverviewState, /// State of the config. pub config: ConfigState, } /// The workspaces state communicated over the event stream. #[derive(Debug, Default)] pub struct WorkspacesState { /// Map from a workspace id to the workspace. pub workspaces: HashMap, } /// The windows state communicated over the event stream. #[derive(Debug, Default)] pub struct WindowsState { /// Map from a window id to the window. pub windows: HashMap, } /// The keyboard layout state communicated over the event stream. #[derive(Debug, Default)] pub struct KeyboardLayoutsState { /// Configured keyboard layouts. pub keyboard_layouts: Option, } /// The overview state communicated over the event stream. #[derive(Debug, Default)] pub struct OverviewState { /// Whether the overview is currently open. pub is_open: bool, } /// The config state communicated over the event stream. #[derive(Debug, Default)] pub struct ConfigState { /// Whether the last config load attempt had failed. pub failed: bool, } impl EventStreamStatePart for EventStreamState { fn replicate(&self) -> Vec { let mut events = Vec::new(); events.extend(self.workspaces.replicate()); events.extend(self.windows.replicate()); events.extend(self.keyboard_layouts.replicate()); events.extend(self.overview.replicate()); events.extend(self.config.replicate()); events } fn apply(&mut self, event: Event) -> Option { let event = self.workspaces.apply(event)?; let event = self.windows.apply(event)?; let event = self.keyboard_layouts.apply(event)?; let event = self.overview.apply(event)?; let event = self.config.apply(event)?; Some(event) } } impl EventStreamStatePart for WorkspacesState { fn replicate(&self) -> Vec { let workspaces = self.workspaces.values().cloned().collect(); vec![Event::WorkspacesChanged { workspaces }] } fn apply(&mut self, event: Event) -> Option { match event { Event::WorkspacesChanged { workspaces } => { self.workspaces = workspaces.into_iter().map(|ws| (ws.id, ws)).collect(); } Event::WorkspaceUrgencyChanged { id, urgent } => { for ws in self.workspaces.values_mut() { if ws.id == id { ws.is_urgent = urgent; } } } Event::WorkspaceActivated { id, focused } => { let ws = self.workspaces.get(&id); let ws = ws.expect("activated workspace was missing from the map"); let output = ws.output.clone(); for ws in self.workspaces.values_mut() { let got_activated = ws.id == id; if ws.output == output { ws.is_active = got_activated; } if focused { ws.is_focused = got_activated; } } } Event::WorkspaceActiveWindowChanged { workspace_id, active_window_id, } => { let ws = self.workspaces.get_mut(&workspace_id); let ws = ws.expect("changed workspace was missing from the map"); ws.active_window_id = active_window_id; } event => return Some(event), } None } } impl EventStreamStatePart for WindowsState { fn replicate(&self) -> Vec { let windows = self.windows.values().cloned().collect(); vec![Event::WindowsChanged { windows }] } fn apply(&mut self, event: Event) -> Option { match event { Event::WindowsChanged { windows } => { self.windows = windows.into_iter().map(|win| (win.id, win)).collect(); } Event::WindowOpenedOrChanged { window } => { let (id, is_focused) = match self.windows.entry(window.id) { Entry::Occupied(mut entry) => { let entry = entry.get_mut(); *entry = window; (entry.id, entry.is_focused) } Entry::Vacant(entry) => { let entry = entry.insert(window); (entry.id, entry.is_focused) } }; if is_focused { for win in self.windows.values_mut() { if win.id != id { win.is_focused = false; } } } } Event::WindowClosed { id } => { let win = self.windows.remove(&id); win.expect("closed window was missing from the map"); } Event::WindowFocusChanged { id } => { for win in self.windows.values_mut() { win.is_focused = Some(win.id) == id; } } Event::WindowUrgencyChanged { id, urgent } => { for win in self.windows.values_mut() { if win.id == id { win.is_urgent = urgent; break; } } } Event::WindowLayoutsChanged { changes } => { for (id, update) in changes { let win = self.windows.get_mut(&id); let win = win.expect("changed window was missing from the map"); win.layout = update; } } event => return Some(event), } None } } impl EventStreamStatePart for KeyboardLayoutsState { fn replicate(&self) -> Vec { if let Some(keyboard_layouts) = self.keyboard_layouts.clone() { vec![Event::KeyboardLayoutsChanged { keyboard_layouts }] } else { vec![] } } fn apply(&mut self, event: Event) -> Option { match event { Event::KeyboardLayoutsChanged { keyboard_layouts } => { self.keyboard_layouts = Some(keyboard_layouts); } Event::KeyboardLayoutSwitched { idx } => { let kb = self.keyboard_layouts.as_mut(); let kb = kb.expect("keyboard layouts must be set before a layout can be switched"); kb.current_idx = idx; } event => return Some(event), } None } } impl EventStreamStatePart for OverviewState { fn replicate(&self) -> Vec { vec![Event::OverviewOpenedOrClosed { is_open: self.is_open, }] } fn apply(&mut self, event: Event) -> Option { match event { Event::OverviewOpenedOrClosed { is_open } => { self.is_open = is_open; } event => return Some(event), } None } } impl EventStreamStatePart for ConfigState { fn replicate(&self) -> Vec { vec![Event::ConfigLoaded { failed: self.failed, }] } fn apply(&mut self, event: Event) -> Option { match event { Event::ConfigLoaded { failed } => { self.failed = failed; } event => return Some(event), } None } }