//! Types for communicating with niri via IPC. #![warn(missing_docs)] use std::collections::HashMap; use std::str::FromStr; use serde::{Deserialize, Serialize}; mod socket; pub use socket::{Socket, SOCKET_PATH_ENV}; 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 the configured keyboard layouts. KeyboardLayouts, /// Request information about the focused output. FocusedOutput, /// Request information about the focused window. FocusedWindow, /// 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. EventStream, /// Respond with an error (for testing error handling). ReturnError, } /// 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 connector name to output info. Outputs(HashMap), /// Information about workspaces. Workspaces(Vec), /// Information about open windows. Windows(Vec), /// Information about the keyboard layout. KeyboardLayouts(KeyboardLayouts), /// Information about the focused output. FocusedOutput(Option), /// Information about the focused window. FocusedWindow(Option), /// Output configuration change result. OutputConfigChanged(OutputConfigChanged), } /// 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, /// Spawn a command. Spawn { /// Command to spawn. #[cfg_attr(feature = "clap", arg(last = true, required = true))] command: Vec, }, /// 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, /// Screenshot the focused screen. ScreenshotScreen, /// Screenshot the focused window. ScreenshotWindow, /// Close the focused window. CloseWindow, /// Toggle fullscreen on the focused window. FullscreenWindow, /// 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 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 above. FocusWindowOrWorkspaceDown, /// Focus the window or the workspace above. FocusWindowOrWorkspaceUp, /// 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 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 the focused window left. ConsumeOrExpelWindowLeft, /// Consume or expel the focused window right. ConsumeOrExpelWindowRight, /// Consume the window to the right into the focused column. ConsumeWindowIntoColumn, /// Expel the focused window from the column. ExpelWindowFromColumn, /// Center the focused column on the screen. CenterColumn, /// 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, /// Move the focused window to the workspace above. MoveWindowToWorkspaceUp, /// Move the focused window to a workspace by reference (index or name). MoveWindowToWorkspace { /// Reference (index or name) of the workspace to move the window to. #[cfg_attr(feature = "clap", arg())] reference: WorkspaceReferenceArg, }, /// Move the focused column to the workspace below. MoveColumnToWorkspaceDown, /// Move the focused column to the workspace above. MoveColumnToWorkspaceUp, /// 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, }, /// Move the focused workspace down. MoveWorkspaceDown, /// Move the focused workspace up. MoveWorkspaceUp, /// Focus the monitor to the left. FocusMonitorLeft, /// Focus the monitor to the right. FocusMonitorRight, /// Focus the monitor below. FocusMonitorDown, /// Focus the monitor above. FocusMonitorUp, /// 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 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, /// Change the height of the focused window. SetWindowHeight { /// How to change the height. #[cfg_attr(feature = "clap", arg())] change: SizeChange, }, /// Reset the height of the focused window back to automatic. ResetWindowHeight, /// Switch between preset column widths. SwitchPresetColumnWidth, /// 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())] change: SizeChange, }, /// 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, /// Toggle a debug tint on windows. ToggleDebugTint, /// Toggle visualization of render element opaque regions. DebugToggleOpaqueRegions, /// Toggle visualization of output damage. DebugToggleDamage, } /// 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), } /// Workspace reference (index or name) to operate on. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum WorkspaceReferenceArg { /// 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, } /// 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, /// 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. pub id: u64, /// Title, if set. pub title: Option, /// Application ID, if set. pub app_id: 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, } /// 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. 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`. 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 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 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, }, /// 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, }, /// 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, }, } 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 indexes 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 LayoutSwitchTarget { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "next" => Ok(Self::Next), "prev" => Ok(Self::Prev), _ => Err(r#"invalid layout action, can be "next" or "prev""#), } } } 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)) } }