atagen

monolith

public
Star0
Watch0
509842c1b6dc0e9d48985cb5aa8d691a1b2301ee
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/ipc/src/lib.rs

fe835aed6c8datagen2026-10-16 10:05 UTC1
//! The control protocol. Newline-delimited JSON over a unix socket, one
fe835aed6c8datagen2026-10-16 10:05 UTC2
//! message per line, each carrying the protocol version `v`.
fe835aed6c8datagen2026-10-16 10:05 UTC3
//!
3b180fd69834atagen2026-10-17 10:40 UTC4
//! Commands also have a word form, `focus left` or `spawn beer`, shared by
fe835aed6c8datagen2026-10-16 10:05 UTC5
//! the CLI and keybindings in the config.
57f44a63d99fatagen2026-10-16 05:34 UTC6
fe835aed6c8datagen2026-10-16 10:05 UTC7
use serde::{Deserialize, Serialize};
fe835aed6c8datagen2026-10-16 10:05 UTC8
use std::fmt;
fe835aed6c8datagen2026-10-16 10:05 UTC9
use std::path::PathBuf;
fe835aed6c8datagen2026-10-16 10:05 UTC10
fe835aed6c8datagen2026-10-16 10:05 UTC11
pub const VERSION: u32 = 1;
fe835aed6c8datagen2026-10-16 10:05 UTC12
fe835aed6c8datagen2026-10-16 10:05 UTC13
/// Where the compositor serving `display` listens.
fe835aed6c8datagen2026-10-16 10:05 UTC14
pub fn socket_path_for(display: &str) -> Option<PathBuf> {
fe835aed6c8datagen2026-10-16 10:05 UTC15
    let dir = std::env::var_os("XDG_RUNTIME_DIR")?;
fe835aed6c8datagen2026-10-16 10:05 UTC16
    Some(PathBuf::from(dir).join(format!("monolith-{display}.sock")))
fe835aed6c8datagen2026-10-16 10:05 UTC17
}
fe835aed6c8datagen2026-10-16 10:05 UTC18
fe835aed6c8datagen2026-10-16 10:05 UTC19
/// Where a client should connect. `$MONOLITH_SOCKET`, else the socket of
fe835aed6c8datagen2026-10-16 10:05 UTC20
/// the compositor serving `$WAYLAND_DISPLAY`.
fe835aed6c8datagen2026-10-16 10:05 UTC21
pub fn socket_path() -> Option<PathBuf> {
fe835aed6c8datagen2026-10-16 10:05 UTC22
    if let Some(p) = std::env::var_os("MONOLITH_SOCKET") {
fe835aed6c8datagen2026-10-16 10:05 UTC23
        return Some(p.into());
fe835aed6c8datagen2026-10-16 10:05 UTC24
    }
fe835aed6c8datagen2026-10-16 10:05 UTC25
    socket_path_for(&std::env::var("WAYLAND_DISPLAY").ok()?)
fe835aed6c8datagen2026-10-16 10:05 UTC26
}
fe835aed6c8datagen2026-10-16 10:05 UTC27
b22c0511d5afatagen2026-10-17 03:48 UTC28
pub use monolith_layout::{Axis, Dir};
fe835aed6c8datagen2026-10-16 10:05 UTC29
fe835aed6c8datagen2026-10-16 10:05 UTC30
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
fe835aed6c8datagen2026-10-16 10:05 UTC31
#[serde(rename_all = "kebab-case")]
fe835aed6c8datagen2026-10-16 10:05 UTC32
pub enum ExpandScope {
fe835aed6c8datagen2026-10-16 10:05 UTC33
    #[default]
fe835aed6c8datagen2026-10-16 10:05 UTC34
    Window,
4444c779eb48atagen2026-10-16 12:07 UTC35
    Container,
fe835aed6c8datagen2026-10-16 10:05 UTC36
}
fe835aed6c8datagen2026-10-16 10:05 UTC37
fe835aed6c8datagen2026-10-16 10:05 UTC38
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC39
#[serde(tag = "command", rename_all = "kebab-case", deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC40
pub enum Command {
fe835aed6c8datagen2026-10-16 10:05 UTC41
    Spawn {
fe835aed6c8datagen2026-10-16 10:05 UTC42
        argv: Vec<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC43
    },
fe835aed6c8datagen2026-10-16 10:05 UTC44
    Close,
fe835aed6c8datagen2026-10-16 10:05 UTC45
    Quit,
fe835aed6c8datagen2026-10-16 10:05 UTC46
    Reload,
fe835aed6c8datagen2026-10-16 10:05 UTC47
    Focus {
fe835aed6c8datagen2026-10-16 10:05 UTC48
        dir: Dir,
fe835aed6c8datagen2026-10-16 10:05 UTC49
    },
af095d8ce0fcatagen2026-10-28 04:02 UTC50
    /// Focuses the latest window on the other layer, floating or tiled.
af095d8ce0fcatagen2026-10-28 04:02 UTC51
    FocusLayer,
fe835aed6c8datagen2026-10-16 10:05 UTC52
    Move {
fe835aed6c8datagen2026-10-16 10:05 UTC53
        dir: Dir,
fe835aed6c8datagen2026-10-16 10:05 UTC54
    },
4444c779eb48atagen2026-10-16 12:07 UTC55
    MoveContainer {
fe835aed6c8datagen2026-10-16 10:05 UTC56
        dir: Dir,
fe835aed6c8datagen2026-10-16 10:05 UTC57
    },
fe835aed6c8datagen2026-10-16 10:05 UTC58
    Resize {
fe835aed6c8datagen2026-10-16 10:05 UTC59
        axis: Axis,
fe835aed6c8datagen2026-10-16 10:05 UTC60
        delta: f64,
fe835aed6c8datagen2026-10-16 10:05 UTC61
    },
b2274115d1c6atagen2026-10-22 10:05 UTC62
    /// Shares the focused window's block equally among its children.
b2274115d1c6atagen2026-10-22 10:05 UTC63
    BalanceBlock,
fe835aed6c8datagen2026-10-16 10:05 UTC64
    Expand {
fe835aed6c8datagen2026-10-16 10:05 UTC65
        scope: ExpandScope,
fe835aed6c8datagen2026-10-16 10:05 UTC66
    },
4444c779eb48atagen2026-10-16 12:07 UTC67
    /// Expands every container above the focused window towards it.
4444c779eb48atagen2026-10-16 12:07 UTC68
    Maximize,
4444c779eb48atagen2026-10-16 12:07 UTC69
    /// Makes the next window move do the opposite of the configured default,
4444c779eb48atagen2026-10-16 12:07 UTC70
    /// swap or merge. Again before moving cancels it.
4444c779eb48atagen2026-10-16 12:07 UTC71
    InvertMove,
fe835aed6c8datagen2026-10-16 10:05 UTC72
    Flip,
fe835aed6c8datagen2026-10-16 10:05 UTC73
    Float,
fe835aed6c8datagen2026-10-16 10:05 UTC74
    Fullscreen,
fe835aed6c8datagen2026-10-16 10:05 UTC75
    Sticky,
fe835aed6c8datagen2026-10-16 10:05 UTC76
    View {
fe835aed6c8datagen2026-10-16 10:05 UTC77
        tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC78
    },
fe835aed6c8datagen2026-10-16 10:05 UTC79
    ToggleView {
fe835aed6c8datagen2026-10-16 10:05 UTC80
        tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC81
    },
fe835aed6c8datagen2026-10-16 10:05 UTC82
    ViewEverywhere {
fe835aed6c8datagen2026-10-16 10:05 UTC83
        tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC84
    },
fe835aed6c8datagen2026-10-16 10:05 UTC85
    /// Sends the focused window to a tag.
fe835aed6c8datagen2026-10-16 10:05 UTC86
    Tag {
fe835aed6c8datagen2026-10-16 10:05 UTC87
        tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC88
    },
fe835aed6c8datagen2026-10-16 10:05 UTC89
    ReorderTag {
fe835aed6c8datagen2026-10-16 10:05 UTC90
        tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC91
        index: usize,
fe835aed6c8datagen2026-10-16 10:05 UTC92
    },
fe835aed6c8datagen2026-10-16 10:05 UTC93
    FocusOutput {
fe835aed6c8datagen2026-10-16 10:05 UTC94
        dir: Dir,
fe835aed6c8datagen2026-10-16 10:05 UTC95
    },
fe835aed6c8datagen2026-10-16 10:05 UTC96
    SendToOutput {
fe835aed6c8datagen2026-10-16 10:05 UTC97
        dir: Dir,
fe835aed6c8datagen2026-10-16 10:05 UTC98
    },
fe835aed6c8datagen2026-10-16 10:05 UTC99
    /// Adds the focused window to a scratchpad.
fe835aed6c8datagen2026-10-16 10:05 UTC100
    Scratchpad {
fe835aed6c8datagen2026-10-16 10:05 UTC101
        name: String,
fe835aed6c8datagen2026-10-16 10:05 UTC102
    },
fe835aed6c8datagen2026-10-16 10:05 UTC103
    Summon {
fe835aed6c8datagen2026-10-16 10:05 UTC104
        name: String,
fe835aed6c8datagen2026-10-16 10:05 UTC105
    },
fe835aed6c8datagen2026-10-16 10:05 UTC106
    Dismiss {
fe835aed6c8datagen2026-10-16 10:05 UTC107
        name: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC108
    },
140b53f6ef84atagen2026-10-19 07:43 UTC109
    /// Switches outputs on or off, all of them without a name.
140b53f6ef84atagen2026-10-19 07:43 UTC110
    Power {
140b53f6ef84atagen2026-10-19 07:43 UTC111
        on: bool,
140b53f6ef84atagen2026-10-19 07:43 UTC112
        #[serde(default)]
140b53f6ef84atagen2026-10-19 07:43 UTC113
        output: Option<String>,
140b53f6ef84atagen2026-10-19 07:43 UTC114
    },
fe835aed6c8datagen2026-10-16 10:05 UTC115
}
fe835aed6c8datagen2026-10-16 10:05 UTC116
fe835aed6c8datagen2026-10-16 10:05 UTC117
#[derive(Debug, Clone, PartialEq, Eq)]
fe835aed6c8datagen2026-10-16 10:05 UTC118
pub struct ParseError(pub String);
fe835aed6c8datagen2026-10-16 10:05 UTC119
fe835aed6c8datagen2026-10-16 10:05 UTC120
impl fmt::Display for ParseError {
fe835aed6c8datagen2026-10-16 10:05 UTC121
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fe835aed6c8datagen2026-10-16 10:05 UTC122
        f.write_str(&self.0)
fe835aed6c8datagen2026-10-16 10:05 UTC123
    }
fe835aed6c8datagen2026-10-16 10:05 UTC124
}
fe835aed6c8datagen2026-10-16 10:05 UTC125
fe835aed6c8datagen2026-10-16 10:05 UTC126
impl std::error::Error for ParseError {}
fe835aed6c8datagen2026-10-16 10:05 UTC127
fe835aed6c8datagen2026-10-16 10:05 UTC128
impl Command {
41569aa8395fatagen2026-10-19 07:35 UTC129
    /// Whether a bind running this still fires on a locked session, which
41569aa8395fatagen2026-10-19 07:35 UTC130
    /// leaves out everything that moves focus, windows or tags, and quitting.
41569aa8395fatagen2026-10-19 07:35 UTC131
    pub fn allowed_while_locked(&self) -> bool {
41569aa8395fatagen2026-10-19 07:35 UTC132
        matches!(self, Command::Spawn { .. } | Command::Reload)
41569aa8395fatagen2026-10-19 07:35 UTC133
    }
41569aa8395fatagen2026-10-19 07:35 UTC134
4444c779eb48atagen2026-10-16 12:07 UTC135
    /// Parses the word form, as in `["move-container", "left"]`.
fe835aed6c8datagen2026-10-16 10:05 UTC136
    pub fn parse<S: AsRef<str>>(words: &[S]) -> Result<Self, ParseError> {
fe835aed6c8datagen2026-10-16 10:05 UTC137
        let words: Vec<&str> = words.iter().map(AsRef::as_ref).collect();
fe835aed6c8datagen2026-10-16 10:05 UTC138
        let Some((&name, args)) = words.split_first() else {
fe835aed6c8datagen2026-10-16 10:05 UTC139
            return Err(ParseError("empty command".into()));
fe835aed6c8datagen2026-10-16 10:05 UTC140
        };
fe835aed6c8datagen2026-10-16 10:05 UTC141
        let err = |m: &str| ParseError(format!("{name}: {m}"));
fe835aed6c8datagen2026-10-16 10:05 UTC142
        let one = || match args {
fe835aed6c8datagen2026-10-16 10:05 UTC143
            [a] => Ok(a.to_string()),
fe835aed6c8datagen2026-10-16 10:05 UTC144
            _ => Err(err("takes exactly one argument")),
fe835aed6c8datagen2026-10-16 10:05 UTC145
        };
fe835aed6c8datagen2026-10-16 10:05 UTC146
        let none = |c: Command| {
fe835aed6c8datagen2026-10-16 10:05 UTC147
            if args.is_empty() {
fe835aed6c8datagen2026-10-16 10:05 UTC148
                Ok(c)
fe835aed6c8datagen2026-10-16 10:05 UTC149
            } else {
fe835aed6c8datagen2026-10-16 10:05 UTC150
                Err(err("takes no arguments"))
fe835aed6c8datagen2026-10-16 10:05 UTC151
            }
fe835aed6c8datagen2026-10-16 10:05 UTC152
        };
fe835aed6c8datagen2026-10-16 10:05 UTC153
        let dir = || -> Result<Dir, ParseError> {
fe835aed6c8datagen2026-10-16 10:05 UTC154
            match one()?.as_str() {
fe835aed6c8datagen2026-10-16 10:05 UTC155
                "left" | "h" => Ok(Dir::Left),
fe835aed6c8datagen2026-10-16 10:05 UTC156
                "right" | "l" => Ok(Dir::Right),
fe835aed6c8datagen2026-10-16 10:05 UTC157
                "up" | "k" => Ok(Dir::Up),
fe835aed6c8datagen2026-10-16 10:05 UTC158
                "down" | "j" => Ok(Dir::Down),
fe835aed6c8datagen2026-10-16 10:05 UTC159
                d => Err(err(&format!("unknown direction {d}"))),
fe835aed6c8datagen2026-10-16 10:05 UTC160
            }
fe835aed6c8datagen2026-10-16 10:05 UTC161
        };
fe835aed6c8datagen2026-10-16 10:05 UTC162
        Ok(match name {
fe835aed6c8datagen2026-10-16 10:05 UTC163
            "spawn" if !args.is_empty() => Command::Spawn {
fe835aed6c8datagen2026-10-16 10:05 UTC164
                argv: args.iter().map(|a| a.to_string()).collect(),
fe835aed6c8datagen2026-10-16 10:05 UTC165
            },
fe835aed6c8datagen2026-10-16 10:05 UTC166
            "spawn" => return Err(err("needs a program")),
fe835aed6c8datagen2026-10-16 10:05 UTC167
            "close" => none(Command::Close)?,
fe835aed6c8datagen2026-10-16 10:05 UTC168
            "quit" => none(Command::Quit)?,
fe835aed6c8datagen2026-10-16 10:05 UTC169
            "reload" => none(Command::Reload)?,
fe835aed6c8datagen2026-10-16 10:05 UTC170
            "flip" => none(Command::Flip)?,
4444c779eb48atagen2026-10-16 12:07 UTC171
            "maximize" => none(Command::Maximize)?,
4444c779eb48atagen2026-10-16 12:07 UTC172
            "invert-move" => none(Command::InvertMove)?,
b2274115d1c6atagen2026-10-22 10:05 UTC173
            "balance-block" => none(Command::BalanceBlock)?,
fe835aed6c8datagen2026-10-16 10:05 UTC174
            "float" => none(Command::Float)?,
fe835aed6c8datagen2026-10-16 10:05 UTC175
            "fullscreen" => none(Command::Fullscreen)?,
fe835aed6c8datagen2026-10-16 10:05 UTC176
            "sticky" => none(Command::Sticky)?,
fe835aed6c8datagen2026-10-16 10:05 UTC177
            "focus" => Command::Focus { dir: dir()? },
af095d8ce0fcatagen2026-10-28 04:02 UTC178
            "focus-layer" => none(Command::FocusLayer)?,
fe835aed6c8datagen2026-10-16 10:05 UTC179
            "move" => Command::Move { dir: dir()? },
4444c779eb48atagen2026-10-16 12:07 UTC180
            "move-container" => Command::MoveContainer { dir: dir()? },
fe835aed6c8datagen2026-10-16 10:05 UTC181
            "focus-output" => Command::FocusOutput { dir: dir()? },
fe835aed6c8datagen2026-10-16 10:05 UTC182
            "send-to-output" => Command::SendToOutput { dir: dir()? },
fe835aed6c8datagen2026-10-16 10:05 UTC183
            "resize" => {
fe835aed6c8datagen2026-10-16 10:05 UTC184
                let [axis, delta] = args else {
fe835aed6c8datagen2026-10-16 10:05 UTC185
                    return Err(err("takes an axis and a delta"));
fe835aed6c8datagen2026-10-16 10:05 UTC186
                };
fe835aed6c8datagen2026-10-16 10:05 UTC187
                let axis = match *axis {
fe835aed6c8datagen2026-10-16 10:05 UTC188
                    "horizontal" | "h" => Axis::Horizontal,
fe835aed6c8datagen2026-10-16 10:05 UTC189
                    "vertical" | "v" => Axis::Vertical,
fe835aed6c8datagen2026-10-16 10:05 UTC190
                    a => return Err(err(&format!("unknown axis {a}"))),
fe835aed6c8datagen2026-10-16 10:05 UTC191
                };
fe835aed6c8datagen2026-10-16 10:05 UTC192
                let delta = delta
fe835aed6c8datagen2026-10-16 10:05 UTC193
                    .parse()
fe835aed6c8datagen2026-10-16 10:05 UTC194
                    .map_err(|_| err(&format!("bad delta {delta}")))?;
fe835aed6c8datagen2026-10-16 10:05 UTC195
                Command::Resize { axis, delta }
fe835aed6c8datagen2026-10-16 10:05 UTC196
            }
fe835aed6c8datagen2026-10-16 10:05 UTC197
            "expand" => Command::Expand {
fe835aed6c8datagen2026-10-16 10:05 UTC198
                scope: match args {
fe835aed6c8datagen2026-10-16 10:05 UTC199
                    [] | ["window"] => ExpandScope::Window,
4444c779eb48atagen2026-10-16 12:07 UTC200
                    ["container"] => ExpandScope::Container,
4444c779eb48atagen2026-10-16 12:07 UTC201
                    _ => return Err(err("takes window or container")),
fe835aed6c8datagen2026-10-16 10:05 UTC202
                },
fe835aed6c8datagen2026-10-16 10:05 UTC203
            },
fe835aed6c8datagen2026-10-16 10:05 UTC204
            "view" => Command::View { tag: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC205
            "toggle-view" => Command::ToggleView { tag: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC206
            "view-everywhere" => Command::ViewEverywhere { tag: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC207
            "tag" => Command::Tag { tag: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC208
            "reorder-tag" => {
fe835aed6c8datagen2026-10-16 10:05 UTC209
                let [tag, index] = args else {
fe835aed6c8datagen2026-10-16 10:05 UTC210
                    return Err(err("takes a tag and an index"));
fe835aed6c8datagen2026-10-16 10:05 UTC211
                };
fe835aed6c8datagen2026-10-16 10:05 UTC212
                let index = index
fe835aed6c8datagen2026-10-16 10:05 UTC213
                    .parse()
fe835aed6c8datagen2026-10-16 10:05 UTC214
                    .map_err(|_| err(&format!("bad index {index}")))?;
fe835aed6c8datagen2026-10-16 10:05 UTC215
                Command::ReorderTag {
fe835aed6c8datagen2026-10-16 10:05 UTC216
                    tag: tag.to_string(),
fe835aed6c8datagen2026-10-16 10:05 UTC217
                    index,
fe835aed6c8datagen2026-10-16 10:05 UTC218
                }
fe835aed6c8datagen2026-10-16 10:05 UTC219
            }
fe835aed6c8datagen2026-10-16 10:05 UTC220
            "scratchpad" => Command::Scratchpad { name: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC221
            "summon" => Command::Summon { name: one()? },
fe835aed6c8datagen2026-10-16 10:05 UTC222
            "dismiss" => Command::Dismiss {
fe835aed6c8datagen2026-10-16 10:05 UTC223
                name: match args {
fe835aed6c8datagen2026-10-16 10:05 UTC224
                    [] => None,
fe835aed6c8datagen2026-10-16 10:05 UTC225
                    [n] => Some(n.to_string()),
fe835aed6c8datagen2026-10-16 10:05 UTC226
                    _ => return Err(err("takes at most one name")),
fe835aed6c8datagen2026-10-16 10:05 UTC227
                },
fe835aed6c8datagen2026-10-16 10:05 UTC228
            },
140b53f6ef84atagen2026-10-19 07:43 UTC229
            "power" => {
140b53f6ef84atagen2026-10-19 07:43 UTC230
                let (on, output) = match args {
140b53f6ef84atagen2026-10-19 07:43 UTC231
                    ["on"] => (true, None),
140b53f6ef84atagen2026-10-19 07:43 UTC232
                    ["off"] => (false, None),
140b53f6ef84atagen2026-10-19 07:43 UTC233
                    ["on", o] => (true, Some(o.to_string())),
140b53f6ef84atagen2026-10-19 07:43 UTC234
                    ["off", o] => (false, Some(o.to_string())),
140b53f6ef84atagen2026-10-19 07:43 UTC235
                    _ => return Err(err("takes on or off and optionally an output")),
140b53f6ef84atagen2026-10-19 07:43 UTC236
                };
140b53f6ef84atagen2026-10-19 07:43 UTC237
                Command::Power { on, output }
140b53f6ef84atagen2026-10-19 07:43 UTC238
            }
fe835aed6c8datagen2026-10-16 10:05 UTC239
            _ => return Err(ParseError(format!("unknown command {name}"))),
fe835aed6c8datagen2026-10-16 10:05 UTC240
        })
fe835aed6c8datagen2026-10-16 10:05 UTC241
    }
fe835aed6c8datagen2026-10-16 10:05 UTC242
}
fe835aed6c8datagen2026-10-16 10:05 UTC243
fe835aed6c8datagen2026-10-16 10:05 UTC244
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC245
#[serde(rename_all = "kebab-case")]
fe835aed6c8datagen2026-10-16 10:05 UTC246
pub enum Request {
fe835aed6c8datagen2026-10-16 10:05 UTC247
    Run(Command),
fe835aed6c8datagen2026-10-16 10:05 UTC248
    Get(Query),
fe835aed6c8datagen2026-10-16 10:05 UTC249
    /// Streams a `State` event after every change until the client hangs up.
fe835aed6c8datagen2026-10-16 10:05 UTC250
    Subscribe {},
fe835aed6c8datagen2026-10-16 10:05 UTC251
}
fe835aed6c8datagen2026-10-16 10:05 UTC252
fe835aed6c8datagen2026-10-16 10:05 UTC253
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
fe835aed6c8datagen2026-10-16 10:05 UTC254
#[serde(rename_all = "kebab-case")]
fe835aed6c8datagen2026-10-16 10:05 UTC255
pub enum Query {
fe835aed6c8datagen2026-10-16 10:05 UTC256
    State,
fe835aed6c8datagen2026-10-16 10:05 UTC257
    Version,
01e6a6d74d49atagen2026-10-19 10:40 UTC258
    Warnings,
fe835aed6c8datagen2026-10-16 10:05 UTC259
}
fe835aed6c8datagen2026-10-16 10:05 UTC260
fe835aed6c8datagen2026-10-16 10:05 UTC261
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC262
pub struct Envelope<T> {
fe835aed6c8datagen2026-10-16 10:05 UTC263
    pub v: u32,
fe835aed6c8datagen2026-10-16 10:05 UTC264
    #[serde(default, skip_serializing_if = "Option::is_none")]
fe835aed6c8datagen2026-10-16 10:05 UTC265
    pub id: Option<u64>,
fe835aed6c8datagen2026-10-16 10:05 UTC266
    #[serde(flatten)]
fe835aed6c8datagen2026-10-16 10:05 UTC267
    pub body: T,
fe835aed6c8datagen2026-10-16 10:05 UTC268
}
fe835aed6c8datagen2026-10-16 10:05 UTC269
fe835aed6c8datagen2026-10-16 10:05 UTC270
impl<T> Envelope<T> {
fe835aed6c8datagen2026-10-16 10:05 UTC271
    pub fn new(id: Option<u64>, body: T) -> Self {
fe835aed6c8datagen2026-10-16 10:05 UTC272
        Self {
fe835aed6c8datagen2026-10-16 10:05 UTC273
            v: VERSION,
fe835aed6c8datagen2026-10-16 10:05 UTC274
            id,
fe835aed6c8datagen2026-10-16 10:05 UTC275
            body,
fe835aed6c8datagen2026-10-16 10:05 UTC276
        }
fe835aed6c8datagen2026-10-16 10:05 UTC277
    }
fe835aed6c8datagen2026-10-16 10:05 UTC278
}
fe835aed6c8datagen2026-10-16 10:05 UTC279
fe835aed6c8datagen2026-10-16 10:05 UTC280
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC281
#[serde(rename_all = "kebab-case")]
fe835aed6c8datagen2026-10-16 10:05 UTC282
pub enum Reply {
fe835aed6c8datagen2026-10-16 10:05 UTC283
    Ok(Option<Answer>),
fe835aed6c8datagen2026-10-16 10:05 UTC284
    Error(String),
fe835aed6c8datagen2026-10-16 10:05 UTC285
    Event(State),
fe835aed6c8datagen2026-10-16 10:05 UTC286
}
fe835aed6c8datagen2026-10-16 10:05 UTC287
fe835aed6c8datagen2026-10-16 10:05 UTC288
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC289
#[serde(rename_all = "kebab-case")]
fe835aed6c8datagen2026-10-16 10:05 UTC290
pub enum Answer {
fe835aed6c8datagen2026-10-16 10:05 UTC291
    State(State),
fe835aed6c8datagen2026-10-16 10:05 UTC292
    Version(String),
ad802fd23506atagen2026-10-19 07:28 UTC293
    Warnings(Vec<String>),
fe835aed6c8datagen2026-10-16 10:05 UTC294
}
fe835aed6c8datagen2026-10-16 10:05 UTC295
fe835aed6c8datagen2026-10-16 10:05 UTC296
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
fe835aed6c8datagen2026-10-16 10:05 UTC297
pub struct State {
4444c779eb48atagen2026-10-16 12:07 UTC298
    /// The next window move does the opposite of the configured default.
4444c779eb48atagen2026-10-16 12:07 UTC299
    pub move_inverted: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC300
    pub tags: Vec<Tag>,
fe835aed6c8datagen2026-10-16 10:05 UTC301
    pub outputs: Vec<Output>,
fe835aed6c8datagen2026-10-16 10:05 UTC302
    pub windows: Vec<Window>,
fe835aed6c8datagen2026-10-16 10:05 UTC303
}
fe835aed6c8datagen2026-10-16 10:05 UTC304
fe835aed6c8datagen2026-10-16 10:05 UTC305
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC306
pub struct Tag {
fe835aed6c8datagen2026-10-16 10:05 UTC307
    pub name: String,
fe835aed6c8datagen2026-10-16 10:05 UTC308
    pub urgent: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC309
}
fe835aed6c8datagen2026-10-16 10:05 UTC310
fe835aed6c8datagen2026-10-16 10:05 UTC311
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC312
pub struct Output {
fe835aed6c8datagen2026-10-16 10:05 UTC313
    pub name: String,
fe835aed6c8datagen2026-10-16 10:05 UTC314
    /// x, y, width, height in logical pixels.
fe835aed6c8datagen2026-10-16 10:05 UTC315
    pub area: [f64; 4],
fe835aed6c8datagen2026-10-16 10:05 UTC316
    pub viewed: Vec<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC317
    pub active: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC318
}
fe835aed6c8datagen2026-10-16 10:05 UTC319
fe835aed6c8datagen2026-10-16 10:05 UTC320
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC321
pub struct Window {
fe835aed6c8datagen2026-10-16 10:05 UTC322
    pub id: u64,
fe835aed6c8datagen2026-10-16 10:05 UTC323
    pub app_id: String,
fe835aed6c8datagen2026-10-16 10:05 UTC324
    pub title: String,
fe835aed6c8datagen2026-10-16 10:05 UTC325
    pub tag: String,
fe835aed6c8datagen2026-10-16 10:05 UTC326
    pub output: Option<String>,
f89d02f177faatagen2026-10-19 10:38 UTC327
    /// x, y, width, height in logical pixels, while placed on an output.
f89d02f177faatagen2026-10-19 10:38 UTC328
    #[serde(default)]
f89d02f177faatagen2026-10-19 10:38 UTC329
    pub area: Option<[f64; 4]>,
fe835aed6c8datagen2026-10-16 10:05 UTC330
    pub floating: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC331
    pub fullscreen: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC332
    pub sticky: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC333
    pub scratchpad: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC334
    pub visible: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC335
    pub focused: bool,
9fdf9584d2bdatagen2026-10-19 07:38 UTC336
    pub urgent: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC337
}