git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit67c353ac31f0844bd83cc53efbc00f18b83d1296
authorLucas Galante <[email protected]>
date2026-05-10 22:04
init: cargo project with wayland protocol codegen

 .gitignore                              |    1 +
 Cargo.lock                              |  381 +++++++
 Cargo.toml                              |   22 +
 build.rs                                |    7 +
 protocol/river-input-management-v1.xml  |  234 ++++
 protocol/river-layer-shell-v1.xml       |  191 ++++
 protocol/river-window-management-v1.xml | 1854 +++++++++++++++++++++++++++++++
 protocol/river-xkb-bindings-v1.xml      |  268 +++++
 src/clearctl.rs                         |    3 +
 src/main.rs                             |    3 +
 10 files changed, 2964 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..c2f0b75
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,381 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "cc"
+version = "1.2.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "clearwm"
+version = "0.1.0"
+dependencies = [
+ "libc",
+ "nix",
+ "serde",
+ "toml",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+ "xkbcommon",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "memmap2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.39.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
+dependencies = [
+ "bitflags",
+ "rustix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
+dependencies = [
+ "bitflags",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "xkbcommon"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
+dependencies = [
+ "libc",
+ "memmap2",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..66543bc
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "clearwm"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "clearwm"
+path = "src/main.rs"
+
+[[bin]]
+name = "clearctl"
+path = "src/clearctl.rs"
+
+[dependencies]
+wayland-client = "0.31"
+wayland-protocols = { version = "0.32", features = ["client", "unstable"] }
+wayland-scanner = "0.31"
+xkbcommon = "0.7"
+toml = "0.8"
+serde = { version = "1", features = ["derive"] }
+nix = { version = "0.29", features = ["signal", "process", "fs"] }
+libc = "0.2"
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..1031b05
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,7 @@
+fn main() {
+    // Track protocol XML files for rebuild
+    println!("cargo:rerun-if-changed=protocol/river-window-management-v1.xml");
+    println!("cargo:rerun-if-changed=protocol/river-xkb-bindings-v1.xml");
+    println!("cargo:rerun-if-changed=protocol/river-layer-shell-v1.xml");
+    println!("cargo:rerun-if-changed=protocol/river-input-management-v1.xml");
+}
diff --git a/protocol/river-input-management-v1.xml b/protocol/river-input-management-v1.xml
new file mode 100644
index 0000000..81f707c
--- /dev/null
+++ b/protocol/river-input-management-v1.xml
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_input_management_v1">
+  <copyright>
+    SPDX-FileCopyrightText: © 2025 Isaac Freund
+    SPDX-License-Identifier: MIT
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="manage seats and input devices">
+    This protocol supports creating/destroying seats, assigning input devices to
+    seats, and configuring input devices (e.g. setting keyboard repeat rate).
+
+    The key words "must", "must not", "required", "shall", "shall not",
+    "should", "should not", "recommended", "may", and "optional" in this
+    document are to be interpreted as described in IETF RFC 2119.
+  </description>
+
+  <interface name="river_input_manager_v1" version="1">
+    <description summary="input manager global interface">
+      Input manager global interface.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_destroy" value="0"/>
+    </enum>
+
+    <request name="stop">
+      <description summary="stop sending events">
+        This request indicates that the client no longer wishes to receive
+        events on this object.
+
+        The Wayland protocol is asynchronous, which means the server may send
+        further events until the stop request is processed. The client must wait
+        for a river_input_manager_v1.finished event before destroying this
+        object.
+      </description>
+    </request>
+
+    <event name="finished">
+      <description summary="the server has finished with the input manager">
+        This event indicates that the server will send no further events on this
+        object. The client should destroy the object. See
+        river_input_manager_v1.destroy for more information.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the river_input_manager_v1 object">
+        This request should be called after the finished event has been received
+        to complete destruction of the object.
+
+        It is a protocol error to make this request before the finished event
+        has been received.
+
+        If a client wishes to destroy this object it should send a
+        river_input_manager_v1.stop request and wait for a
+        river_input_manager_v1.finished event. Once the finished event is
+        received it is safe to destroy this object and any other objects created
+        through this interface.
+      </description>
+    </request>
+
+    <request name="create_seat">
+      <description summary="create a new seat">
+        Create a new seat with the given name. Has no effect if a seat with the
+        given name already exists.
+
+        The default seat with name "default" always exists and does not need to
+        be explicitly created.
+      </description>
+      <arg name="name" type="string"/>
+    </request>
+
+    <request name="destroy_seat">
+      <description summary="destroy a seat">
+        Destroy the seat with the given name. Has no effect if a seat with the
+        given name does not exist.
+
+        The default seat with name "default" cannot be destroyed and attempting
+        to destroy it will have no effect.
+
+        Any input devices assigned to the destroyed seat at the time of
+        destruction are assigned to the default seat.
+      </description>
+      <arg name="name" type="string"/>
+    </request>
+
+    <event name="input_device">
+      <description summary="new input device">
+        A new input device has been created.
+      </description>
+      <arg name="id" type="new_id" interface="river_input_device_v1"/>
+    </event>
+  </interface>
+
+  <interface name="river_input_device_v1" version="1">
+    <description summary="an input device">
+      An input device represents a physical keyboard, mouse, touchscreen, or
+      drawing tablet tool. It is assigned to exactly one seat at a time.
+      By default, all input devices are assigned to the default seat.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_repeat_info" value="0"/>
+      <entry name="invalid_scroll_factor" value="1"/>
+      <entry name="invalid_map_to_rectangle" value="2"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the input device object">
+        This request indicates that the client will no longer use the input
+        device object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <event name="removed">
+      <description summary="the input device is removed">
+        This event indicates that the input device has been removed.
+
+        The server will send no further events on this object and ignore any
+        request (other than river_input_device_v1.destroy) made after this event is
+        sent. The client should destroy this object with the
+        river_input_device_v1.destroy request to free up resources.
+      </description>
+    </event>
+
+    <enum name="type">
+      <entry name="keyboard" value="0"/>
+      <entry name="pointer" value="1"/>
+      <entry name="touch" value="2"/>
+      <entry name="tablet" value="3"/>
+    </enum>
+
+    <event name="type">
+      <description summary="the type of the input device">
+        The type of the input device. This event is sent once when the
+        river_input_device_v1 object is created. The device type cannot
+        change during the lifetime of the object.
+      </description>
+      <arg name="type" type="uint" enum="type"/>
+    </event>
+
+    <event name="name">
+      <description summary="the name of the input device">
+        The name of the input device. This event is sent once when the
+        river_input_device_v1 object is created. The device name cannot
+        change during the lifetime of the object.
+      </description>
+      <arg name="name" type="string"/>
+    </event>
+
+    <request name="assign_to_seat">
+      <description summary="assign the input device to a seat">
+        Assign the input device to a seat. All input devices not explicitly
+        assigned to a seat are considered assigned to the default seat.
+
+        Has no effect if a seat with the given name does not exist.
+      </description>
+      <arg name="name" type="string" summary="name of the seat"/>
+    </request>
+
+    <request name="set_repeat_info">
+      <description summary="set keyboard repeat rate and delay">
+        Set repeat rate and delay for a keyboard input device. Has no effect if
+        the device is not a keyboard.
+
+        Negative values for either rate or delay are illegal. A rate of zero
+        will disable any repeating (regardless of the value of delay).
+      </description>
+      <arg name="rate" type="int" summary="rate in key repeats per second"/>
+      <arg name="delay" type="int" summary="delay in milliseconds"/>
+    </request>
+
+    <request name="set_scroll_factor">
+      <description summary="set scroll factor">
+        Set the scroll factor for a pointer input device. Has no effect if the
+        device is not a pointer.
+
+        For example, a factor of 0.5 will make scrolling twice as slow while a
+        factor of 3.0 will make scrolling 3 times as fast.
+
+        Setting a scroll factor less than 0 is a protocol error.
+      </description>
+      <arg name="factor" type="fixed"/>
+    </request>
+
+    <request name="map_to_output">
+      <description summary="map input device to the given output">
+        Map the input device to the given output. Has no effect if the device is
+        not a pointer, touch, or tablet device.
+
+        If mapped to both an output and a rectangle, the rectangle has priority.
+
+        Passing null clears an existing mapping.
+      </description>
+      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+    </request>
+
+    <request name="map_to_rectangle">
+      <description summary="map input device to the given rectangle">
+        Map the input device to the given rectangle in the global compositor
+        coordinate space. Has no effect if the device is not a pointer, touch,
+        or tablet device.
+
+        If mapped to both an output and a rectangle, the rectangle has priority.
+
+        Width and height must be greater than or equal to 0.
+
+        Passing 0 for width or height clears an existing mapping.
+      </description>
+      <arg name="x" type="int"/>
+      <arg name="y" type="int"/>
+      <arg name="width" type="int"/>
+      <arg name="height" type="int"/>
+    </request>
+  </interface>
+</protocol>
diff --git a/protocol/river-layer-shell-v1.xml b/protocol/river-layer-shell-v1.xml
new file mode 100644
index 0000000..0167e9d
--- /dev/null
+++ b/protocol/river-layer-shell-v1.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_layer_shell_v1">
+  <copyright>
+    SPDX-FileCopyrightText: © 2025 Isaac Freund
+    SPDX-License-Identifier: MIT
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="optional layer shell support">
+    This protocol allows the river-window-management-v1 window manager to
+    support the wlr-layer-shell-unstable-v1 protocol.
+
+    The key words "must", "must not", "required", "shall", "shall not",
+    "should", "should not", "recommended", "may", and "optional" in this
+    document are to be interpreted as described in IETF RFC 2119.
+  </description>
+
+  <interface name="river_layer_shell_v1" version="1">
+    <description summary="river layer shell global interface">
+      This global interface should only be advertised to the client if the
+      river_window_manager_v1 global is also advertised. Binding this interface
+      indicates that the window manager supports layer shell.
+
+      If the window manager does not bind this interface, the compositor should
+      not allow clients to map layer surfaces. This can be achieved by
+      closing layer surfaces immediately.
+    </description>
+
+    <enum name="error">
+      <entry name="object_already_created" value="0"
+        summary="the layer_shell_output/seat object was already created."/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the river_layer_shell_v1 object">
+        This request indicates that the client will no longer use the
+        river_layer_shell_v1 object.
+      </description>
+    </request>
+
+    <request name="get_output">
+      <description summary="get layer shell output state">
+        It is a protocol error to make this request more than once for a given
+        river_output_v1 object.
+      </description>
+      <arg name="id" type="new_id" interface="river_layer_shell_output_v1"/>
+      <arg name="output" type="object" interface="river_output_v1"/>
+    </request>
+
+    <request name="get_seat">
+      <description summary="get layer shell seat state">
+        It is a protocol error to make this request more than once for a given
+        river_seat_v1 object.
+      </description>
+      <arg name="id" type="new_id" interface="river_layer_shell_seat_v1"/>
+      <arg name="seat" type="object" interface="river_seat_v1"/>
+    </request>
+  </interface>
+
+  <interface name="river_layer_shell_output_v1" version="1">
+    <description summary="layer shell output state">
+      The lifetime of this object is tied to the corresponding river_output_v1.
+      This object is made inert when the river_output_v1.removed event is sent
+      and should be destroyed.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the object">
+        This request indicates that the client will no longer use the
+        river_layer_shell_output_v1 object and that it may be safely destroyed.
+
+        This request should be made after the river_output_v1.removed event is
+        received to complete destruction of the output.
+      </description>
+    </request>
+
+    <event name="non_exclusive_area">
+      <description summary="area left after subtracting exclusive zones">
+        This event indicates the area of the output remaining after subtracting
+        the exclusive zones of layer surfaces. Exclusive zones are a hint, the
+        window manager is free to ignore this area hint if it wishes.
+
+        The x and y values are in the global coordinate space, not relative to
+        the position of the output.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="x" type="int" summary="global x coordinate"/>
+      <arg name="y" type="int" summary="global y coordinate"/>
+      <arg name="width" type="int" summary="area width"/>
+      <arg name="height" type="int" summary="area height"/>
+    </event>
+
+    <request name="set_default">
+      <description summary="Set default output for layer surfaces">
+        Mark this output as the default for new layer surfaces which do not
+        request a specific output themselves. This request overrides any
+        previous set_default request on any river_layer_shell_output_v1 object.
+
+        If no set_default request is made or if the default output is destroyed,
+        the default output is undefined until the next set_default request.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="river_layer_shell_seat_v1" version="1">
+    <description summary="layer shell seat state">
+      The lifetime of this object is tied to the corresponding river_seat_v1.
+      This object is made inert when the river_seat_v1.removed event is sent and
+      should be destroyed.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the object">
+        This request indicates that the client will no longer use the
+        river_layer_shell_seat_v1 object and that it may be safely destroyed.
+
+        This request should be made after the river_seat_v1.removed event is
+        received to complete destruction of the seat.
+      </description>
+    </request>
+
+    <event name="focus_exclusive">
+      <description summary="layer shell surface has exclusive focus">
+        A layer shell surface will be given exclusive keyboard focus at the end
+        of the manage sequence in which this event is sent. The window manager
+        may want to update window decorations or similar to indicate that no
+        window is focused.
+
+        Until the focus_non_exclusive or focus_none event is sent, all window
+        manager requests to change focus are ignored.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="focus_non_exclusive">
+      <description summary="layer shell surface wants non-exclusive focus">
+        A layer shell surface will be given non-exclusive keyboard focus at the
+        end of the manage sequence in which this event is sent. The window
+        manager may want to update window decorations or similar to indicate
+        that no window is focused.
+
+        The window manager continues to control focus and may choose to focus a
+        different window/shell surface at any time. If the window manager sets
+        focus during the same manage sequence in which this event is sent, the
+        layer surface will not be focused.
+
+        If the layer surface with non-exclusive focus is closed or the window
+        manager chooses to move focus away from the layer surface, a focus_none
+        event will be sent in the next manage sequence.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="focus_none">
+      <description summary="no layer shell surface has focus">
+        No layer shell surface will have keyboard focus at the end of the manage
+        sequence in which this event is sent. The window manager may want to
+        return focus to whichever window last had focus, for example.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+  </interface>
+</protocol>
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
new file mode 100644
index 0000000..608225d
--- /dev/null
+++ b/protocol/river-window-management-v1.xml
@@ -0,0 +1,1854 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_window_management_v1">
+  <copyright>
+    SPDX-FileCopyrightText: © 2024 Isaac Freund
+    SPDX-License-Identifier: MIT
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="frame-perfect window management">
+    This protocol allows a single "window manager" client to determine the
+    window management policy of the compositor. State is globally
+    double-buffered allowing for frame perfect state changes involving multiple
+    windows.
+
+    The key words "must", "must not", "required", "shall", "shall not",
+    "should", "should not", "recommended", "may", and "optional" in this
+    document are to be interpreted as described in IETF RFC 2119.
+  </description>
+
+  <interface name="river_window_manager_v1" version="4">
+    <description summary="window manager global interface">
+      This global interface should only be advertised to the window manager
+      process. Only one window management client may be active at a time. The
+      compositor should use the unavailable event if necessary to enforce this.
+
+      There are two disjoint categories of state managed by this protocol:
+
+      Window management state influences the communication between the server
+      and individual window clients (e.g. xdg_toplevels). Window management
+      state includes window dimensions, fullscreen state, keyboard focus,
+      keyboard bindings, and more.
+
+      Rendering state only affects the rendered output of the compositor and
+      does not influence communication between the server and individual window
+      clients. Rendering state includes the position and rendering order of
+      windows, shell surfaces, decoration surfaces, borders, and more.
+
+      Window management state may only be modified by the window manager as part
+      of a manage sequence. A manage sequence is started with the manage_start
+      event and ended with the manage_finish request. It is a protocol error to
+      modify window management state outside of a manage sequence.
+
+      A manage sequence is always followed by at least one render sequence. A
+      render sequence is started with the render_start event and ended with the
+      render_finish request.
+
+      Rendering state may be modified by the window manager during a manage
+      sequence or a render sequence. Regardless of when the rendering state is
+      modified, it is applied with the next render_finish request. It is a
+      protocol error to modify rendering state outside of a manage or render
+      sequence.
+
+      The server will start a manage sequence by sending new state and the
+      manage_start event as soon as possible whenever there is a change in state
+      that must be communicated with the window manager.
+
+      If the window manager client needs to ensure a manage sequence is started
+      due to a state change the compositor is not aware of, it may send the
+      manage_dirty request.
+
+      The server will start a render sequence by sending new state and the
+      render_start event as soon as possible whenever there is a change in
+      window dimensions that must be communicated with the window manager.
+      Multiple render sequences may be made consecutively without a manage
+      sequence in between, for example if a window independently changes its own
+      dimensions.
+
+      To summarize, the main loop of this protocol is as follows:
+
+      1. The server sends events indicating all changes since the last
+         manage sequence followed by the manage_start event.
+
+      2. The client sends requests modifying window management state or
+         rendering state (as defined above) followed by the manage_finish
+         request.
+
+      3. The server sends new state to windows and waits for responses.
+
+      4. The server sends new window dimensions to the client followed by the
+         render_start event.
+
+      5. The client sends requests modifying rendering state (as defined above)
+         followed by the render_finish request.
+
+      6. If window dimensions change, loop back to step 4.
+         If state that requires a manage sequence changes or if the client makes
+         a manage_dirty request, loop back to step 1.
+
+      For the purposes of frame perfection, the server may delay rendering new
+      state committed by the windows in step 3 until after step 5 is finished.
+
+      It is a protocol error for the client to make a manage_finish or
+      render_finish request that violates this ordering.
+    </description>
+
+    <enum name="error">
+      <entry name="sequence_order" value="0"
+        summary="request violates manage/render sequence ordering"/>
+      <entry name="role" value="1"
+        summary="given wl_surface already has a role"/>
+      <entry name="unresponsive" value="2"
+        summary="window manager unresponsive"/>
+    </enum>
+
+    <event name="unavailable">
+      <description summary="window management unavailable">
+        This event indicates that window management is not available to the
+        client, perhaps due to another window management client already running.
+        The circumstances causing this event to be sent are compositor policy.
+
+        If sent, this event is guaranteed to be the first and only event sent by
+        the server.
+
+        The server will send no further events on this object. The client should
+        destroy this object and all objects created through this interface.
+      </description>
+    </event>
+
+    <request name="stop">
+      <description summary="stop sending events">
+        This request indicates that the client no longer wishes to receive
+        events on this object.
+
+        The Wayland protocol is asynchronous, which means the server may send
+        further events until the stop request is processed. The client must wait
+        for a river_window_manager_v1.finished event before destroying this
+        object.
+      </description>
+    </request>
+
+    <event name="finished">
+      <description summary="the server has finished with the window manager">
+        This event indicates that the server will send no further events on this
+        object. The client should destroy the object. See
+        river_window_manager_v1.destroy for more information.
+      </description>
+    </event>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the river_window_manager_v1 object">
+        This request should be called after the finished event has been received
+        to complete destruction of the object.
+
+        If a client wishes to destroy this object it should send a
+        river_window_manager_v1.stop request and wait for a
+        river_window_manager_v1.finished event. Once the finished event is
+        received it is safe to destroy this object and any other objects created
+        through this interface.
+      </description>
+    </request>
+
+    <event name="manage_start">
+      <description summary="start a manage sequence">
+        This event indicates that the server has sent events indicating all
+        state changes since the last manage sequence.
+
+        In response to this event, the client should make requests modifying
+        window management state as it chooses. Then, the client must make the
+        manage_finish request.
+
+        See the description of the river_window_manager_v1 interface for a
+        complete overview of the manage/render sequence loop.
+      </description>
+    </event>
+
+    <request name="manage_finish">
+      <description summary="finish a manage sequence">
+        This request indicates that the client has made all changes to window
+        management state it wishes to include in the current manage sequence and
+        that the server should atomically send these state changes to the
+        windows and continue with the manage sequence.
+
+        After sending this request, it is a protocol error for the client to
+        make further changes to window management state until the next
+        manage_start event is received.
+
+        See the description of the river_window_manager_v1 interface for a
+        complete overview of the manage/render sequence loop.
+      </description>
+    </request>
+
+    <request name="manage_dirty">
+      <description summary="ensure a manage sequence is started">
+        This request ensures a manage sequence is started and that a
+        manage_start event is sent by the server. If this request is made during
+        an ongoing manage sequence, a new manage sequence will be started as
+        soon as the current one is completed.
+
+        The client may want to use this request due to an internal state change
+        that the compositor is not aware of (e.g. a dbus event) which should
+        affect window management or rendering state.
+      </description>
+    </request>
+
+    <event name="render_start">
+      <description summary="start a render sequence">
+        This event indicates that the server has sent all river_node_v1.position
+        and river_window_v1.dimensions events necessary.
+
+        In response to this event, the client should make requests modifying
+        rendering state as it chooses. Then, the client must make the
+        render_finish request.
+
+        See the description of the river_window_manager_v1 interface for a
+        complete overview of the manage/render sequence loop.
+      </description>
+    </event>
+
+    <request name="render_finish">
+      <description summary="finish a render sequence">
+        This request indicates that the client has made all changes to rendering
+        state it wishes to include in the current manage sequence and that the
+        server should atomically apply and display these state changes to the
+        user.
+
+        After sending this request, it is a protocol error for the client to
+        make further changes to rendering state until the next manage_start or
+        render_start event is received, whichever comes first.
+
+        See the description of the river_window_manager_v1 interface for a
+        complete overview of the manage/render sequence loop.
+      </description>
+    </request>
+
+    <event name="session_locked">
+      <description summary="the session has been locked">
+        This event indicates that the session has been locked.
+
+        The window manager may wish to restrict which key bindings are available
+        while locked or otherwise use this information.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="session_unlocked">
+      <description summary="the session has been unlocked">
+        This event indicates that the session has been unlocked.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="window">
+      <description summary="new window">
+        A new window has been created.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="id" type="new_id" interface="river_window_v1" summary="new window"/>
+    </event>
+
+    <event name="output">
+      <description summary="new output">
+        A new logical output has been created, perhaps due to a new physical
+        monitor being plugged in or perhaps due to a change in configuration.
+
+        This event will be followed by river_output_v1.position and dimensions
+        events as well as a manage_start event after all other new state has
+        been sent by the server.
+      </description>
+      <arg name="id" type="new_id" interface="river_output_v1" summary="new output"/>
+    </event>
+
+    <event name="seat">
+      <description summary="new seat">
+        A new seat has been created.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="id" type="new_id" interface="river_seat_v1" summary="new seat"/>
+    </event>
+
+    <request name="get_shell_surface">
+      <description summary="assign the river_shell_surface_v1 surface role">
+        Create a new shell surface for window manager UI and assign the
+        river_shell_surface_v1 role to the surface.
+
+        Providing a wl_surface which already has a role or already has a buffer
+        attached or committed is a protocol error.
+      </description>
+      <arg name="id" type="new_id" interface="river_shell_surface_v1"
+        summary="new river shell surface"/>
+      <arg name="surface" type="object" interface="wl_surface"
+        summary="base surface"/>
+    </request>
+
+    <request name="exit_session" since="4">
+      <description summary="exit the Wayland session">
+        End the current Wayland session and exit the compositor.
+        All Wayland clients running in the current session, including
+        the window manager, will be disconnected.
+
+        Window managers should only make this request if the user explicitly
+        asks to exit the Wayland session, not for example on normal window
+        manager termination.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="river_window_v1" version="4">
+    <description summary="a logical window">
+      This represents a logical window. For example, a window may correspond to
+      an xdg_toplevel or Xwayland window.
+
+      A newly created window will not be displayed until the window manager
+      makes a propose_dimensions or fullscreen request as part of a manage
+      sequence, the server replies with a dimensions event as part of a render
+      sequence, and that render sequence is finished.
+    </description>
+
+    <enum name="error">
+      <entry name="node_exists" value="0"
+        summary="window already has a node object"/>
+      <entry name="invalid_dimensions" value="1"
+        summary="proposed dimensions out of bounds"/>
+      <entry name="invalid_border" value="2"
+        summary="invalid arg to set_borders"/>
+      <entry name="invalid_clip_box" value="3"
+        summary="invalid arg to set_clip_box"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the window object">
+        This request indicates that the client will no longer use the window
+        object and that it may be safely destroyed.
+
+        This request should be made after the river_window_v1.closed event or
+        river_window_manager_v1.finished is received to complete destruction of
+        the window.
+      </description>
+    </request>
+
+    <event name="closed">
+      <description summary="the window has been closed">
+        The window has been closed by the server, perhaps due to an
+        xdg_toplevel.close request or similar.
+
+        The server will send no further events on this object and ignore any
+        request other than river_window_v1.destroy made after this event is
+        sent. The client should destroy this object with the
+        river_window_v1.destroy request to free up resources.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <request name="close">
+      <description summary="request that the window be closed">
+        Request that the window be closed. The window may ignore this request or
+        only close after some delay, perhaps opening a dialog asking the user to
+        save their work or similar.
+
+        The server will send a river_window_v1.closed event if/when the window
+        has been closed.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="get_node">
+      <description summary="get the window's render list node">
+        Get the node in the render list corresponding to the window.
+
+        It is a protocol error to make this request more than once for a single
+        window.
+      </description>
+      <arg name="id" type="new_id" interface="river_node_v1" summary="new node"/>
+    </request>
+
+    <event name="dimensions_hint">
+      <description summary="the window's preferred min/max dimensions">
+        This event informs the window manager of the window's preferred min/max
+        dimensions. These preferences are a hint, and the window manager is free
+        to propose dimensions outside of these bounds.
+
+        All min/max width/height values must be strictly greater than or equal
+        to 0. A value of 0 indicates that the window has no preference for that
+        value.
+
+        The min_width/min_height must be strictly less than or equal to the
+        max_width/max_height.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="min_width" type="int" summary="minimum width"/>
+      <arg name="min_height" type="int" summary="minimum height"/>
+      <arg name="max_width" type="int" summary="maximum width"/>
+      <arg name="max_height" type="int" summary="maximum height"/>
+    </event>
+
+    <event name="dimensions">
+      <description summary="window dimensions">
+        This event indicates the dimensions of the window in the compositor's
+        logical coordinate space. The width and height must be strictly greater
+        than zero.
+
+        Note that the dimensions of a river_window_v1 refer to the dimensions of
+        the window content and are unaffected by the presence of borders or
+        decoration surfaces.
+
+        This event is sent as part of a render sequence before the render_start
+        event.
+
+        It may be sent due to a propose_dimensions or fullscreen request in a
+        previous manage sequence or because a window independently decides to
+        change its dimensions.
+
+        The window will not be displayed until the first dimensions event is
+        received and the render sequence is finished.
+      </description>
+      <arg name="width" type="int" summary="window content width"/>
+      <arg name="height" type="int" summary="window content height"/>
+    </event>
+
+    <request name="propose_dimensions">
+      <description summary="propose window dimensions">
+        This request proposes dimensions for the window in the compositor's
+        logical coordinate space.
+
+        The width and height must be greater than or equal to zero. If the width
+        or height is zero the window will be allowed to decide its own
+        dimensions.
+
+        The window may not take the exact dimensions proposed. The actual
+        dimensions taken by the window will be sent in a subsequent
+        river_window_v1.dimensions event. For example, a terminal emulator may
+        only allow dimensions that are multiple of the cell size.
+
+        When a propose_dimensions request is made, the server must send a
+        dimensions event in response as soon as possible. It may not be possible
+        to send a dimensions event in the very next render sequence if, for
+        example, the window takes too long to respond to the proposed
+        dimensions. In this case, the server will send the dimensions event in a
+        future render sequence.
+
+        Note that the dimensions of a river_window_v1 refer to the dimensions of
+        the window content and are unaffected by the presence of borders or
+        decoration surfaces.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="width" type="int" summary="proposed content width"/>
+      <arg name="height" type="int" summary="proposed content height"/>
+    </request>
+
+    <request name="hide">
+      <description summary="request that the window be hidden">
+        Request that the window be hidden. Has no effect if the window is
+        already hidden. Hides any window borders and decorations as well.
+
+        Newly created windows are considered shown unless explicitly hidden with
+        the hide request.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="show">
+      <description summary="request that the window be shown">
+        Request that the window be shown. Has no effect if the window is not
+        hidden. Does not guarantee that the window is visible as it may be
+        completely obscured by other windows placed above it for example.
+
+        Newly created windows are considered shown unless explicitly hidden with
+        the hide request.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="app_id">
+      <description summary="the window set an application ID">
+        The window set an application ID.
+
+        The app_id argument will be null if the window has never set an
+        application ID or if the window cleared its application ID. (Xwayland
+        windows may do this for example, though xdg-toplevels may not.)
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="app_id" type="string" allow-null="true"
+        summary="window application ID"/>
+    </event>
+
+    <event name="title">
+      <description summary="the window set a title">
+        The window set a title.
+
+        The title argument will be null if the window has never set a title or
+        if the window cleared its title. (Xwayland windows may do this for
+        example, though xdg-toplevels may not.)
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="title" type="string" allow-null="true" summary="window title"/>
+    </event>
+
+    <event name="parent">
+      <description summary="the window set a parent">
+        The window set a parent window. If this event is never received or if
+        the parent argument is null then the window has no parent.
+
+        A surface with a parent set might be a dialog, file picker, or similar
+        for the parent window.
+
+        Child windows should generally be rendered directly above their parent.
+
+        The compositor must guarantee that there are no loops in the window
+        tree: a parent must not be the descendant of one of its children.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="parent" type="object" allow-null="true"
+        interface="river_window_v1" summary="parent window, if any"/>
+    </event>
+
+    <enum name="decoration_hint">
+      <entry name="only_supports_csd" value="0"
+        summary="only supports client side decoration"/>
+      <entry name="prefers_csd" value="1"
+        summary="client side decoration preferred, both CSD and SSD supported"/>
+      <entry name="prefers_ssd" value="2"
+        summary="server side decoration preferred, both CSD and SSD supported"/>
+      <entry name="no_preference" value="3"
+        summary="no preference, both CSD and SSD supported"/>
+    </enum>
+
+    <event name="decoration_hint">
+      <description summary="supported/preferred decoration style">
+        Information from the window about the supported and preferred client
+        side/server side decoration options.
+
+        This event may be sent multiple times over the lifetime of the window if
+        the window changes its preferences.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="hint" type="uint" enum="decoration_hint" summary="decoration hint"/>
+    </event>
+
+    <request name="use_csd">
+      <description summary="tell the client to use CSD">
+        Tell the client to use client side decoration and draw its own title
+        bar, borders, etc.
+
+        This is the default if neither this request nor the use_ssd request is
+        ever made.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="use_ssd">
+      <description summary="tell the client to use SSD">
+        Tell the client to use server side decoration and not draw any client
+        side decorations.
+
+        This request will have no effect if the client only supports client side
+        decoration, see the decoration_hint event.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <enum name="edges" bitfield="true">
+      <entry name="none" value="0"/>
+      <entry name="top" value="1"/>
+      <entry name="bottom" value="2"/>
+      <entry name="left" value="4"/>
+      <entry name="right" value="8"/>
+    </enum>
+
+    <request name="set_borders">
+      <description summary="set window borders">
+        This request decorates the window with borders drawn by the compositor
+        on the specified edges of the window. Borders are drawn above the window
+        content.
+
+        Corners are drawn only between borders on adjacent edges. If e.g. the
+        left edge has a border and the top edge does not, the border drawn on
+        the left edge will not extend vertically beyond the top edge of the
+        window.
+
+        Borders are not drawn while the window is fullscreen.
+
+        The color is defined by four 32-bit RGBA values. Unless specified in
+        another protocol extension, the RGBA values use pre-multiplied alpha.
+
+        Setting the edges to none or the width to 0 disables the borders.
+        Setting a negative width is a protocol error.
+
+        This request completely overrides all previous set_borders requests.
+        Only the most recent set_borders request has an effect.
+
+        Note that the position/dimensions of a river_window_v1 refer to the
+        position/dimensions of the window content and are unaffected by the
+        presence of borders or decoration surfaces.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="edges" type="uint" enum="edges" summary="border edges"/>
+      <arg name="width" type="int" summary="border width"/>
+      <arg name="r" type="uint" summary="32-bit red value"/>
+      <arg name="g" type="uint" summary="32-bit green value"/>
+      <arg name="b" type="uint" summary="32-bit blue value"/>
+      <arg name="a" type="uint" summary="32-bit alpha value"/>
+    </request>
+
+    <request name="set_tiled">
+      <description summary="set window tiled state">
+        Inform the window that it is part of a tiled layout and adjacent to
+        other elements in the tiled layout on the given edges.
+
+        The window should use this information to change the style of its client
+        side decorations and avoid drawing e.g. drop shadows outside of the
+        window dimensions on the tiled edges.
+
+        Setting the edges argument to none informs the window that it is not
+        part of a tiled layout. If this request is never made, the window is
+        informed that it is not part of a tiled layout.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="edges" type="uint" enum="edges" summary="tiled edges"/>
+    </request>
+
+    <request name="get_decoration_above">
+      <description summary="create a decoration surface above the window">
+        Create a decoration surface and assign the river_decoration_v1 role to
+        the surface. The created decoration is placed above the window in
+        rendering order, see the description of river_decoration_v1.
+
+        Providing a wl_surface which already has a role or already has a buffer
+        attached or committed is a protocol error.
+      </description>
+      <arg name="id" type="new_id" interface="river_decoration_v1"
+        summary="new decoration surface"/>
+      <arg name="surface" type="object" interface="wl_surface"
+        summary="base surface"/>
+    </request>
+
+    <request name="get_decoration_below">
+      <description summary="create a decoration surface below the window">
+        Create a decoration surface and assign the river_decoration_v1 role to
+        the surface. The created decoration is placed below the window in
+        rendering order, see the description of river_decoration_v1.
+
+        Providing a wl_surface which already has a role or already has a buffer
+        attached or committed is a protocol error.
+      </description>
+      <arg name="id" type="new_id" interface="river_decoration_v1"
+        summary="new decoration surface"/>
+      <arg name="surface" type="object" interface="wl_surface"
+        summary="base surface"/>
+    </request>
+
+    <event name="pointer_move_requested">
+      <description summary="window requested interactive pointer move">
+        This event informs the window manager that the window has requested to
+        be interactively moved using the pointer. The seat argument indicates the
+        seat for the move.
+
+        The xdg-shell protocol for example allows windows to request that an
+        interactive move be started, perhaps when a client-side rendered
+        titlebar is dragged.
+
+        The window manager may use the river_seat_v1.op_start_pointer request to
+        interactively move the window or ignore this event entirely.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="seat" type="object" interface="river_seat_v1"
+        summary="requested seat"/>
+    </event>
+
+    <event name="pointer_resize_requested">
+      <description summary="window requested interactive pointer resize">
+        This event informs the window manager that the window has requested to
+        be interactively resized using the pointer. The seat argument indicates
+        the seat for the resize.
+
+        The edges argument indicates which edges the window has requested to be
+        resized from. The edges argument will never be none and will never have
+        both top and bottom or both left and right edges set.
+
+        The xdg-shell protocol for example allows windows to request that an
+        interactive resize be started, perhaps when the corner of client-side
+        rendered decorations is dragged.
+
+        The window manager may use the river_seat_v1.op_start_pointer request to
+        interactively resize the window or ignore this event entirely.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="seat" type="object" interface="river_seat_v1"
+        summary="requested seat"/>
+      <arg name="edges" type="uint" enum="edges"
+        summary="requested edges"/>
+    </event>
+
+    <request name="inform_resize_start">
+      <description summary="inform the window it is being resized">
+        Inform the window that it is being resized. The window manager should
+        use this request to inform windows that are the target of an interactive
+        resize for example.
+
+        The window manager remains responsible for handling the position and
+        dimensions of the window while it is resizing.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="inform_resize_end">
+      <description summary="inform the window it no longer being resized">
+        Inform the window that it is no longer being resized. The window manager
+        should use this request to inform windows that are the target of an
+        interactive resize that the interactive resize has ended for example.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <enum name="capabilities" bitfield="true">
+      <entry name="window_menu" value="1"/>
+      <entry name="maximize" value="2"/>
+      <entry name="fullscreen" value="4"/>
+      <entry name="minimize" value="8"/>
+    </enum>
+
+    <request name="set_capabilities">
+      <description summary="inform window of supported capabilities">
+        This request informs the window of the capabilities supported by the
+        window manager. If the window manager, for example, ignores requests to
+        be maximized from the window it should not tell the window that it
+        supports the maximize capability.
+
+        The window might use this information to, for example, only show a
+        maximize button if the window manager supports the maximize capability.
+
+        The window manager client should use this request to set capabilities
+        for all new windows. If this request is never made, the compositor will
+        inform windows that all capabilities are supported.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="caps" type="uint" enum="capabilities"
+        summary="supported capabilities"/>
+    </request>
+
+    <event name="show_window_menu_requested">
+      <description summary="window requested that the window menu be shown">
+        The xdg-shell protocol for example allows windows to request that a
+        window menu be shown, for example when the user right clicks on client
+        side window decorations.
+
+        A window menu might include options to maximize or minimize the window.
+
+        The window manager is free to ignore this request and decide what the
+        window menu contains if it does choose to show one.
+
+        The x and y arguments indicate where the window requested that the
+        window menu be shown.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="x" type="int" summary="x offset from top left corner"/>
+      <arg name="y" type="int" summary="y offset from top left corner"/>
+    </event>
+
+    <event name="maximize_requested">
+      <description summary="the window requested to be maximized">
+        The xdg-shell protocol for example allows windows to request to be
+        maximized.
+
+        The window manager is free to honor this request using
+        river_window_v1.inform_maximize or ignore it.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="unmaximize_requested">
+      <description summary="the window requested to be unmaximized">
+        The xdg-shell protocol for example allows windows to request to be
+        unmaximized.
+
+        The window manager is free to honor this request using
+        river_window_v1.inform_unmaximized or ignore it.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <request name="inform_maximized">
+      <description summary="inform the window that it is maximized">
+        Inform the window that it is maximized. The window might use this
+        information to adapt the style of its client-side window decorations for
+        example.
+
+        The window manager remains responsible for handling the position and
+        dimensions of the window while it is maximized.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="inform_unmaximized">
+      <description summary="inform the window that it is unmaximized">
+        Inform the window that it is unmaximized. The window might use this
+        information to adapt the style of its client-side window decorations for
+        example.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="fullscreen_requested">
+      <description summary="the window requested to be fullscreen">
+        The xdg-shell protocol for example allows windows to request that they
+        be made fullscreen and allows them to provide an optional output hint.
+
+        If the output argument is null, the window has no preference and the
+        window manager should choose an output.
+
+        The window manager is free to honor this request using
+        river_window_v1.fullscreen or ignore it.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="output" type="object" allow-null="true"
+        interface="river_output_v1" summary="fullscreen output requested"/>
+    </event>
+
+    <event name="exit_fullscreen_requested">
+      <description summary="the window requested to exit fullscreen">
+        The xdg-shell protocol for example allows windows to request to exit
+        fullscreen.
+
+        The window manager is free to honor this request using
+        river_window_v1.exit_fullscreen or ignore it.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <request name="inform_fullscreen">
+      <description summary="inform the window that it is fullscreen">
+        Inform the window that it is fullscreen. The window might use this
+        information to adapt the style of its client-side window decorations for
+        example.
+
+        This request does not affect the size/position of the window or cause it
+        to become the only window rendered, see the river_window_v1.fullscreen
+        and exit_fullscreen requests for that.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="inform_not_fullscreen">
+      <description summary="inform the window that it is not fullscreen">
+        Inform the window that it is not fullscreen. The window might use this
+        information to adapt the style of its client-side window decorations for
+        example.
+
+        This request does not affect the size/position of the window or cause it
+        to become the only window rendered, see the river_window_v1.fullscreen
+        and exit_fullscreen requests for that.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="fullscreen">
+      <description summary="make the window fullscreen">
+        Make the window fullscreen on the given output. If multiple windows are
+        fullscreen on the same output at the same time only the "top" window in
+        rendering order shall be displayed.
+
+        All river_shell_surface_v1 objects above the top fullscreen window in
+        the rendering order will continue to be rendered.
+
+        The compositor will handle the position and dimensions of the window
+        while it is fullscreen. The set_position and propose_dimensions requests
+        shall not affect the current position and dimensions of a fullscreen
+        window.
+
+        When a fullscreen request is made, the server must send a dimensions
+        event in response as soon as possible. It may not be possible to send a
+        dimensions event in the very next render sequence if, for example, the
+        window takes too long to respond. In this case, the server will send the
+        dimensions event in a future render sequence.
+
+        The compositor will clip window content, decoration surfaces, and
+        borders to the given output's dimensions while the window is fullscreen.
+        The effects of set_clip_box and set_content_clip_box are ignored while
+        the window is fullscreen.
+
+        If the output on which a window is currently fullscreen is removed, the
+        windowing state is modified as if there were an exit_fullscreen request
+        made in the same manage sequence as the river_output_v1.removed event.
+
+        This request does not inform the window that it is fullscreen, see the
+        river_window_v1.inform_fullscreen and inform_not_fullscreen requests.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="output" type="object" interface="river_output_v1"
+        summary="fullscreen output"/>
+    </request>
+
+    <request name="exit_fullscreen">
+      <description summary="make the window not fullscreen">
+        Make the window not fullscreen.
+
+        The position and dimensions are undefined after this request is made
+        until a manage sequence in which the window manager makes the
+        propose_dimensions and set_position requests is completed.
+
+        The window manager should make propose_dimensions and set_position
+        requests in the same manage sequence as the exit_fullscreen request for
+        frame perfection.
+
+        This request does not inform the window that it is fullscreen, see the
+        river_window_v1.inform_fullscreen and inform_not_fullscreen requests.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="minimize_requested">
+      <description summary="the window requested to be minimized">
+        The xdg-shell protocol for example allows windows to request to be
+        minimized.
+
+        The window manager is free to ignore this request, hide the window, or
+        do whatever else it chooses.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <request name="set_clip_box" since="2">
+      <description summary="clip the window to a given box">
+        Clip the window, including borders and decoration surfaces, to the box
+        specified by the x, y, width, and height arguments. The x/y position of
+        the box is relative to the top left corner of the window.
+
+        The width and height arguments must be greater than or equal to 0.
+
+        Setting a clip box with 0 width or height disables clipping.
+
+        The clip box is ignored while the window is fullscreen.
+
+        Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="x" type="int" summary="x relative to top left window corner"/>
+      <arg name="y" type="int" summary="y relative to top left window corner"/>
+      <arg name="width" type="int" summary="clip box width"/>
+      <arg name="height" type="int" summary="clip box height"/>
+    </request>
+
+    <event name="unreliable_pid" since="2">
+      <description summary="unreliable PID of the window's creator">
+        This event gives an unreliable PID of the process that created the
+        window. Obtaining this information is inherently racy due to PID reuse.
+        Therefore, this PID must not be used for anything security sensitive.
+
+        Note also that a single process may create multiple windows, so there is
+        not necessarily a 1-to-1 mapping from PID to window. Multiple windows
+        may have the same PID.
+
+        This event is sent once when the river_window_v1 is created and never
+        sent again.
+      </description>
+      <arg name="unreliable_pid" type="int" summary="unreliable PID"/>
+    </event>
+
+    <request name="set_content_clip_box" since="3">
+      <description summary="clip the window content to a given box">
+        Clip the content of the window, excluding borders and decoration
+        surfaces, to the box specified by the x, y, width, and height arguments.
+        The x/y position of the box is relative to the top left corner of the
+        window.
+
+        Borders drawn by the compositor (see set_borders) are placed around the
+        intersection of the window content (as defined by the dimensions event)
+        and the content clip box when content clipping is enabled.
+
+        The width and height arguments must be greater than or equal to 0.
+
+        Setting a box with 0 width or height disables content clipping.
+
+        The content clip box is ignored while the window is fullscreen.
+
+        Both set_clip_box and set_content_clip_box may be enabled simultaneously.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="x" type="int" summary="x relative to top left window corner"/>
+      <arg name="y" type="int" summary="y relative to top left window corner"/>
+      <arg name="width" type="int" summary="clip box width"/>
+      <arg name="height" type="int" summary="clip box height"/>
+    </request>
+
+    <event name="presentation_hint" since="4">
+      <description summary="presentation hint set by the window">
+        This event communicates the window's preferred presentation mode.
+
+        This event will be followed by a render_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="hint" type="uint" enum="river_output_v1.presentation_mode"
+        summary="presentation hint"/>
+    </event>
+
+    <event name="identifier" since="4">
+      <description summary="unique window identifier">
+        The identifier is a string that contains up to 32 printable ASCII bytes.
+        The identifier must not be an empty string.
+
+        It is compositor policy how the identifier is generated, but the following
+        properties must be upheld:
+
+        1. The identifier must uniquely identify the window. Two windows must not
+           share the same identifier.
+
+        2. The identifier must not be reused. This avoids races around window
+           creation/destruction when identifiers are used in out-of-band IPC.
+
+        If the compositor implements the ext-foreign-toplevel-list-v1 protocol,
+        the river_window_v1.identifier event must match the corresponding
+        ext_foreign_toplevel_handle_v1.identifier event.
+
+        This event is sent once when the river_window_v1 is created and never
+        sent again.
+      </description>
+      <arg name="identifier" type="string" summary="unique identifier"/>
+    </event>
+
+    <request name="set_dimension_bounds" since="4">
+      <description summary="recommend maximum dimensions to the window">
+        Recommend that the window keep its dimensions within a given
+        maximum width/height. This recommendation is only a hint and the window
+        may ignore it.
+
+        Setting the width and height to 0 indicates that there are no bounds
+        and is equivalent to having never made this request.
+
+        Setting width or height to a negative value is a protocol error.
+
+        The server should communicate this hint to an xdg_toplevel window with
+        the xdg_toplevel.configure_bounds event for example.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="max_width" type="int" summary="maximum width"/>
+      <arg name="max_height" type="int" summary="maximum height"/>
+    </request>
+  </interface>
+
+  <interface name="river_decoration_v1" version="4">
+    <description summary="a window decoration">
+      The rendering order of windows with decorations is follows:
+
+      1. Decorations created with get_decoration_below at the bottom
+      2. Window content
+      3. Borders configured with river_window_v1.set_borders
+      4. Decorations created with get_decoration_above at the top
+
+      The relative ordering of decoration surfaces above/below a window is
+      undefined by this protocol and left up to the compositor.
+    </description>
+
+    <enum name="error">
+      <entry name="no_commit" value="0"
+        summary="failed to commit the surface before the window manager commit"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the decoration object">
+        This request indicates that the client will no longer use the decoration
+        object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="set_offset">
+      <description summary="set offset from the window's top left corner">
+        This request sets the offset of the decoration surface from the top left
+        corner of the window.
+
+        If this request is never sent, the x and y offsets are undefined by this
+        protocol and left up to the compositor.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="x" type="int" summary="x relative to top left window corner"/>
+      <arg name="y" type="int" summary="y relative to top left window corner"/>
+    </request>
+
+    <request name="sync_next_commit">
+      <description summary="sync next commit with other rendering state">
+        Synchronize application of the next wl_surface.commit request on the
+        decoration surface with rest of the state atomically applied with the
+        next river_window_manager_v1.render_finish request.
+
+        The client must make a wl_surface.commit request on the decoration
+        surface after this request and before the render_finish request, failure
+        to do so is a protocol error.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="river_shell_surface_v1" version="4">
+    <description summary="a surface for window manager UI">
+      The window manager might use a shell surface to display a status bar,
+      background image, desktop notifications, launcher, desktop menu, or
+      whatever else it wants.
+    </description>
+
+    <enum name="error">
+      <entry name="node_exists" value="0"
+        summary="shell surface already has a node object"/>
+      <entry name="no_commit" value="1"
+        summary="failed to commit the surface before the window manager commit"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the shell surface object">
+        This request indicates that the client will no longer use the shell
+        surface object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="get_node">
+      <description summary="get the shell surface's render list node">
+        Get the node in the render list corresponding to the shell surface.
+
+        It is a protocol error to make this request more than once for a single
+        shell surface.
+      </description>
+      <arg name="id" type="new_id" interface="river_node_v1" summary="new node"/>
+    </request>
+
+    <request name="sync_next_commit">
+      <description summary="sync next surface commit to window manager commit">
+        Synchronize application of the next wl_surface.commit request on the
+        shell surface with rest of the rendering state atomically applied with
+        the next river_window_manager_v1.render_finish request.
+
+        The client must make a wl_surface.commit request on the shell surface
+        after this request and before the render_finish request, failure to do
+        so is a protocol error.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+  </interface>
+
+  <interface name="river_node_v1" version="4">
+    <description summary="a node in the render list">
+      The render list is a list of nodes that determines the rendering order of
+      the compositor. Nodes may correspond to windows or shell surfaces. The
+      relative ordering of nodes may be changed with the place_above and
+      place_below requests, changing the rendering order.
+
+      The initial position of a node in the render list is undefined, the window
+      manager client must use the place_above or place_below request to
+      guarantee a specific rendering order.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the decoration object">
+        This request indicates that the client will no longer use the node
+        object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="set_position">
+      <description summary="set absolute position of the node">
+        Set the absolute position of the node in the compositor's logical
+        coordinate space. The x and y coordinates may be positive or negative.
+
+        Note that the position of a river_window_v1 refers to the position of
+        the window content and is unaffected by the presence of borders or
+        decoration surfaces.
+
+        If this request is never sent, the position of the node is undefined by
+        this protocol and left up to the compositor.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="x" type="int" summary="global x coordinate"/>
+      <arg name="y" type="int" summary="global y coordinate"/>
+    </request>
+
+    <request name="place_top">
+      <description summary="place node above all other nodes">
+        This request places the node above all other nodes in the compositor's
+        render list.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="place_bottom">
+      <description summary="place node below all other nodes">
+        This request places the node below all other nodes in the compositor's
+        render list.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="place_above">
+      <description summary="place node above another node">
+        This request places the node directly above another node in the
+        compositor's render list.
+
+        Attempting to place a node above itself has no effect.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="other" type="object" interface="river_node_v1"
+        summary="other node"/>
+    </request>
+
+    <request name="place_below">
+      <description summary="place node below another node">
+        This request places the node directly below another node in the
+        compositor's render list.
+
+        Attempting to place a node below itself has no effect.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="other" type="object" interface="river_node_v1"
+        summary="other node"/>
+    </request>
+  </interface>
+
+  <interface name="river_output_v1" version="4">
+    <description summary="a logical output">
+      An area in the compositor's logical coordinate space that should be
+      treated as a single output for window management purposes. This area may
+      correspond to a single physical output or multiple physical outputs in the
+      case of mirroring or tiled monitors depending on the hardware and
+      compositor configuration.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the output object">
+        This request indicates that the client will no longer use the output
+        object and that it may be safely destroyed.
+
+        This request should be made after the river_output_v1.removed event is
+        received to complete destruction of the output.
+      </description>
+    </request>
+
+    <event name="removed">
+      <description summary="the output is removed">
+        This event indicates that the logical output is no longer conceptually
+        part of window management space.
+
+        The server will send no further events on this object and ignore any
+        request (other than river_output_v1.destroy) made after this event is
+        sent. The client should destroy this object with the
+        river_output_v1.destroy request to free up resources.
+
+        This event may be sent because a corresponding physical output has been
+        physically unplugged or because some output configuration has changed.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="wl_output">
+      <description summary="corresponding wl_output">
+        The wl_output object corresponding to the river_output_v1. The argument
+        is the global name of the wl_output advertised with wl_registry.global.
+
+        It is guaranteed that the corresponding wl_output is advertised before
+        this event is sent.
+
+        This event is sent exactly once. The wl_output associated with a
+        river_output_v1 cannot change. It is guaranteed that there is a 1-to-1
+        mapping between wl_output and river_output_v1 objects.
+
+        The global_remove event for the corresponding wl_output may be sent
+        before the river_output_v1.remove event. This is due to the fact that
+        river_output_v1 state changes are synced to the river window management
+        manage sequence while changes to globals are not.
+
+        Rationale: The window manager may need information provided by the
+        wl_output interface such as the name/description. It also may need the
+        wl_output object to start screencopy for example.
+      </description>
+      <arg name="name" type="uint" summary="name of the wl_output global"/>
+    </event>
+
+    <event name="position">
+      <description summary="output position">
+        This event indicates the position of the output in the compositor's
+        logical coordinate space. The x and y coordinates may be positive or
+        negative.
+
+        This event is sent once when the river_output_v1 is created and again
+        whenever the position changes.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The server must guarantee that the position and dimensions events do not
+        cause the areas of multiple logical outputs to overlap when the
+        corresponding manage_start event is received.
+      </description>
+      <arg name="x" type="int" summary="global x coordinate"/>
+      <arg name="y" type="int" summary="global y coordinate"/>
+    </event>
+
+    <event name="dimensions">
+      <description summary="output dimensions">
+        This event indicates the dimensions of the output in the compositor's
+        logical coordinate space. The width and height will always be strictly
+        greater than zero.
+
+        This event is sent once when the river_output_v1 is created and again
+        whenever the dimensions change.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The server must guarantee that the position and dimensions events do not
+        cause the areas of multiple logical outputs to overlap when the
+        corresponding manage_start event is received.
+      </description>
+      <arg name="width" type="int" summary="output width"/>
+      <arg name="height" type="int" summary="output height"/>
+    </event>
+
+    <enum name="error" since="4">
+      <entry name="invalid_presentation_mode" value="0" since="4"
+        summary="invalid presentation mode enum value"/>
+    </enum>
+
+    <enum name="presentation_mode" since="4">
+      <entry name="vsync" value="0">
+        <description summary="tearing-free presentation">
+          Output page-flips should be synchronized to the vertical blanking
+          period, eliminating tearing. This is the default presentation mode.
+        </description>
+      </entry>
+      <entry name="async" value="1">
+        <description summary="asynchronous presentation">
+          Output page-flips should not be synchronized to the vertical blanking
+          period, visual screen tearing may occur.
+        </description>
+      </entry>
+    </enum>
+
+    <request name="set_presentation_mode" since="4">
+      <description summary="set the preferred presentation mode">
+        Set the preferred presentation mode of the output. The compositor should
+        always respect the preference of the window manager if possible. If this
+        request is never made, the preferred presentation mode is vsync.
+
+        This request modifies rendering state and may only be made as part of a
+        render sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="mode" type="uint" enum="presentation_mode"
+        summary="preferred presentation mode"/>
+    </request>
+  </interface>
+
+  <interface name="river_seat_v1" version="4">
+    <description summary="a window management seat">
+      This object represents a single user's collection of input devices. It
+      allows the window manager to route keyboard input to windows, get
+      high-level information about pointer input, define pointer bindings, etc.
+
+      For keyboard bindings, see the river-xkb-bindings-v1 protocol.
+
+      Since version 4: The cursor surface/shape set by the window manager on the
+      wl_pointer of this seat is used when no client has pointer focus, for
+      example during a pointer operation. Since the window manager is allowed to
+      set cursor surface/shape even when it does not have pointer focus, the
+      compositor must ignore the serial argument of wl_pointer.set_cursor and
+      wp_cursor_shape_device_v1.set_shape requests made by the window manager.
+
+      The most recent cursor surface/shape set by the window manager is
+      remembered by the compositor and restored whenever no client has pointer
+      focus. If the window manager never sets a cursor surface/shape, the
+      "default" shape is used.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the seat object">
+        This request indicates that the client will no longer use the seat
+        object and that it may be safely destroyed.
+
+        This request should be made after the river_seat_v1.removed event is
+        received to complete destruction of the seat.
+      </description>
+    </request>
+
+    <event name="removed">
+      <description summary="the seat is removed">
+        This event indicates that seat is no longer in use and should be
+        destroyed.
+
+        The server will send no further events on this object and ignore any
+        request (other than river_seat_v1.destroy) made after this event is
+        sent.  The client should destroy this object with the
+        river_seat_v1.destroy request to free up resources.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="wl_seat">
+      <description summary="corresponding wl_seat">
+        The wl_seat object corresponding to the river_seat_v1. The argument is
+        the global name of the wl_seat advertised with wl_registry.global.
+
+        It is guaranteed that the corresponding wl_seat is advertised before
+        this event is sent.
+
+        This event is sent exactly once. The wl_seat associated with a
+        river_seat_v1 cannot change. It is guaranteed that there is a 1-to-1
+        mapping between wl_seat and river_seat_v1 objects.
+
+        The global_remove event for the corresponding wl_seat may be sent before
+        the river_seat_v1.remove event. This is due to the fact that
+        river_seat_v1 state changes are synced to the river window management
+        manage sequence while changes to globals are not.
+
+        Rationale: The window manager may want to trigger window management
+        state changes based on normal input events received by its shell
+        surfaces for example.
+      </description>
+      <arg name="name" type="uint" summary="name of the wl_seat global"/>
+    </event>
+
+    <request name="focus_window">
+      <description summary="give keyboard focus to a window">
+        Request that the compositor send keyboard input to the given window.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="window" type="object" interface="river_window_v1"
+        summary="window to focus"/>
+    </request>
+
+    <request name="focus_shell_surface">
+      <description summary="give keyboard focus to a shell_surface">
+        Request that the compositor send keyboard input to the given shell
+        surface.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="shell_surface" type="object" interface="river_shell_surface_v1"
+        summary="shell surface to focus"/>
+    </request>
+
+    <request name="clear_focus">
+      <description summary="clear keyboard focus">
+        Request that the compositor not send keyboard input to any client.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="pointer_enter">
+      <description summary="pointer entered a window">
+        The seat's pointer entered the given window's area.
+
+        The area of a window is defined to include the area defined by the
+        window dimensions, borders configured using river_window_v1.set_borders,
+        and the input regions of decoration surfaces. In particular, it does not
+        include input regions of surfaces belonging to the window that extend
+        outside the window dimensions.
+
+        The pointer of a seat may only enter a single window at a time. When the
+        pointer moves between windows, the pointer_leave event for the old
+        window must be sent before the pointer_enter event for the new window.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="window" type="object" interface="river_window_v1"
+        summary="window entered"/>
+    </event>
+
+    <event name="pointer_leave">
+      <description summary="pointer left the entered window">
+        The seat's pointer left the window for which pointer_enter was most
+        recently sent. See pointer_enter for details.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <event name="window_interaction">
+      <description summary="a window has been interacted with">
+        A window has been interacted with beyond the pointer merely passing over
+        it. This event might be sent due to a pointer button press or due to a
+        touch/tablet tool interaction with the window.
+
+        There are no guarantees regarding how this event is sent in relation to
+        the pointer_enter and pointer_leave events as the interaction may use
+        touch or tablet tool input.
+
+        Rationale: this event gives window managers necessary information to
+        determine when to send keyboard focus, raise a window that already has
+        keyboard focus, etc. Rather than expose all pointer, touch, and tablet
+        events to window managers, a policy over mechanism approach is taken.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="window" type="object" interface="river_window_v1"
+        summary="window interacted with"/>
+    </event>
+
+    <event name="shell_surface_interaction">
+      <description summary="a shell surface has been interacted with">
+        A shell surface has been interacted with beyond the pointer merely
+        passing over it. This event might be sent due to a pointer button press
+        or due to a touch/tablet tool interaction with the shell_surface.
+
+        There are no guarantees regarding how this event is sent in relation to
+        the pointer_enter and pointer_leave events as the interaction may use
+        touch or tablet tool input.
+
+        Rationale: While the shell surface does receive all wl_pointer,
+        wl_touch, etc. input events for the surface directly, these events do
+        not necessarily trigger a manage sequence and therefore do not allow the
+        window manager to update focus or perform other actions in response to
+        the input in a race-free way.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="shell_surface" type="object" interface="river_shell_surface_v1"
+        summary="shell surface interacted with"/>
+    </event>
+
+    <request name="op_start_pointer">
+      <description summary="start an interactive pointer operation">
+        Start an interactive pointer operation. During the operation, op_delta
+        events will be sent based on pointer input.
+
+        When all pointer buttons are released, the op_release event is sent.
+
+        The pointer operation continues until the op_end request is made during
+        a manage sequence and that manage sequence is finished.
+
+        The window manager may use this operation to implement interactive
+        move/resize of windows by setting the position of windows and proposing
+        dimensions based off of the op_delta events.
+
+        This request is ignored if an operation is already in progress.
+
+        The compositor must ensure that no client has pointer focus from this
+        seat during the pointer operation. This means that the window manager
+        has control over the pointer's cursor surface/shape during the pointer
+        operation. See the river_seat_v1 description.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="op_delta">
+      <description summary="total cumulative motion since op start">
+        This event indicates the total change in position since the start of the
+        operation of the pointer/touch point/etc.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+      <arg name="dx" type="int" summary="total change in x"/>
+      <arg name="dy" type="int" summary="total change in y"/>
+    </event>
+
+    <event name="op_release">
+      <description summary="operation input has been released">
+        The input driving the current interactive operation has been released.
+        For a pointer op for example, all pointer buttons have been released.
+
+        Depending on the op type, op_delta events may continue to be sent until
+        the op is ended with the op_end request.
+
+        This event is sent at most once during an interactive operation.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+
+    <request name="op_end">
+      <description summary="end an interactive operation">
+        End an interactive operation.
+
+        This request is ignored if there is no operation in progress.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <enum name="modifiers" bitfield="true">
+      <description summary="a set of keyboard modifiers">
+        This enum is used to describe the keyboard modifiers that must be held
+        down to trigger a key binding or pointer binding.
+
+        Note that river and wlroots use the values 2 and 16 for capslock and
+        numlock internally. It doesn't make sense to use locked modifiers for
+        bindings however so these values are not included in this enum.
+      </description>
+      <entry name="none" value="0"/>
+      <entry name="shift" value="1"/>
+      <entry name="ctrl" value="4"/>
+      <entry name="mod1" value="8" summary="commonly called alt"/>
+      <entry name="mod3" value="32"/>
+      <entry name="mod4" value="64" summary="commonly called super or logo"/>
+      <entry name="mod5" value="128"/>
+    </enum>
+
+    <request name="get_pointer_binding">
+      <description summary="define a new pointer binding">
+        Define a pointer binding in terms of a pointer button, keyboard
+        modifiers, and other configurable properties.
+
+        The button argument is a Linux input event code defined in the
+        linux/input-event-codes.h header file (e.g. BTN_RIGHT).
+
+        The new pointer binding is not enabled until initial configuration is
+        completed and the enable request is made during a manage sequence.
+      </description>
+      <arg name="id" type="new_id" interface="river_pointer_binding_v1"
+        summary="new pointer binding"/>
+      <arg name="button" type="uint" summary="a Linux input event code"/>
+      <arg name="modifiers" type="uint" enum="modifiers"
+        summary="keyboard modifiers"/>
+    </request>
+
+    <request name="set_xcursor_theme" since="2">
+      <description summary="set the xcursor theme for the seat">
+        Set the XCursor theme for the seat. This theme is used for cursors
+        rendered by the compositor, but not necessarily for cursors rendered by
+        clients.
+
+        Note: The window manager may also wish to set the XCURSOR_THEME and
+        XCURSOR_SIZE environment variable for programs it starts.
+      </description>
+      <arg name="name" type="string" summary="xcursor theme name"/>
+      <arg name="size" type="uint" summary="cursor size"/>
+    </request>
+
+    <event name="pointer_position" since="2">
+      <description summary="The current position of the pointer">
+        The current position of the pointer in the compositor's logical
+        coordinate space.
+
+        This state is special in that a change in pointer position alone must
+        not cause the compositor to start a manage sequence.
+
+        Assuming the seat has a pointer, this event must be sent in every manage
+        sequence unless there is no change in x/y position since the last time this
+        event was sent.
+      </description>
+      <arg name="x" type="int" summary="global x coordinate"/>
+      <arg name="y" type="int" summary="global y coordinate"/>
+    </event>
+
+    <request name="pointer_warp" since="3">
+      <description summary="warp the pointer to a given position">
+        Warp the pointer to the given position in the compositor's logical
+        coordinate space.
+
+        If the given position is outside the bounds of all outputs, the pointer
+        will be warped to the closest point inside an output instead.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="x" type="int" summary="global x coordinate"/>
+      <arg name="y" type="int" summary="global y coordinate"/>
+    </request>
+  </interface>
+
+  <interface name="river_pointer_binding_v1" version="4">
+    <description summary="configure a pointer binding, receive trigger events">
+      This object allows the window manager to configure a pointer binding and
+      receive events when the binding is triggered.
+
+      The new pointer binding is not enabled until the enable request is made
+      during a manage sequence.
+
+      Normally, all pointer button events are sent to the surface with pointer
+      focus by the compositor. Pointer button events that trigger a pointer
+      binding are not sent to the surface with pointer focus.
+
+      If multiple pointer bindings would be triggered by a single physical
+      pointer event on the compositor side, it is compositor policy which
+      pointer binding(s) will receive press/release events or if all of the
+      matched pointer bindings receive press/release events.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the pointer binding object">
+        This request indicates that the client will no longer use the pointer
+        binding object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="enable">
+      <description summary="enable the pointer binding">
+        This request should be made after all initial configuration has been
+        completed and the window manager wishes the pointer binding to be able
+        to be triggered.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="disable">
+      <description summary="disable the pointer binding">
+        This request may be used to temporarily disable the pointer binding. It
+        may be later re-enabled with the enable request.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="pressed">
+      <description summary="the bound pointer button has been pressed">
+        This event indicates that the pointer button triggering the binding has
+        been pressed.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The compositor should wait for the manage sequence to complete before
+        processing further input events. This allows the window manager client
+        to, for example, modify key bindings and keyboard focus without racing
+        against future input events. The window manager should of course respond
+        as soon as possible as the capacity of the compositor to buffer incoming
+        input events is finite.
+      </description>
+    </event>
+
+    <event name="released">
+      <description summary="the bound pointer button has been released">
+        This event indicates that the pointer button triggering the binding has
+        been released.
+
+        Releasing the modifiers for the binding without releasing the pointer
+        button does not trigger the release event. This event is sent when the
+        pointer button is released, even if the modifiers have changed since the
+        pressed event.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The compositor should wait for the manage sequence to complete before
+        processing further input events. This allows the window manager client
+        to, for example, modify key bindings and keyboard focus without racing
+        against future input events. The window manager should of course respond
+        as soon as possible as the capacity of the compositor to buffer incoming
+        input events is finite.
+      </description>
+    </event>
+  </interface>
+</protocol>
diff --git a/protocol/river-xkb-bindings-v1.xml b/protocol/river-xkb-bindings-v1.xml
new file mode 100644
index 0000000..de4f100
--- /dev/null
+++ b/protocol/river-xkb-bindings-v1.xml
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="river_xkb_bindings_v1">
+  <copyright>
+    SPDX-FileCopyrightText: © 2025 Isaac Freund
+    SPDX-License-Identifier: MIT
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="xkbcommon-based key bindings">
+    This protocol allows the river-window-management-v1 window manager to
+    define key bindings in terms of xkbcommon keysyms and other configurable
+    properties.
+
+    The key words "must", "must not", "required", "shall", "shall not",
+    "should", "should not", "recommended", "may", and "optional" in this
+    document are to be interpreted as described in IETF RFC 2119.
+  </description>
+
+  <interface name="river_xkb_bindings_v1" version="2">
+    <description summary="xkbcommon bindings global interface">
+      This global interface should only be advertised to the client if the
+      river_window_manager_v1 global is also advertised.
+    </description>
+
+    <enum name="error" since="2">
+      <entry name="object_already_created" value="0" since="2"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the river_xkb_bindings_v1 object">
+        This request indicates that the client will no longer use the
+        river_xkb_bindings_v1 object.
+      </description>
+    </request>
+
+    <request name="get_xkb_binding">
+      <description summary="define a new xkbcommon key binding">
+        Define a key binding for the given seat in terms of an xkbcommon keysym
+        and other configurable properties.
+
+        The new key binding is not enabled until initial configuration is
+        completed and the enable request is made during a manage sequence.
+      </description>
+      <arg name="seat" type="object" interface="river_seat_v1"/>
+      <arg name="id" type="new_id" interface="river_xkb_binding_v1"/>
+      <arg name="keysym" type="uint" summary="an xkbcommon keysym"/>
+      <arg name="modifiers" type="uint" enum="river_seat_v1.modifiers"/>
+    </request>
+
+    <request name="get_seat" since="2">
+      <description summary="manage seat-specific state">
+        Create an object to manage seat-specific xkb bindings state.
+
+        It is a protocol error to make this request more than once for a given
+        river_seat_v1 object.
+      </description>
+      <arg name="id" type="new_id" interface="river_xkb_bindings_seat_v1"/>
+      <arg name="seat" type="object" interface="river_seat_v1"/>
+    </request>
+  </interface>
+
+  <interface name="river_xkb_binding_v1" version="2">
+    <description summary="configure a xkb key binding, receive trigger events">
+      This object allows the window manager to configure a xkbcommon key binding
+      and receive events when the key binding is triggered.
+
+      The new key binding is not enabled until the enable request is made during
+      a manage sequence.
+
+      Normally, all key events are sent to the surface with keyboard focus by
+      the compositor. Key events that trigger a key binding are not sent to the
+      surface with keyboard focus.
+
+      If multiple key bindings would be triggered by a single physical key event
+      on the compositor side, it is compositor policy which key binding(s) will
+      receive press/release events or if all of the matched key bindings receive
+      press/release events.
+
+      Key bindings might be matched by the same physical key event due to shared
+      keysym and modifiers. The layout override feature may also cause the same
+      physical key event to trigger two key bindings with different keysyms and
+      different layout overrides configured.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the xkb binding object">
+        This request indicates that the client will no longer use the xkb key
+        binding object and that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="set_layout_override">
+      <description summary="override currently active xkb layout">
+        Specify an xkb layout that should be used to translate key events for
+        the purpose of triggering this key binding irrespective of the currently
+        active xkb layout.
+
+        The layout argument is a 0-indexed xkbcommon layout number for the
+        keyboard that generated the key event.
+
+        If this request is never made, the currently active xkb layout of the
+        keyboard that generated the key event will be used.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+      <arg name="layout" type="uint" summary="0-indexed xkbcommon layout"/>
+    </request>
+
+    <request name="enable">
+      <description summary="enable the key binding">
+        This request should be made after all initial configuration has been
+        completed and the window manager wishes the key binding to be able to be
+        triggered.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="disable">
+      <description summary="disable the key binding">
+        This request may be used to temporarily disable the key binding. It may
+        be later re-enabled with the enable request.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="pressed">
+      <description summary="the key triggering the binding has been pressed">
+        This event indicates that the physical key triggering the binding has
+        been pressed.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The compositor should wait for the manage sequence to complete before
+        processing further input events. This allows the window manager client
+        to, for example, modify key bindings and keyboard focus without racing
+        against future input events. The window manager should of course respond
+        as soon as possible as the capacity of the compositor to buffer incoming
+        input events is finite.
+      </description>
+    </event>
+
+    <event name="released">
+      <description summary="the key triggering the binding has been released">
+        This event indicates that the physical key triggering the binding has
+        been released.
+
+        Releasing the modifiers for the binding without releasing the "main"
+        physical key that produces the bound keysym does not trigger the release
+        event. This event is sent when the "main" key is released, even if the
+        modifiers have changed since the pressed event.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+
+        The compositor should wait for the manage sequence to complete before
+        processing further input events. This allows the window manager client
+        to, for example, modify key bindings and keyboard focus without racing
+        against future input events. The window manager should of course respond
+        as soon as possible as the capacity of the compositor to buffer incoming
+        input events is finite.
+      </description>
+    </event>
+
+    <event name="stop_repeat" since="2">
+      <description summary="repeating should be stopped">
+        This event indicates that repeating should be stopped for the binding if
+        the window manager has been repeating some action since the pressed
+        event.
+
+        This event is generally sent when some other (possible unbound) key is
+        pressed after the pressed event is sent and before the released event
+        is sent for this binding.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="river_xkb_bindings_seat_v1" version="2">
+    <description summary="xkb bindings seat">
+      This object manages xkb bindings state associated with a specific seat.
+    </description>
+
+    <request name="destroy" type="destructor" since="2">
+      <description summary="destroy the object">
+        This request indicates that the client will no longer use the object and
+        that it may be safely destroyed.
+      </description>
+    </request>
+
+    <request name="ensure_next_key_eaten" since="2">
+      <description summary="ensure the next key press event is eaten">
+        Ensure that the next non-modifier key press and corresponding release
+        events for this seat are not sent to the currently focused surface.
+
+        If the next non-modifier key press triggers a binding, the
+        pressed/released events are sent to the river_xkb_binding_v1 object as
+        usual.
+
+        If the next non-modifier key press does not trigger a binding, the
+        ate_unbound_key event is sent instead.
+
+        Rationale: the window manager may wish to implement "chorded"
+        keybindings where triggering a binding activates a "submap" with a
+        different set of keybindings. Without a way to eat the next key
+        press event, there is no good way for the window manager to know that it
+        should error out and exit the submap when a key not bound in the submap
+        is pressed.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <request name="cancel_ensure_next_key_eaten" since="2">
+      <description summary="cancel an ensure_next_key_eaten request">
+        This requests cancels the effect of the latest ensure_next_key_eaten
+        request if no key has been eaten due to the request yet. This request
+        has no effect if a key has already been eaten or no
+        ensure_next_key_eaten was made.
+
+        Rationale: the window manager may wish cancel an uncompleted "chorded"
+        keybinding after a timeout of a few seconds. Note that since this
+        timeout use-case requires the window manager to trigger a manage sequence
+        with the river_window_manager_v1.manage_dirty request it is possible that
+        the ate_unbound_key key event may be sent before the window manager has
+        a chance to make the cancel_ensure_next_key_eaten request.
+
+        This request modifies window management state and may only be made as
+        part of a manage sequence, see the river_window_manager_v1 description.
+      </description>
+    </request>
+
+    <event name="ate_unbound_key" since="2">
+      <description summary="an unbound key press event was eaten">
+        An unbound key press event was eaten due to the ensure_next_key_eaten
+        request.
+
+        This event will be followed by a manage_start event after all other new
+        state has been sent by the server.
+      </description>
+    </event>
+  </interface>
+</protocol>
diff --git a/src/clearctl.rs b/src/clearctl.rs
new file mode 100644
index 0000000..7dd073b
--- /dev/null
+++ b/src/clearctl.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("clearctl placeholder");
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ec7d568
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("clearwm starting...");
+}