//! Wire protocol shared between the `quibble` compositor daemon and the //! `quibblectl` CLI. //! //! The transport is newline-delimited JSON over a Unix-domain socket: the //! client writes one [`Request`] as a single JSON line and reads back one //! [`Response`] line. Keeping the types here makes this module the single //! source of truth for the format used by both binaries. use serde::{Deserialize, Serialize}; /// Stable identifier assigned to a toplevel window when the client creates it. /// /// This is deliberately independent of any Wayland object id so that the CLI /// has a value it can hold onto across requests. Ids increase with creation /// order, which is also the order the compositor lays windows out in. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct WindowId(pub u64); /// An axis-aligned rectangle in output-logical coordinates. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Rect { pub x: i32, pub y: i32, pub w: i32, pub h: i32, } /// The compositor's window-arrangement policy. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Layout { /// One near-fullscreen toplevel at a time, popups floating on top. Tiled, /// Freely overlapping toplevels, z-order following focus. Floating, } /// Press/release state for a pointer or touch button. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ButtonState { Pressed, Released, } /// Press/release state for a keyboard key. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum KeyState { Pressed, Released, } /// Scroll axis for a pointer axis (wheel) event. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Axis { Vertical, Horizontal, } /// Metadata describing a toplevel, returned by introspection requests. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WindowInfo { pub id: WindowId, #[serde(default)] pub app_id: Option, #[serde(default)] pub title: Option, pub geometry: Rect, pub focused: bool, /// Whether the client has presented a buffer. A toplevel exists from the /// moment the client creates it, but is not on screen — and has no /// geometry — until it draws into it for the first time. pub mapped: bool, } /// A command sent from the CLI to the compositor. /// /// Coordinates in pointer/touch requests are absolute, in output-logical /// space; the compositor resolves which surface they land on. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Request { /// Liveness check. Ping, /// Composite the current scene and return it as a PNG. Screenshot { /// Restrict the capture to this region; `None` captures the whole output. #[serde(default)] region: Option, }, /// Move the pointer to an absolute location. PointerMotion { x: f64, y: f64 }, /// Press or release a pointer button (evdev button code, e.g. `0x110` = left). PointerButton { button: u32, state: ButtonState }, /// Emit a scroll event. PointerAxis { axis: Axis, value: f64 }, /// Press or release a key (raw evdev keycode, i.e. xkb keycode minus 8). Key { keycode: u32, state: KeyState }, /// Type a UTF-8 string; the compositor maps characters to key events using /// its active keymap, pressing/releasing modifiers as needed. Text { text: String }, /// Begin a touch contact in a given slot. TouchDown { slot: u32, x: f64, y: f64 }, /// Move an existing touch contact. TouchMotion { slot: u32, x: f64, y: f64 }, /// End a touch contact. TouchUp { slot: u32 }, /// Cancel all active touch contacts. TouchCancel, /// List every mapped toplevel. ListWindows, /// Block until a toplevel matching the given filters is mapped, or the /// timeout elapses. An empty filter matches the next window to map. WaitForWindow { #[serde(default)] app_id: Option, #[serde(default)] title: Option, timeout_ms: u64, }, /// Give keyboard focus to a specific window and raise it. FocusWindow { id: WindowId }, /// Move a window (floating layout only). MoveWindow { id: WindowId, x: i32, y: i32 }, /// Resize a window (floating layout only). ResizeWindow { id: WindowId, w: i32, h: i32 }, /// Switch the arrangement policy and relayout existing windows. SetLayout { layout: Layout }, /// Resize/rescale the virtual output. SetOutput { width: i32, height: i32, scale: f64 }, /// Read the current clipboard (primary selection text). ClipboardGet, /// Set the clipboard selection to the given text. ClipboardSet { text: String }, /// Open (`true`) or close (`false`) the monitor window. Monitor { on: bool }, /// Ask the compositor to shut down. Quit, } /// The compositor's reply to a [`Request`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Response { /// Generic success for requests without a payload. Ok, /// Reply to [`Request::Ping`]. Pong, /// A captured screenshot. Screenshot { png: Png }, /// Reply to [`Request::ListWindows`]. Windows(Vec), /// The window matched by [`Request::WaitForWindow`]. Window(WindowInfo), /// Reply to [`Request::ClipboardGet`]; `None` when the selection is empty. Clipboard { text: Option }, /// A [`Request::WaitForWindow`] whose timeout elapsed with no match. Timeout, /// The request could not be satisfied. Error { message: String }, } /// A binary blob (a PNG image) transported as base64 so it stays compact in /// the JSON envelope instead of exploding into an array of integers. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Png(pub Vec); impl Serialize for Png { fn serialize(&self, serializer: S) -> Result { use base64::Engine as _; serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(&self.0)) } } impl<'de> Deserialize<'de> for Png { fn deserialize>(deserializer: D) -> Result { use base64::Engine as _; let s = String::deserialize(deserializer)?; base64::engine::general_purpose::STANDARD .decode(s.as_bytes()) .map(Png) .map_err(serde::de::Error::custom) } } impl Request { /// Serialize to a single newline-terminated JSON line. pub fn to_line(&self) -> String { let mut s = serde_json::to_string(self).expect("Request serialization is infallible"); s.push('\n'); s } } impl Response { /// Serialize to a single newline-terminated JSON line. pub fn to_line(&self) -> String { let mut s = serde_json::to_string(self).expect("Response serialization is infallible"); s.push('\n'); s } }