#![warn(missing_docs)]
use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
pub mod socket;
pub mod state;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum Request {
Version,
Outputs,
Workspaces,
Windows,
KeyboardLayouts,
FocusedOutput,
FocusedWindow,
Action(Action),
Output {
output: String,
action: OutputAction,
},
EventStream,
ReturnError,
}
pub type Reply = Result<Response, String>;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum Response {
Handled,
Version(String),
Outputs(HashMap<String, Output>),
Workspaces(Vec<Workspace>),
Windows(Vec<Window>),
KeyboardLayouts(KeyboardLayouts),
FocusedOutput(Option<Output>),
FocusedWindow(Option<Window>),
OutputConfigChanged(OutputConfigChanged),
}
#[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 {
Quit {
#[cfg_attr(feature = "clap", arg(short, long))]
skip_confirmation: bool,
},
PowerOffMonitors {},
PowerOnMonitors {},
Spawn {
#[cfg_attr(feature = "clap", arg(last = true, required = true))]
command: Vec<String>,
},
DoScreenTransition {
#[cfg_attr(feature = "clap", arg(short, long))]
delay_ms: Option<u16>,
},
Screenshot {},
ScreenshotScreen {},
#[cfg_attr(feature = "clap", clap(about = "Screenshot the focused window"))]
ScreenshotWindow {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
#[cfg_attr(feature = "clap", clap(about = "Close the focused window"))]
CloseWindow {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
#[cfg_attr(
feature = "clap",
clap(about = "Toggle fullscreen on the focused window")
)]
FullscreenWindow {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
FocusWindow {
#[cfg_attr(feature = "clap", arg(long))]
id: u64,
},
FocusColumnLeft {},
FocusColumnRight {},
FocusColumnFirst {},
FocusColumnLast {},
FocusColumnRightOrFirst {},
FocusColumnLeftOrLast {},
FocusWindowOrMonitorUp {},
FocusWindowOrMonitorDown {},
FocusColumnOrMonitorLeft {},
FocusColumnOrMonitorRight {},
FocusWindowDown {},
FocusWindowUp {},
FocusWindowDownOrColumnLeft {},
FocusWindowDownOrColumnRight {},
FocusWindowUpOrColumnLeft {},
FocusWindowUpOrColumnRight {},
FocusWindowOrWorkspaceDown {},
FocusWindowOrWorkspaceUp {},
MoveColumnLeft {},
MoveColumnRight {},
MoveColumnToFirst {},
MoveColumnToLast {},
MoveColumnLeftOrToMonitorLeft {},
MoveColumnRightOrToMonitorRight {},
MoveWindowDown {},
MoveWindowUp {},
MoveWindowDownOrToWorkspaceDown {},
MoveWindowUpOrToWorkspaceUp {},
#[cfg_attr(
feature = "clap",
clap(about = "Consume or expel the focused window left")
)]
ConsumeOrExpelWindowLeft {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
#[cfg_attr(
feature = "clap",
clap(about = "Consume or expel the focused window right")
)]
ConsumeOrExpelWindowRight {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
ConsumeWindowIntoColumn {},
ExpelWindowFromColumn {},
CenterColumn {},
FocusWorkspaceDown {},
FocusWorkspaceUp {},
FocusWorkspace {
#[cfg_attr(feature = "clap", arg())]
reference: WorkspaceReferenceArg,
},
FocusWorkspacePrevious {},
MoveWindowToWorkspaceDown {},
MoveWindowToWorkspaceUp {},
#[cfg_attr(
feature = "clap",
clap(about = "Move the focused window to a workspace by reference (index or name)")
)]
MoveWindowToWorkspace {
#[cfg_attr(feature = "clap", arg(long))]
window_id: Option<u64>,
#[cfg_attr(feature = "clap", arg())]
reference: WorkspaceReferenceArg,
},
MoveColumnToWorkspaceDown {},
MoveColumnToWorkspaceUp {},
MoveColumnToWorkspace {
#[cfg_attr(feature = "clap", arg())]
reference: WorkspaceReferenceArg,
},
MoveWorkspaceDown {},
MoveWorkspaceUp {},
FocusMonitorLeft {},
FocusMonitorRight {},
FocusMonitorDown {},
FocusMonitorUp {},
MoveWindowToMonitorLeft {},
MoveWindowToMonitorRight {},
MoveWindowToMonitorDown {},
MoveWindowToMonitorUp {},
MoveColumnToMonitorLeft {},
MoveColumnToMonitorRight {},
MoveColumnToMonitorDown {},
MoveColumnToMonitorUp {},
#[cfg_attr(
feature = "clap",
clap(about = "Change the height of the focused window")
)]
SetWindowHeight {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
#[cfg_attr(feature = "clap", arg())]
change: SizeChange,
},
#[cfg_attr(
feature = "clap",
clap(about = "Reset the height of the focused window back to automatic")
)]
ResetWindowHeight {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
SwitchPresetColumnWidth {},
SwitchPresetWindowHeight {
#[cfg_attr(feature = "clap", arg(long))]
id: Option<u64>,
},
MaximizeColumn {},
SetColumnWidth {
#[cfg_attr(feature = "clap", arg())]
change: SizeChange,
},
SwitchLayout {
#[cfg_attr(feature = "clap", arg())]
layout: LayoutSwitchTarget,
},
ShowHotkeyOverlay {},
MoveWorkspaceToMonitorLeft {},
MoveWorkspaceToMonitorRight {},
MoveWorkspaceToMonitorDown {},
MoveWorkspaceToMonitorUp {},
ToggleDebugTint {},
DebugToggleOpaqueRegions {},
DebugToggleDamage {},
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum SizeChange {
SetFixed(i32),
SetProportion(f64),
AdjustFixed(i32),
AdjustProportion(f64),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum WorkspaceReferenceArg {
Id(u64),
Index(u8),
Name(String),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum LayoutSwitchTarget {
Next,
Prev,
}
#[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 {
Off,
On,
Mode {
#[cfg_attr(feature = "clap", arg())]
mode: ModeToSet,
},
Scale {
#[cfg_attr(feature = "clap", arg())]
scale: ScaleToSet,
},
Transform {
#[cfg_attr(feature = "clap", arg())]
transform: Transform,
},
Position {
#[cfg_attr(feature = "clap", command(subcommand))]
position: PositionToSet,
},
Vrr {
#[cfg_attr(feature = "clap", command(flatten))]
vrr: VrrToSet,
},
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum ModeToSet {
Automatic,
Specific(ConfiguredMode),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct ConfiguredMode {
pub width: u16,
pub height: u16,
pub refresh: Option<f64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum ScaleToSet {
Automatic,
Specific(f64),
}
#[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 {
#[cfg_attr(feature = "clap", command(name = "auto"))]
Automatic,
#[cfg_attr(feature = "clap", command(name = "set"))]
Specific(ConfiguredPosition),
}
#[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 {
pub x: i32,
pub y: i32,
}
#[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 {
#[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,
#[cfg_attr(feature = "clap", arg(long))]
pub on_demand: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Output {
pub name: String,
pub make: String,
pub model: String,
pub serial: Option<String>,
pub physical_size: Option<(u32, u32)>,
pub modes: Vec<Mode>,
pub current_mode: Option<usize>,
pub vrr_supported: bool,
pub vrr_enabled: bool,
pub logical: Option<LogicalOutput>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Mode {
pub width: u16,
pub height: u16,
pub refresh_rate: u32,
pub is_preferred: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct LogicalOutput {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub scale: f64,
pub transform: Transform,
}
#[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 {
Normal,
#[serde(rename = "90")]
_90,
#[serde(rename = "180")]
_180,
#[serde(rename = "270")]
_270,
Flipped,
#[cfg_attr(feature = "clap", value(name("flipped-90")))]
Flipped90,
#[cfg_attr(feature = "clap", value(name("flipped-180")))]
Flipped180,
#[cfg_attr(feature = "clap", value(name("flipped-270")))]
Flipped270,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Window {
pub id: u64,
pub title: Option<String>,
pub app_id: Option<String>,
pub workspace_id: Option<u64>,
pub is_focused: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum OutputConfigChanged {
Applied,
OutputWasMissing,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct Workspace {
pub id: u64,
pub idx: u8,
pub name: Option<String>,
pub output: Option<String>,
pub is_active: bool,
pub is_focused: bool,
pub active_window_id: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct KeyboardLayouts {
pub names: Vec<String>,
pub current_idx: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum Event {
WorkspacesChanged {
workspaces: Vec<Workspace>,
},
WorkspaceActivated {
id: u64,
focused: bool,
},
WorkspaceActiveWindowChanged {
workspace_id: u64,
active_window_id: Option<u64>,
},
WindowsChanged {
windows: Vec<Window>,
},
WindowOpenedOrChanged {
window: Window,
},
WindowClosed {
id: u64,
},
WindowFocusChanged {
id: Option<u64>,
},
KeyboardLayoutsChanged {
keyboard_layouts: KeyboardLayouts,
},
KeyboardLayoutSwitched {
idx: u8,
},
}
impl FromStr for WorkspaceReferenceArg {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let reference = if let Ok(index) = s.parse::<i32>() {
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<Self, Self::Err> {
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<Self, Self::Err> {
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<Self, Self::Err> {
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<Self, Self::Err> {
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<Self, Self::Err> {
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<Self, Self::Err> {
if s.eq_ignore_ascii_case("auto") {
return Ok(Self::Automatic);
}
let scale = s.parse().map_err(|_| "error parsing scale")?;
Ok(Self::Specific(scale))
}
}