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

commitc7d134ff54e031f60843d60ada831185322a4602
parentd795162545
authorLucas Galante <[email protected]>
date2026-05-26 22:53
Initialize clear-river-rust port project on rust-port branch

 .builds/alpine.yml                                 |   65 -
 .builds/archlinux.yml                              |   64 -
 .builds/freebsd.yml                                |   65 -
 .editorconfig                                      |   22 -
 .forgejo/issue_template/bug.yml                    |   73 -
 .forgejo/issue_template/config.yml                 |    8 -
 .forgejo/issue_template/use_case.yml               |   22 -
 .gitignore                                         |    7 +-
 .mailmap                                           |    6 -
 CODE_OF_CONDUCT.md                                 |   87 -
 CONTRIBUTING.md                                    |   77 -
 Cargo.toml                                         |    6 +
 LICENSES/0BSD.txt                                  |    5 -
 LICENSES/CC-BY-SA-4.0.txt                          |  170 --
 LICENSES/GPL-3.0-only.txt                          |  232 ---
 LICENSES/MIT.txt                                   |   18 -
 PACKAGING.md                                       |  183 --
 README.md                                          |  129 --
 build.zig                                          |  260 ---
 build.zig.zon                                      |   36 -
 common/flags.zig                                   |   73 -
 common/slotmap.zig                                 |  271 ---
 contrib/river.desktop                              |    8 -
 doc/river.1.scd                                    |   73 -
 logo/logo.svg                                      |    7 -
 logo/logo_text.svg                                 |    7 -
 logo/logo_text_adaptive_color.svg                  |   12 -
 protocol/river-input-management-v1.xml             |  244 ---
 protocol/river-layer-shell-v1.xml                  |  191 --
 protocol/river-libinput-config-v1.xml              |  901 ----------
 protocol/river-window-management-v1.xml            | 1859 --------------------
 protocol/river-xkb-bindings-v1.xml                 |  314 ----
 protocol/river-xkb-config-v1.xml                   |  287 ---
 protocol/upstream/virtual-keyboard-unstable-v1.xml |  114 --
 protocol/upstream/wlr-layer-shell-unstable-v1.xml  |  408 -----
 .../wlr-output-power-management-unstable-v1.xml    |  129 --
 river/Cursor.zig                                   |  953 ----------
 river/Decoration.zig                               |  165 --
 river/DragIcon.zig                                 |   66 -
 river/IdleInhibitManager.zig                       |   61 -
 river/IdleInhibitor.zig                            |   52 -
 river/InputDevice.zig                              |  298 ----
 river/InputManager.zig                             |  287 ---
 river/InputPopup.zig                               |  173 --
 river/InputRelay.zig                               |  233 ---
 river/Keyboard.zig                                 |  250 ---
 river/KeyboardGroup.zig                            |  457 -----
 river/LayerShell.zig                               |  193 --
 river/LayerShellOutput.zig                         |  180 --
 river/LayerShellSeat.zig                           |   93 -
 river/LayerSurface.zig                             |  180 --
 river/LibinputAccelConfig.zig                      |  111 --
 river/LibinputConfig.zig                           |  105 --
 river/LibinputDevice.zig                           |  583 ------
 river/LockManager.zig                              |  245 ---
 river/LockSurface.zig                              |  124 --
 river/Output.zig                                   |  548 ------
 river/OutputManager.zig                            |  488 -----
 river/PointerBinding.zig                           |  149 --
 river/PointerConstraint.zig                        |  222 ---
 river/Scene.zig                                    |  201 ---
 river/SceneNodeData.zig                            |   71 -
 river/Seat.zig                                     | 1050 -----------
 river/Server.zig                                   |  565 ------
 river/ShellSurface.zig                             |  162 --
 river/Tablet.zig                                   |   40 -
 river/TabletTool.zig                               |  270 ---
 river/TextInput.zig                                |  112 --
 river/Window.zig                                   | 1221 -------------
 river/WindowManager.zig                            |  566 ------
 river/WmNode.zig                                   |  136 --
 river/XdgDecoration.zig                            |   68 -
 river/XdgPopup.zig                                 |  102 --
 river/XdgToplevel.zig                              |  477 -----
 river/XkbBinding.zig                               |  228 ---
 river/XkbBindings.zig                              |   88 -
 river/XkbBindingsSeat.zig                          |  128 --
 river/XkbConfig.zig                                |  166 --
 river/XkbKeyboard.zig                              |  217 ---
 river/XkbKeymap.zig                                |   80 -
 river/XwaylandOverrideRedirect.zig                 |  201 ---
 river/XwaylandWindow.zig                           |  346 ----
 river/c.h                                          |    6 -
 river/main.zig                                     |  333 ----
 river/process.zig                                  |   64 -
 river/util.zig                                     |   27 -
 river/wlroots_log_wrapper.c                        |   45 -
 src/main.rs                                        |    3 +
 88 files changed, 10 insertions(+), 19612 deletions(-)

diff --git a/.builds/alpine.yml b/.builds/alpine.yml
deleted file mode 100644
index f7886d0..0000000
--- a/.builds/alpine.yml
+++ /dev/null
@@ -1,65 +0,0 @@
-# SPDX-FileCopyrightText: © 2022 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-submitter:
-  git.sr.ht:
-    enabled: false
-
-image: alpine/edge
-packages:
-  - eudev-dev
-  - libinput-dev
-  - libseat-dev
-  - mesa-dev
-  - libffi-dev
-  - expat-dev
-  - hwdata-dev
-  - libdisplay-info-dev
-  - libxkbcommon-dev
-  - xcb-util-image-dev
-  - xcb-util-renderutil-dev
-  - xcb-util-wm-dev
-  - pixman-dev
-  - libevdev-dev
-  - wayland-dev
-  - wayland-protocols
-  - xwayland-dev
-  - meson
-  - reuse
-  - scdoc
-  - tar
-  - wget
-  - xz
-sources:
-  - https://codeberg.org/river/river
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git
-tasks:
-  - install_deps: |
-      cd wlroots
-      git checkout 0.20.0
-      meson setup build --auto-features=enabled -Drenderers=gles2 \
-            -Dcolor-management=disabled -Dlibliftoff=disabled \
-            -Dexamples=false -Dwerror=false -Db_ndebug=false \
-            -Dxcb-errors=disabled --prefix /usr
-      sudo ninja -C build/ install
-      cd ..
-
-      # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
-      tar xf zig-x86_64-linux-0.16.0.tar.xz
-      sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
-      sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
-  - build: |
-      cd river
-      zig build --summary all
-  - build_xwayland: |
-      cd river
-      zig build --summary all -Dxwayland
-  - fmt: |
-      cd river
-      zig fmt --check river/
-      zig fmt --check build.zig
-      zig fmt --check build.zig.zon
-  - reuse: |
-      cd river
-      reuse lint
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
deleted file mode 100644
index 48f4075..0000000
--- a/.builds/archlinux.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-# SPDX-FileCopyrightText: © 2022 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-submitter:
-  git.sr.ht:
-    enabled: false
-
-image: archlinux
-packages:
-  - libdisplay-info
-  - libevdev
-  - libinput
-  - libxkbcommon
-  - mesa
-  - meson
-  - pixman
-  - wayland
-  - wayland-protocols
-  - hwdata
-  - xcb-util-errors
-  - xcb-util-image
-  - xcb-util-renderutil
-  - xcb-util-wm
-  - xorg-xwayland
-  - reuse
-  - seatd
-  - tar
-  - wget
-  - xz
-sources:
-  - https://codeberg.org/river/river
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git
-tasks:
-  - install_deps: |
-      cd wlroots
-      git checkout 0.20.0
-      meson setup build --auto-features=enabled -Drenderers=gles2 \
-            -Dcolor-management=disabled -Dlibliftoff=disabled \
-            -Dexamples=false -Dwerror=false -Db_ndebug=false \
-            -Dxcb-errors=disabled --prefix /usr
-      sudo ninja -C build/ install
-      cd ..
-
-      # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
-      tar xf zig-x86_64-linux-0.16.0.tar.xz
-      sudo mv zig-x86_64-linux-0.16.0/zig /usr/bin/
-      sudo mv zig-x86_64-linux-0.16.0/lib /usr/lib/zig
-  - build: |
-      cd river
-      # Arch's glibc version has SFrame sections in crt objects, force llvm/lld usage as a workaround
-      # https://codeberg.org/ziglang/zig/issues/31272
-      zig build -Dllvm --summary all
-  - build_xwayland: |
-      cd river
-      zig build -Dllvm --summary all -Dxwayland
-  - fmt: |
-      cd river
-      zig fmt --check river/
-      zig fmt --check build.zig
-      zig fmt --check build.zig.zon
-  - reuse: |
-      cd river
-      reuse lint
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
deleted file mode 100644
index 7d9087f..0000000
--- a/.builds/freebsd.yml
+++ /dev/null
@@ -1,65 +0,0 @@
-# SPDX-FileCopyrightText: © 2022 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-submitter:
-  git.sr.ht:
-    enabled: false
-
-image: freebsd/latest
-packages:
-  - devel/evdev-proto
-  - devel/libevdev
-  - devel/libepoll-shim
-  - devel/libudev-devd
-  - devel/meson
-  - devel/pkgconf
-  - graphics/mesa-libs
-  - graphics/wayland
-  - graphics/wayland-protocols
-  - misc/hwdata
-  - x11/libX11
-  - x11/libinput
-  - x11/libxcb
-  - x11/libxkbcommon
-  - x11/pixman
-  - x11/xcb-util-errors
-  - x11/xcb-util-renderutil
-  - x11/xcb-util-wm
-  - x11-servers/xwayland
-  - security/ca_root_nss
-  - sysutils/seatd
-  - sysutils/libdisplay-info
-  - gmake
-  - scdoc
-  - wget
-sources:
-  - https://codeberg.org/river/river
-  - https://gitlab.freedesktop.org/wlroots/wlroots.git
-tasks:
-  - install_deps: |
-      cd wlroots
-      git checkout 0.20.0
-      meson setup build --auto-features=enabled -Drenderers=gles2 \
-            -Dallocators=gbm \
-            -Dcolor-management=disabled -Dlibliftoff=disabled \
-            -Dexamples=false -Dwerror=false -Db_ndebug=false \
-            -Dxcb-errors=disabled --prefix /usr
-      sudo ninja -C build/ install
-      cd ..
-
-      # Eat Github's resources rather than the Zig Software Foundation's resources!
-      wget -nv https://github.com/ifreund/zig-tarball-mirror/releases/download/0.16.0/zig-x86_64-freebsd-0.16.0.tar.xz
-      tar xf zig-x86_64-freebsd-0.16.0.tar.xz
-      sudo mv zig-x86_64-freebsd-0.16.0/zig /usr/bin/
-      sudo mv zig-x86_64-freebsd-0.16.0/lib /usr/lib/zig
-  - build: |
-      cd river
-      zig build --summary all
-  - build_xwayland: |
-      cd river
-      zig build --summary all -Dxwayland
-  - fmt: |
-      cd river
-      zig fmt --check river/
-      zig fmt --check build.zig
-      zig fmt --check build.zig.zon
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 27d43c0..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,22 +0,0 @@
-# SPDX-FileCopyrightText: © 2020 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.zig]
-indent_style = space
-indent_size = 4
-
-[*.{xml,yml}]
-indent_style = space
-indent_size = 2
-
-[*.scd]
-indent_style = tab
-indent_size = 4
diff --git a/.forgejo/issue_template/bug.yml b/.forgejo/issue_template/bug.yml
deleted file mode 100644
index 4d897c0..0000000
--- a/.forgejo/issue_template/bug.yml
+++ /dev/null
@@ -1,73 +0,0 @@
-# SPDX-FileCopyrightText: © 2024 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-name: Bug Report
-description: File a bug report
-labels: ["bug"]
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Please provide as many details as possible, we must be able to
-        understand the bug in order to fix it.
-
-        Don't forget to search the issue tracker in case there is
-        already an open issue for the bug you found.
-  - type: input
-    id: river-version
-    attributes:
-      label: River Version
-      description: |
-        The output of `river -version`.
-        If the version starts with 0.3, please open an issue on the
-        [river-classic](https://codeberg.org/river/river-classic/) repository instead.
-      placeholder: "0.4.0-dev.247+a050b8b"
-    validations:
-      required: true
-  - type: input
-    id: wm
-    attributes:
-      label: Window Manager
-    validations:
-      required: true
-  - type: input
-    id: wm-version
-    attributes:
-      label: Window Manager Version
-    validations:
-      required: true
-  - type: textarea
-    id: repro
-    attributes:
-      label: Description of Bug and Steps to Reproduce
-      description: Exactly what steps can someone else take to see the bug themselves? What happens?
-    validations:
-      required: true
-  - type: markdown
-    attributes:
-      value: |
-        Please provide as many details as possible, we must be able to
-        understand the bug in order to fix it.
-
-        Use a debug build of river if possible to get a better quality
-        stacktrace in the case of a crash:
-        ```
-        zig build -Doptimize=Debug <other desired options>
-        ```
-
-        Set the log level to debug and redirect river's stderr to a file:
-        ```
-        river -log-level debug 2> river.log
-        ```
-        Reproduce your problem and exit river.
-
-        If the issue is with a specific Wayland client (e.g. firefox) a log of the
-        Wayland protocol messages between the client and river is often very helpful.
-        Obtain one by setting WAYLAND_DEBUG=1 and redirecting stderr to a file.
-        ```
-        WAYLAND_DEBUG=1 firefox 2> firefox.log
-        ```
-  - type: textarea
-    id: logs
-    attributes:
-      label: Relevant logs, Stacktraces, etc.
diff --git a/.forgejo/issue_template/config.yml b/.forgejo/issue_template/config.yml
deleted file mode 100644
index a8e36db..0000000
--- a/.forgejo/issue_template/config.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-# SPDX-FileCopyrightText: © 2024 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-blank_issues_enabled: true
-contact_links:
-  - name: IRC
-    url: https://web.libera.chat/?channels=#river
-    about: Join the IRC channel for river-related discussion and support
diff --git a/.forgejo/issue_template/use_case.yml b/.forgejo/issue_template/use_case.yml
deleted file mode 100644
index 03854a8..0000000
--- a/.forgejo/issue_template/use_case.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# SPDX-FileCopyrightText: © 2026 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-name: Use Case
-description: Describe a use case not supported by river
-labels: ["use case"]
-body:
-  - type: textarea
-    id: description
-    attributes:
-      label: Use Case Description
-      description: |
-        Please describe your use case in detail.
-
-        Do you have a suggestion for how river could support it?
-
-        Are there any workarounds you have tried?
-
-        Don't forget to search the issue tracker in case there is
-        already an open issue tracking support for your use case!
-    validations:
-      required: true
diff --git a/.gitignore b/.gitignore
index f6ce2cb..ea8c4bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1 @@
-# SPDX-FileCopyrightText: © 2020 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-.zig-cache/
-zig-out/
-zig-pkg/
+/target
diff --git a/.mailmap b/.mailmap
deleted file mode 100644
index 2982110..0000000
--- a/.mailmap
+++ /dev/null
@@ -1,6 +0,0 @@
-# SPDX-FileCopyrightText: © 2024 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-Isaac Freund <[email protected]> <[email protected]>
-Hugo Machet <[email protected]> <[email protected]>
-Hugo Machet <[email protected]> <[email protected]>
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index 6e9dd03..0000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,87 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2024 The River Developers
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-# River Code of Conduct
-
-## Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, caste, color, religion, or sexual
-identity and orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
-  and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the overall
-  community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or advances of
-  any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email address,
-  without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
-  professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-Participants in the river community are expected to uphold the described
-standards not only in official community spaces (issue trackers, IRC channels,
-etc.) but in all public spaces. The Code of Conduct however does acknowledge
-that people are fallible and that it is possible to truely correct a past
-pattern of unacceptable behavior. That is to say, the scope of the Code of
-Conduct does not necessarily extend into the distant past.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-[[email protected]](mailto:[email protected]).
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-The consequences for Code of Conduct violations will be decided upon and
-enforced by community leaders. These may include a formal warning, a temporary
-ban from community spaces, a permanent ban from community spaces, etc.
-
-There are no hard and fast rules for exactly what behavior in which space will
-result in what consequences, it is up to the community leaders to enforce the
-Code of Conduct in a way that they believe best promotes a healthy community.
-
-## Attribution
-
-This Code of Conduct is adapted from the
-[Contributor Covenant](https://www.contributor-covenant.org/),
-version 2.1, available at
-https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index cc2ac04..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,77 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2020 The River Developers
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-
-## Contributing to river
-
-Contributing is as simple as opening a pull request on
-[codeberg](https://codeberg.org/river/river).
-You'll likely have more success with your contribution if you visit
-[#river](https://web.libera.chat/?channels=#river) on irc.libera.chat to discuss
-your plans first.
-
-## Strict No LLM / No AI Policy
-
-Use of generative AI/LLMs is strictly forbidden for all contributions to river.
-
-This includes bug reports and comments on the issue tracker.
-
-## Commit messages
-
-Please take the time to write a good commit message, having a clean git
-history makes maintaining and contributing to river easier. Commit messages
-should start with a prefix indicating what part of river is affected by the
-change, followed by a brief summary.
-
-For example:
-
-```
-build: add -Dxwayland option
-```
-
-or
-
-```
-Keyboard: fix redundant leave/enter on creation
-```
-
-In addition to the summary, feel free to add any other details you want preceded
-by a blank line. A good rule of thumb is that anything you would write in a pull
-request description on codeberg has a place in the commit message as well.
-
-For further details regarding commit style and git history see
-[weston's contributing guidelines](https://gitlab.freedesktop.org/wayland/weston/-/blob/master/CONTRIBUTING.md#formatting-and-separating-commits).
-
-## Coding style
-
-Please follow the
-[Zig Style Guide](https://ziglang.org/documentation/master/#Style-Guide)
-and run `zig fmt` before every commit. With regards to line length, keep it
-under 100 columns in general but prioritize readability over adhering to a
-strict limit. Note that inserting a trailing comma after the last parameter in
-function calls, struct declarations, etc. will cause `zig fmt` to wrap those
-lines. I highly recommend configuring your editor to run `zig fmt` on write.
-
-The single additional style rule is to avoid writing `if` statements and
-similar across multiple lines without braces:
-
-```zig
-test {
-    // avoid this
-    if (foo)
-        bar();
-
-    // prefer this
-    if (foo) bar();
-
-    // or this
-    if (foo) {
-        bar();
-    }
-}
-```
-
-On a higher level, prioritize simplicity of code over nearly everything else.
-Performance is only a valid reason for code complexity if there are profiling
-results to back it up which demonstrate a significant benefit.
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..aa6c7a9
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "clear-river-rust"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/LICENSES/0BSD.txt b/LICENSES/0BSD.txt
deleted file mode 100644
index 0b8ae76..0000000
--- a/LICENSES/0BSD.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Copyright (C) YEAR by AUTHOR EMAIL
-
-Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/LICENSES/CC-BY-SA-4.0.txt b/LICENSES/CC-BY-SA-4.0.txt
deleted file mode 100644
index 835a683..0000000
--- a/LICENSES/CC-BY-SA-4.0.txt
+++ /dev/null
@@ -1,170 +0,0 @@
-Creative Commons Attribution-ShareAlike 4.0 International
-
- Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
-
-Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
-
-Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors.
-
-Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described.
-
-Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public.
-
-Creative Commons Attribution-ShareAlike 4.0 International Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
-
-Section 1 – Definitions.
-
-     a.	Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
-
-     b.	Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
-
-     c.	BY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.
-
-     d.	Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
-
-     e.	Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
-
-     f.	Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
-
-     g.	License Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.
-
-     h.	Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
-
-     i.	Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
-
-     j.	Licensor means the individual(s) or entity(ies) granting rights under this Public License.
-
-     k.	Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
-
-     l.	Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
-
-     m.	You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
-
-Section 2 – Scope.
-
-     a.	License grant.
-
-          1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
-
-               A. reproduce and Share the Licensed Material, in whole or in part; and
-
-               B. produce, reproduce, and Share Adapted Material.
-
-          2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
-
-          3. Term. The term of this Public License is specified in Section 6(a).
-
-          4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
-
-          5. Downstream recipients.
-
-               A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
-
-               B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.
-
-               C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
-
-          6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
-
-     b.	Other rights.
-
-          1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
-
-          2. Patent and trademark rights are not licensed under this Public License.
-
-          3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
-
-Section 3 – License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the following conditions.
-
-     a.	Attribution.
-
-          1. If You Share the Licensed Material (including in modified form), You must:
-
-               A. retain the following if it is supplied by the Licensor with the Licensed Material:
-
-                    i.	identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
-
-                    ii.	a copyright notice;
-
-                    iii. a notice that refers to this Public License;
-
-                    iv.	a notice that refers to the disclaimer of warranties;
-
-                    v.	a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
-
-               B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
-
-               C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
-
-          2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
-
-          3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
-
-     b.	ShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
-
-          1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.
-
-          2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
-
-          3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
-
-Section 4 – Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
-
-     a.	for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
-
-     b.	if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
-
-     c.	You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
-For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
-
-Section 5 – Disclaimer of Warranties and Limitation of Liability.
-
-     a.	Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.
-
-     b.	To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.
-
-     c.	The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
-
-Section 6 – Term and Termination.
-
-     a.	This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
-
-     b.	Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
-
-          1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
-
-          2. upon express reinstatement by the Licensor.
-
-     c.	For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
-
-     d.	For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
-
-     e.	Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
-
-Section 7 – Other Terms and Conditions.
-
-     a.	The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
-
-     b.	Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
-
-Section 8 – Interpretation.
-
-     a.	For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
-
-     b.	To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
-
-     c.	No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
-
-     d.	Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
-
-Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
-
-Creative Commons may be contacted at creativecommons.org.
diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt
deleted file mode 100644
index f6cdd22..0000000
--- a/LICENSES/GPL-3.0-only.txt
+++ /dev/null
@@ -1,232 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
-Version 3, 29 June 2007
-
-Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
-
-Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
-
-Preamble
-
-The GNU General Public License is a free, copyleft license for software and other kinds of works.
-
-The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
-
-When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
-
-To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
-
-For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
-
-Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
-
-For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
-
-Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
-
-Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
-
-The precise terms and conditions for copying, distribution and modification follow.
-
-TERMS AND CONDITIONS
-
-0. Definitions.
-
-“This License” refers to version 3 of the GNU General Public License.
-
-“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
-
-“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
-
-To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
-
-A “covered work” means either the unmodified Program or a work based on the Program.
-
-To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
-
-To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
-
-An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
-
-1. Source Code.
-The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
-
-A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
-
-The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
-
-The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
-
-The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
-
-The Corresponding Source for a work in source code form is that same work.
-
-2. Basic Permissions.
-All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
-
-You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
-
-Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
-
-3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
-
-When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
-
-4. Conveying Verbatim Copies.
-You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
-
-You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
-
-5. Conveying Modified Source Versions.
-You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
-
-     a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
-
-     b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
-
-     c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
-
-     d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
-
-A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
-
-6. Conveying Non-Source Forms.
-You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
-
-     a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
-
-     b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
-
-     c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
-
-     d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
-
-     e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
-
-A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
-
-A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
-
-“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
-
-If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
-
-The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
-
-Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
-
-7. Additional Terms.
-“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
-
-When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
-
-Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
-
-     a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
-
-     b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
-
-     c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
-
-     d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
-
-     e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
-
-     f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
-
-All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
-
-If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
-
-Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
-
-8. Termination.
-You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
-
-However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
-
-Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
-
-Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
-
-9. Acceptance Not Required for Having Copies.
-You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
-
-10. Automatic Licensing of Downstream Recipients.
-Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
-
-An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
-
-You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
-
-11. Patents.
-A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
-
-A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
-
-Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
-
-In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
-
-If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
-
-If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
-
-A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
-
-Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
-
-12. No Surrender of Others' Freedom.
-If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
-
-13. Use with the GNU Affero General Public License.
-Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
-
-14. Revised Versions of this License.
-The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
-
-If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
-
-Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
-
-15. Disclaimer of Warranty.
-THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-16. Limitation of Liability.
-IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
-
-17. Interpretation of Sections 15 and 16.
-If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
-
-END OF TERMS AND CONDITIONS
-
-How to Apply These Terms to Your New Programs
-
-If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
-
-To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
-
-     <one line to give the program's name and a brief idea of what it does.>
-     Copyright (C) <year>  <name of author>
-
-     This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
-
-     This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-     You should have received a copy of the GNU General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
-
-     <program>  Copyright (C) <year>  <name of author>
-     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-     This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
-
-You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
-
-The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt
deleted file mode 100644
index d817195..0000000
--- a/LICENSES/MIT.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-MIT License
-
-Copyright (c) <year> <copyright holders>
-
-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.
diff --git a/PACKAGING.md b/PACKAGING.md
deleted file mode 100644
index 2f33dad..0000000
--- a/PACKAGING.md
+++ /dev/null
@@ -1,183 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2022 The River Developers
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-
-# Packaging river for distribution
-
-First of all, I apologize for writing river in Zig. It will likely make
-your job harder until Zig is more mature/stable. I do however believe that
-writing my software in Zig allows me to deliver the best quality I can
-despite the drawbacks of depending on a relatively immature language/toolchain.
-
-## Source tarballs
-
-Source tarballs with stable checksums and git submodule sources included may
-be found on the [codeberg releases page](https://codeberg.org/river/river/releases).
-These tarballs are signed with the PGP key available on my website at
-<https://isaacfreund.com/public_key.txt>.
-
-For the 0.1.3 release for example, the tarball and signature URLs are:
-```
-https://codeberg.org/river/river/releases/download/v0.1.3/river-0.1.3.tar.gz
-https://codeberg.org/river/river/releases/download/v0.1.3/river-0.1.3.tar.gz.sig
-```
-
-## Zig version
-
-Until Zig 1.0, Zig releases will often have breaking changes that prevent
-river from building. River tracks the latest minor version Zig release
-and is only compatible with that release and any patch releases. At the time
-of writing for example river is compatible with Zig 0.9.0 and 0.9.1 but
-not Zig 0.8.0 or 0.10.0.
-
-## Zig Package Manager
-
-River uses the built-in Zig package manager for its (few) Zig dependencies.
-By default, running `zig build` will fetch river's Zig dependencies from the
-internet and store them in the global zig cache before building river. Since
-accessing the internet is forbidden or at least frowned upon by most distro
-packaging infrastructure, there are ways to fetch the Zig dependencies in a
-separate step before building river:
-
-1. Fetch step with internet access:
-
-   For each package in the `build.zig.zon` manifest file run the following command
-   with the tarball URL in the `build.zig.zon`:
-
-   ```
-   zig fetch --global-cache-dir /tmp/foobar $URL
-   ```
-
-   This command will download and unpack the tarball, hash the contents of the
-   tarball, and store the contents in the `/tmp/foobar/p/$HASH` directory. This
-   hash should match the corresponding hash field in the `build.zig.zon`.
-
-2. Build step with no internet access:
-
-   The `--system` flag for `zig build` takes a path to an arbitrary directory in
-   which zig packages stored in subdirectories matching their hash can be found.
-
-   ```
-   zig build --system /tmp/foobar/p/ ...
-   ```
-
-   This flag will disable all internet access and error if a package is not found
-   in the provided directory.
-
-It is also possible for distros to distribute Zig package manager packages as
-distro packages, although there are still some rough edges as the support for
-this is not yet mature. See this patchset for Chimera Linux for an example of
-how this can work: https://github.com/chimera-linux/cports/pull/1395
-
-## Build options
-
-River is built using the Zig build system. To see all available build
-options run `zig build --help`.
-
-Important: By default Zig will build for the host system/cpu using the
-equivalent of `-march=native`. To produce a portable binary `-Dcpu=baseline`
-at a minimum must be passed.
-
-Here are a few other options that are particularly relevant to packagers:
-
-- `-Dcpu=baseline`: Build for the "baseline" CPU of the target architecture,
-or any other CPU/feature set (e.g. `-Dcpu=x86_64_v2`).
-
-  - Individual features can be added/removed with `+`/`-`
-  (e.g. `-Dcpu=x86_64+avx2-cmov`).
-
-  - For a list of CPUs see for example `zig targets | jq '.cpus.x86_64 |
-  keys'`.
-
-  - For a list of features see for example `zig targets | jq
-  '.cpusFeatures.x86_64'`.
-
-- `-Dtarget=x86_64-linux-gnu`: Target architecture, OS, and ABI triple. See
-the output of `zig targets` for an exhaustive list of targets and CPU features,
-use of `jq(1)` to inspect the output recommended.
-
-- `-Dpie`: Build a position independent executable.
-
-- `-Dstrip`: Build without debug info. This not the same as invoking `strip(1)`
-on the resulting binary as it prevents the compiler from emitting debug info
-in the first place. For greatest effect, both may be used.
-
-- `--sysroot /path/to/sysroot`: Set the sysroot for cross compilation.
-
-- `--libc my_libc.txt`: Set system libc paths for cross compilation. Run
-`zig libc` to see a documented template for what this file should contain.
-
-- Enable compiler optimizations:
-
-  - `-Doptimize=ReleaseSafe`: Optimize for execution speed,
-  keep all assertions and runtime safety checks active.
-
-  - `-Doptimize=ReleaseFast`: Optimize for execution speed,
-  disable all assertions and runtime safety checks.
-
-  - `-Doptimize=ReleaseSmall`: Optimize for binary size,
-  disable all assertions and runtime safety checks.
-
-Please use `-Doptimize=ReleaseSafe` when building river for general
-use. CPU execution speed is not the performance bottleneck for river, the
-GPU is. Additionally, the increased safety is more than worth the binary
-size trade-off in my opinion.
-
-## Build prefix and DESTDIR
-
-To control the build prefix and directory use `--prefix` and the `DESTDIR`
-environment variable. For example
-```bash
-DESTDIR="/foo/bar" zig build --prefix /usr install
-```
-will install river to `/foo/bar/usr/bin/river`.
-
-The Zig build system only has a single install step, there is no way to build
-artifacts for a given prefix and then install those artifacts to that prefix
-at some later time. However, much existing distribution packaging tooling
-expect separate build and install steps. To fit the Zig build system into this
-tooling, I recommend the following pattern:
-
-```bash
-build() {
-    DESTDIR="/tmp/river-destdir" zig build --prefix /usr install
-}
-
-install() {
-    cp -r /tmp/river-destdir/* /desired/install/location
-}
-```
-
-## Examples
-
-Build for the host architecture and libc ABI:
-```bash
-DESTDIR=/foo/bar zig build -Doptimize=ReleaseSafe -Dcpu=baseline \
-    -Dstrip -Dpie --prefix /usr install
-```
-
-Cross compile for aarch64 musl libc based linux:
-```bash
-cat > xbps_zig_libc.txt <<-EOF
-    include_dir=${XBPS_CROSS_BASE}/usr/include
-    sys_include_dir=${XBPS_CROSS_BASE}/usr/include
-    crt_dir=${XBPS_CROSS_BASE}/usr/lib
-    msvc_lib_dir=
-    kernel32_lib_dir=
-    gcc_dir=
-EOF
-
-DESTDIR="/foo/bar" zig build \
-    --sysroot "${XBPS_CROSS_BASE}" \
-    --libc xbps_zig_libc.txt \
-    -Dtarget=aarch64-linux-musl -Dcpu=baseline \
-    -Doptimize=ReleaseSafe -Dstrip -Dpie \
-    --prefix /usr install
-```
-
-## Questions?
-
-If you have any questions feel free to reach out to me at
-`[email protected]` or in `#zig` or `#river` on `irc.libera.chat`, my
-nick is `ifreund`.
diff --git a/README.md b/README.md
deleted file mode 100644
index 5e94c53..0000000
--- a/README.md
+++ /dev/null
@@ -1,129 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2020 The River Developers
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-
-<div align="center">
-  <img src="logo/logo_text_adaptive_color.svg" width="600em">
-</div>
-
-## Overview
-
-River is a non-monolithic Wayland compositor. Unlike other Wayland compositors,
-river does not combine the compositor and window manager into one program.
-Instead, users can choose any window manager implementing the
-[river-window-management-v1] protocol.
-
-Read my blog post, [Separating the Wayland Compositor and Window Manager](https://isaacfreund.com/blog/river-window-management/),
-for an in-depth explanation.
-
-There is a [list of compatible window managers](https://codeberg.org/river/wiki/src/branch/main/pages/wm-list.md)
-on our [wiki](https://codeberg.org/river/wiki).
-
-> *If you are looking for the old dynamic tiling version of river, see
-[river-classic](https://codeberg.org/river/river-classic).*
-
-## Links
-
-- [Protocol Docs](https://isaacfreund.com/docs/wayland/)
-- [tinyrwm](https://codeberg.org/river/tinyrwm) example window manager
-- [Wiki](https://codeberg.org/river/wiki)
-- IRC: [#river](https://web.libera.chat/?channels=#river) on irc.libera.chat ([logs](https://libera.catirclogs.org/river))
-- [Zulip](https://river-compositor.zulipchat.com) (new)
-- [Issue Tracker](https://codeberg.org/river/river/issues)
-- [Code of Conduct](CODE_OF_CONDUCT.md)
-
-## Features
-
-River defers all window management policy to a separate window manager
-implementing the [river-window-management-v1] protocol. This includes window
-position/size, pointer/keyboard bindings, focus management, window decorations,
-desktop shell graphics, and more.
-
-River itself provides frame perfect rendering, good performance, support for
-many Wayland protocol extensions, robust Xwayland support, the ability to
-hot-swap window managers, and more.
-
-The [river-window-management-v1] protocol and other river protocol extensions
-are stable.  We do not break window managers.
-
-## Motivation
-
-Why split the window manager to a separate process?
-
-- Significantly lower the barrier to entry for writing a Wayland window manager.
-- Allow implementing Wayland window managers in high-level garbage collected
-  languages without impacting compositor performance and latency.
-- Allow hot-swapping between window managers without restarting the compositor
-  and all Wayland programs.
-- Promote diversity and experimentation in window manager design.
-
-## Building
-
-Note: If you are packaging river for distribution, see [PACKAGING.md](PACKAGING.md).
-
-To compile river first ensure that you have the following dependencies
-installed. The "development" versions are required if applicable to your
-distribution.
-
-- [zig](https://ziglang.org/download/) 0.16
-- wayland
-- wayland-protocols
-- [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) 0.20
-- xkbcommon 1.12 or newer
-- libevdev
-- pixman
-- pkg-config
-- scdoc (optional, but required for man page generation)
-
-Then run, for example:
-```
-zig build -Doptimize=ReleaseSafe --prefix ~/.local install
-```
-To enable Xwayland support pass the `-Dxwayland` option as well.
-Run `zig build -h` to see a list of all options.
-
-## Usage
-
-River can either be run nested in an X11/Wayland session or directly
-from a tty using KMS/DRM. Simply run the `river` command.
-
-On startup river will run an executable file at `$XDG_CONFIG_HOME/river/init`
-if such an executable exists. If `$XDG_CONFIG_HOME` is not set,
-`~/.config/river/init` will be used instead.
-
-Usually this executable is a shell script which starts the user's window manager
-and any other long-running programs.
-
-For complete documentation see the `river(1)` man page.
-
-## Strict No LLM / No AI Policy
-
-Use of generative AI/LLMs is strictly forbidden for all contributions to river.
-
-This includes bug reports and comments on the issue tracker.
-
-## Donate
-
-Unfortunately, the current pace of river's development is not sustainable
-without more financial support. If my work on river adds value to your life
-please consider setting up a recurring donation through [liberapay]. You can
-also support me with a one-time or monthly donation on [github sponsors] or
-[ko-fi] though I prefer liberapay as it is run by a non-profit. Thank you for
-your support!
-
-## Licensing
-
-This project follows the [REUSE Specification](https://reuse.software/spec-3.3/),
-all files have SPDX copyright and license information.
-
-In overview:
-
-- River's source code is released under the GPL-3.0-only license.
-- River's Wayland protocols are released under the MIT license.
-- River's logo and documentation are released under the CC-BY-SA-4.0 license.
-
-[river-window-management-v1]: https://isaacfreund.com/docs/wayland/river-window-management-v1
-[liberapay]: https://liberapay.com/ifreund
-[github sponsors]: https://github.com/sponsors/ifreund
-[ko-fi]: https://ko-fi.com/ifreund
diff --git a/build.zig b/build.zig
deleted file mode 100644
index c5bbd8e..0000000
--- a/build.zig
+++ /dev/null
@@ -1,260 +0,0 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const std = @import("std");
-const assert = std.debug.assert;
-const Build = std.Build;
-const fs = std.fs;
-const mem = std.mem;
-
-const manifest = @import("build.zig.zon");
-const version = manifest.version;
-
-const Scanner = @import("wayland").Scanner;
-const Translator = @import("translate_c").Translator;
-
-pub fn build(b: *Build) !void {
-    const target = b.standardTargetOptions(.{});
-    const optimize = b.standardOptimizeOption(.{});
-
-    const strip = b.option(bool, "strip", "Omit debug information") orelse false;
-    const pie = b.option(bool, "pie", "Build a Position Independent Executable") orelse false;
-    const use_llvm = b.option(bool, "llvm", "Force use of Zig's LLVM backend and the lld linker");
-
-    const omit_frame_pointer = switch (optimize) {
-        .Debug, .ReleaseSafe => false,
-        .ReleaseFast, .ReleaseSmall => true,
-    };
-
-    const man_pages = b.option(
-        bool,
-        "man-pages",
-        "Set to true to build man pages. Requires scdoc. Defaults to true if scdoc is found.",
-    ) orelse scdoc_found: {
-        _ = b.findProgram(&.{"scdoc"}, &.{}) catch |err| switch (err) {
-            error.FileNotFound => break :scdoc_found false,
-        };
-        break :scdoc_found true;
-    };
-
-    const xwayland = b.option(
-        bool,
-        "xwayland",
-        "Set to true to enable xwayland support",
-    ) orelse false;
-
-    const full_version = blk: {
-        if (b.option([]const u8, "version-string", "Override `river -version` output.")) |version_override| {
-            break :blk version_override;
-        } else if (mem.endsWith(u8, version, "-dev")) {
-            var ret: u8 = undefined;
-
-            const git_describe_long = b.runAllowFail(
-                &.{ "git", "-C", b.build_root.path orelse ".", "describe", "--long" },
-                &ret,
-                .ignore,
-            ) catch break :blk version;
-
-            var it = mem.splitSequence(u8, mem.trim(u8, git_describe_long, &std.ascii.whitespace), "-");
-            _ = it.next().?; // previous tag
-            const commit_count = it.next().?;
-            const commit_hash = it.next().?;
-            assert(it.next() == null);
-            assert(commit_hash[0] == 'g');
-
-            // Follow semantic versioning, e.g. 0.2.0-dev.42+d1cf95b
-            break :blk b.fmt(version ++ ".{s}+{s}", .{ commit_count, commit_hash[1..] });
-        } else {
-            break :blk version;
-        }
-    };
-
-    const options = b.addOptions();
-    options.addOption(bool, "xwayland", xwayland);
-    options.addOption([]const u8, "version", full_version);
-
-    const scanner = Scanner.create(b, .{});
-
-    scanner.addSystemProtocol("stable/tablet/tablet-v2.xml");
-    scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
-    scanner.addSystemProtocol("staging/color-management/color-management-v1.xml");
-    scanner.addSystemProtocol("staging/color-representation/color-representation-v1.xml");
-    scanner.addSystemProtocol("staging/cursor-shape/cursor-shape-v1.xml");
-    scanner.addSystemProtocol("staging/ext-session-lock/ext-session-lock-v1.xml");
-    scanner.addSystemProtocol("staging/tearing-control/tearing-control-v1.xml");
-    scanner.addSystemProtocol("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml");
-    scanner.addSystemProtocol("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml");
-    scanner.addSystemProtocol("unstable/xdg-decoration/xdg-decoration-unstable-v1.xml");
-    scanner.addSystemProtocol("unstable/xdg-foreign/xdg-foreign-unstable-v2.xml");
-
-    scanner.addCustomProtocol(b.path("protocol/river-window-management-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/river-xkb-bindings-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/river-layer-shell-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/river-input-management-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/river-libinput-config-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/river-xkb-config-v1.xml"));
-
-    scanner.addCustomProtocol(b.path("protocol/upstream/wlr-layer-shell-unstable-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/upstream/wlr-output-power-management-unstable-v1.xml"));
-    scanner.addCustomProtocol(b.path("protocol/upstream/virtual-keyboard-unstable-v1.xml"));
-
-    // Some of these versions may be out of date with what wlroots implements.
-    // This is not a problem in practice though as long as river successfully compiles.
-    // These versions control Zig code generation and have no effect on anything internal
-    // to wlroots. Therefore, the only thing that can happen due to a version being too
-    // old is that river fails to compile.
-    scanner.generate("wl_compositor", 4);
-    scanner.generate("wl_subcompositor", 1);
-    scanner.generate("wl_shm", 1);
-    scanner.generate("wl_output", 4);
-    scanner.generate("wl_seat", 7);
-    scanner.generate("wl_data_device_manager", 3);
-
-    scanner.generate("xdg_wm_base", 2);
-    scanner.generate("zwp_pointer_gestures_v1", 3);
-    scanner.generate("zwp_pointer_constraints_v1", 1);
-    scanner.generate("zwp_tablet_manager_v2", 1);
-    scanner.generate("zxdg_decoration_manager_v1", 1);
-    scanner.generate("zxdg_importer_v2", 1);
-    scanner.generate("zxdg_exporter_v2", 1);
-    scanner.generate("ext_session_lock_manager_v1", 1);
-    scanner.generate("wp_cursor_shape_manager_v1", 1);
-    scanner.generate("wp_tearing_control_manager_v1", 1);
-    scanner.generate("wp_color_manager_v1", 2);
-    scanner.generate("wp_color_representation_manager_v1", 1);
-
-    scanner.generate("river_window_manager_v1", 4);
-    scanner.generate("river_xkb_bindings_v1", 3);
-    scanner.generate("river_layer_shell_v1", 1);
-    scanner.generate("river_input_manager_v1", 2);
-    scanner.generate("river_libinput_config_v1", 2);
-    scanner.generate("river_xkb_config_v1", 2);
-
-    scanner.generate("zwlr_output_power_manager_v1", 1);
-    scanner.generate("zwlr_layer_shell_v1", 4);
-    scanner.generate("zwp_virtual_keyboard_manager_v1", 1);
-
-    const wayland = b.createModule(.{ .root_source_file = scanner.result });
-
-    const xkbcommon = b.dependency("xkbcommon", .{}).module("xkbcommon");
-    const pixman = b.dependency("pixman", .{}).module("pixman");
-
-    const wlroots = b.dependency("wlroots", .{}).module("wlroots");
-    wlroots.addImport("wayland", wayland);
-    wlroots.addImport("xkbcommon", xkbcommon);
-    wlroots.addImport("pixman", pixman);
-
-    // We need to ensure the wlroots include path obtained from pkg-config is
-    // exposed to the wlroots module for @cImport() to work. This seems to be
-    // the best way to do so with the current std.Build API.
-    wlroots.resolved_target = target;
-    const wlroots_pkgconf = "wlroots-0.20";
-    wlroots.linkSystemLibrary(wlroots_pkgconf, .{});
-
-    const flags = b.createModule(.{ .root_source_file = b.path("common/flags.zig") });
-    const slotmap = b.createModule(.{ .root_source_file = b.path("common/slotmap.zig") });
-
-    const translate_c: Translator = .init(b.dependency("translate_c", .{}), .{
-        .name = "c",
-        .c_source_file = b.path("river/c.h"),
-        .target = target,
-        .optimize = optimize,
-    });
-    translate_c.linkSystemLibrary("libevdev", .{});
-    translate_c.linkSystemLibrary("libinput", .{});
-
-    {
-        const river = b.addExecutable(.{
-            .name = "river",
-            .root_module = b.createModule(.{
-                .root_source_file = b.path("river/main.zig"),
-                .target = target,
-                .optimize = optimize,
-                .strip = strip,
-                .link_libc = true,
-            }),
-            .use_llvm = use_llvm,
-            .use_lld = use_llvm,
-        });
-        river.root_module.addOptions("build_options", options);
-
-        river.root_module.linkSystemLibrary("libevdev", .{});
-        river.root_module.linkSystemLibrary("libinput", .{});
-        river.root_module.linkSystemLibrary("wayland-server", .{});
-        river.root_module.linkSystemLibrary(wlroots_pkgconf, .{});
-        river.root_module.linkSystemLibrary("xkbcommon", .{});
-        river.root_module.linkSystemLibrary("pixman-1", .{});
-
-        river.root_module.addImport("wayland", wayland);
-        river.root_module.addImport("xkbcommon", xkbcommon);
-        river.root_module.addImport("pixman", pixman);
-        river.root_module.addImport("wlroots", wlroots);
-        river.root_module.addImport("flags", flags);
-        river.root_module.addImport("slotmap", slotmap);
-        river.root_module.addImport("c", translate_c.mod);
-
-        river.root_module.addCSourceFile(.{
-            .file = b.path("river/wlroots_log_wrapper.c"),
-            .flags = &.{ "-std=c99", "-O2" },
-        });
-
-        river.pie = pie;
-        river.root_module.omit_frame_pointer = omit_frame_pointer;
-
-        b.installArtifact(river);
-    }
-
-    {
-        const wf = Build.Step.WriteFile.create(b);
-        const pc_file = wf.add("river-protocols.pc", b.fmt(
-            \\prefix={s}
-            \\datarootdir=${{prefix}}/share
-            \\pkgdatadir=${{pc_sysrootdir}}${{datarootdir}}/river-protocols
-            \\
-            \\Name: river-protocols
-            \\URL: https://isaacfreund.com/software/river
-            \\Description: Protocol files for river, a non-monolithic Wayland compositor
-            \\Version: {s}
-        , .{ b.install_prefix, full_version }));
-        b.getInstallStep().dependOn(&b.addInstallFile(pc_file, "share/pkgconfig/river-protocols.pc").step);
-        inline for (&.{
-            "river-window-management-v1.xml",
-            "river-xkb-bindings-v1.xml",
-            "river-layer-shell-v1.xml",
-            "river-input-management-v1.xml",
-            "river-libinput-config-v1.xml",
-            "river-xkb-config-v1.xml",
-        }) |protocol| {
-            b.installFile("protocol/" ++ protocol, "share/river-protocols/stable/" ++ protocol);
-        }
-    }
-
-    if (man_pages) {
-        inline for (.{"river"}) |page| {
-            // Workaround for https://github.com/ziglang/zig/issues/16369
-            // Even passing a buffer to std.Build.Step.Run appears to be racy and occasionally deadlocks.
-            const scdoc = b.addSystemCommand(&.{ "/bin/sh", "-c", "scdoc < doc/" ++ page ++ ".1.scd" });
-            // This makes the caching work for the Workaround, and the extra argument is ignored by /bin/sh.
-            scdoc.addFileArg(b.path("doc/" ++ page ++ ".1.scd"));
-
-            const stdout = scdoc.captureStdOut(.{});
-            b.getInstallStep().dependOn(&b.addInstallFile(stdout, "share/man/man1/" ++ page ++ ".1").step);
-        }
-    }
-
-    {
-        const slotmap_test = b.addTest(.{
-            .root_module = b.createModule(.{
-                .root_source_file = b.path("common/slotmap.zig"),
-                .target = target,
-                .optimize = optimize,
-            }),
-            .use_llvm = use_llvm,
-            .use_lld = use_llvm,
-        });
-        const run_slotmap_test = b.addRunArtifact(slotmap_test);
-
-        const test_step = b.step("test", "Run the tests");
-        test_step.dependOn(&run_slotmap_test.step);
-    }
-}
diff --git a/build.zig.zon b/build.zig.zon
deleted file mode 100644
index 55a74c6..0000000
--- a/build.zig.zon
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-FileCopyrightText: © 2024 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-.{
-    .name = .river,
-    // While a river release is in development, this string should contain
-    // the version in development with the "-dev" suffix.
-    // When a release is tagged, the "-dev" suffix should be removed for the
-    // commit that gets tagged.
-    // Directly after the tagged commit, the version should be bumped and the
-    // "-dev" suffix added.
-    .version = "0.5.0-dev",
-    .paths = .{""},
-    .dependencies = .{
-        .pixman = .{
-            .url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.3.0.tar.gz",
-            .hash = "pixman-0.3.0-LClMnz2VAAAs7QSCGwLimV5VUYx0JFnX5xWU6HwtMuDX",
-        },
-        .wayland = .{
-            .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
-            .hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
-        },
-        .wlroots = .{
-            .url = "https://codeberg.org/ifreund/zig-wlroots/archive/v0.20.1.tar.gz",
-            .hash = "wlroots-0.20.1-jmOlcqNVBAB3uB5oqBTzpRlwu-FmMyyZMVAWCe5kmcSt",
-        },
-        .xkbcommon = .{
-            .url = "https://codeberg.org/ifreund/zig-xkbcommon/archive/v0.4.0.tar.gz",
-            .hash = "xkbcommon-0.4.0-VDqIe0i2AgDRsok2GpMFYJ8SVhQS10_PI2M_CnHXsJJZ",
-        },
-        .translate_c = .{
-            .url = "git+https://codeberg.org/ziglang/translate-c/#57c559cf581b1fcad90494eda219f98abeb155ce",
-            .hash = "translate_c-0.0.0-Q_BUWlX1BgCD1wo6uo97prlp9VJ4gxAjwN_vZ7nsSjGN",
-        },
-    },
-    .fingerprint = 0xf5e3672b8e8d6efc,
-}
diff --git a/common/flags.zig b/common/flags.zig
deleted file mode 100644
index 72f197f..0000000
--- a/common/flags.zig
+++ /dev/null
@@ -1,73 +0,0 @@
-// SPDX-FileCopyrightText: © 2023 Isaac Freund
-// SPDX-License-Identifier: 0BSD
-
-const std = @import("std");
-const mem = std.mem;
-
-pub const Flag = struct {
-    name: []const u8,
-    kind: enum { boolean, arg },
-};
-
-pub fn parser(comptime flags: []const Flag) type {
-    return struct {
-        pub const Result = struct {
-            /// Remaining args after the recognized flags
-            args: []const [:0]const u8,
-            /// Data obtained from parsed flags
-            flags: Flags,
-
-            pub const Flags = flags_type: {
-                const Attributes = std.builtin.Type.StructField.Attributes;
-                var names: [flags.len][]const u8 = undefined;
-                var types: [flags.len]type = undefined;
-                var attrs: [flags.len]Attributes = undefined;
-                for (flags, &names, &types, &attrs) |flag, *name, *ty, *attr| {
-                    name.* = flag.name;
-                    switch (flag.kind) {
-                        .boolean => {
-                            ty.* = bool;
-                            attr.* = .{ .default_value_ptr = &false };
-                        },
-                        .arg => {
-                            ty.* = ?[:0]const u8;
-                            attr.* = .{ .default_value_ptr = &@as(ty.*, null) };
-                        },
-                    }
-                }
-                break :flags_type @Struct(.auto, null, &names, &types, &attrs);
-            };
-        };
-
-        pub fn parse(args: []const [:0]const u8) error{MissingFlagArgument}!Result {
-            var result_flags: Result.Flags = .{};
-
-            var i: usize = 0;
-            outer: while (i < args.len) : (i += 1) {
-                inline for (flags) |flag| {
-                    if (mem.eql(u8, "-" ++ flag.name, args[i])) {
-                        switch (flag.kind) {
-                            .boolean => @field(result_flags, flag.name) = true,
-                            .arg => {
-                                i += 1;
-                                if (i == args.len) {
-                                    std.log.err("option '-" ++ flag.name ++
-                                        "' requires an argument but none was provided!", .{});
-                                    return error.MissingFlagArgument;
-                                }
-                                @field(result_flags, flag.name) = args[i];
-                            },
-                        }
-                        continue :outer;
-                    }
-                }
-                break;
-            }
-
-            return Result{
-                .args = args[i..],
-                .flags = result_flags,
-            };
-        }
-    };
-}
diff --git a/common/slotmap.zig b/common/slotmap.zig
deleted file mode 100644
index 9494101..0000000
--- a/common/slotmap.zig
+++ /dev/null
@@ -1,271 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 Isaac Freund
-// SPDX-License-Identifier: 0BSD
-
-const std = @import("std");
-const assert = std.debug.assert;
-const mem = std.mem;
-
-pub fn SlotMap(comptime T: type) type {
-    return struct {
-        const Map = @This();
-
-        /// This is packed just to make == work.
-        pub const Key = packed struct {
-            generation: u32,
-            index: u32,
-        };
-
-        const Slot = struct {
-            generation: u32,
-            data: union(enum) {
-                value: T,
-                // Index of the next free slot or slots.items.len + 1 if
-                // this is the last free slot.
-                next_free: u32,
-            },
-        };
-
-        slots: std.ArrayListUnmanaged(Slot),
-        /// Number of values stored in the map.
-        count: u32,
-        /// Index of the first free slot or slots.items.len + 1 if there
-        /// is no free slot.
-        first_free: u32,
-
-        pub const empty: Map = .{
-            .slots = .empty,
-            .count = 0,
-            .first_free = 1,
-        };
-
-        pub fn deinit(map: *Map, gpa: mem.Allocator) void {
-            map.slots.deinit(gpa);
-        }
-
-        pub fn put(map: *Map, gpa: mem.Allocator, value: T) error{OutOfMemory}!Key {
-            if (map.first_free < map.slots.items.len) {
-                const index = map.first_free;
-                const slot = &map.slots.items[index];
-                map.first_free = slot.data.next_free;
-                slot.data = .{ .value = value };
-                map.count += 1;
-                return .{
-                    .generation = slot.generation,
-                    .index = index,
-                };
-            }
-            try map.slots.append(gpa, .{
-                .generation = 0,
-                .data = .{ .value = value },
-            });
-            map.count += 1;
-            map.first_free += 1;
-            return .{
-                .generation = 0,
-                .index = @intCast(map.slots.items.len - 1),
-            };
-        }
-
-        pub fn get(map: *Map, key: Key) ?T {
-            if (map.getSlot(key)) |slot| {
-                return slot.data.value;
-            }
-            return null;
-        }
-
-        pub fn remove(map: *Map, key: Key) void {
-            if (map.getSlot(key)) |slot| {
-                assert(slot.data == .value);
-                slot.* = .{
-                    .generation = slot.generation +% 1,
-                    .data = .{ .next_free = map.first_free },
-                };
-                map.count -= 1;
-                map.first_free = key.index;
-            }
-        }
-
-        fn getSlot(map: *Map, key: Key) ?*Slot {
-            if (key.index < map.slots.items.len) {
-                if (key.generation == map.slots.items[key.index].generation) {
-                    return &map.slots.items[key.index];
-                }
-            }
-            return null;
-        }
-
-        pub const Iterator = struct {
-            map: *Map,
-            index: u32,
-
-            pub fn next(it: *Iterator) ?T {
-                while (it.index < it.map.slots.items.len) {
-                    defer it.index += 1;
-                    switch (it.map.slots.items[it.index].data) {
-                        .value => |value| return value,
-                        .next_free => {},
-                    }
-                }
-                return null;
-            }
-        };
-
-        /// Removing values from the map during iteration is safe.
-        /// Adding values to the map during iteration is safe but there is
-        /// no guarantee whether or not values added during iteration will
-        /// be seen by the iterator.
-        pub fn iterator(map: *Map) Iterator {
-            return .{ .map = map, .index = 0 };
-        }
-    };
-}
-
-// TODO fuzz test?
-test "basic" {
-    const testing = std.testing;
-
-    var map: SlotMap(u32) = .empty;
-    defer map.deinit(testing.allocator);
-
-    const five = try map.put(testing.allocator, 5);
-    try testing.expectEqual(5, map.get(five));
-    try testing.expectEqual(5, map.get(five));
-
-    map.remove(five);
-    try testing.expectEqual(null, map.get(five));
-
-    map.remove(five);
-    try testing.expectEqual(null, map.get(five));
-
-    const six = try map.put(testing.allocator, 6);
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-
-    map.remove(five);
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-
-    const seven = try map.put(testing.allocator, 7);
-    const eight = try map.put(testing.allocator, 8);
-    const nine = try map.put(testing.allocator, 9);
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-
-    map.remove(five);
-    map.remove(eight);
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(null, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(null, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-}
-
-test "iteration" {
-    const testing = std.testing;
-
-    var map: SlotMap(u64) = .empty;
-    defer map.deinit(testing.allocator);
-
-    {
-        var it = map.iterator();
-        try testing.expectEqual(null, it.next());
-    }
-
-    const five = try map.put(testing.allocator, 5);
-    const six = try map.put(testing.allocator, 6);
-    const seven = try map.put(testing.allocator, 7);
-    const eight = try map.put(testing.allocator, 8);
-    const nine = try map.put(testing.allocator, 9);
-
-    try testing.expectEqual(5, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-    try expectIterate(&.{ 5, 6, 7, 8, 9 }, &map);
-
-    map.remove(seven);
-    try testing.expectEqual(5, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(null, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-    try expectIterate(&.{ 5, 6, 8, 9 }, &map);
-
-    const ten = try map.put(testing.allocator, 10);
-    try testing.expectEqual(5, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(null, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-    try testing.expectEqual(10, map.get(ten));
-    try expectIterate(&.{ 5, 6, 10, 8, 9 }, &map);
-
-    map.remove(five);
-    map.remove(nine);
-    map.remove(six);
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(null, map.get(six));
-    try testing.expectEqual(null, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(null, map.get(nine));
-    try testing.expectEqual(10, map.get(ten));
-    try expectIterate(&.{ 10, 8 }, &map);
-}
-
-fn expectIterate(expected: []const u64, map: *SlotMap(u64)) !void {
-    var it = map.iterator();
-    var i: u32 = 0;
-    while (it.next()) |value| : (i += 1) {
-        try std.testing.expect(i < expected.len);
-        try std.testing.expectEqual(expected[i], value);
-    }
-    try std.testing.expectEqual(i, map.count);
-    try std.testing.expectEqual(i, expected.len);
-}
-
-test "remove during iteration" {
-    const testing = std.testing;
-
-    var map: SlotMap(u64) = .empty;
-    defer map.deinit(testing.allocator);
-
-    const five = try map.put(testing.allocator, 5);
-    const six = try map.put(testing.allocator, 6);
-    const seven = try map.put(testing.allocator, 7);
-    const eight = try map.put(testing.allocator, 8);
-    const nine = try map.put(testing.allocator, 9);
-
-    try testing.expectEqual(5, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-    try expectIterate(&.{ 5, 6, 7, 8, 9 }, &map);
-
-    var it = map.iterator();
-    map.remove(five);
-
-    try testing.expectEqual(6, it.next());
-    try testing.expectEqual(null, map.get(five));
-    try testing.expectEqual(6, map.get(six));
-    try testing.expectEqual(7, map.get(seven));
-    try testing.expectEqual(8, map.get(eight));
-    try testing.expectEqual(9, map.get(nine));
-
-    try testing.expectEqual(7, it.next());
-    map.remove(seven);
-    map.remove(nine);
-    map.remove(eight);
-    try testing.expectEqual(null, it.next());
-}
diff --git a/contrib/river.desktop b/contrib/river.desktop
deleted file mode 100644
index c8fab43..0000000
--- a/contrib/river.desktop
+++ /dev/null
@@ -1,8 +0,0 @@
-# SPDX-FileCopyrightText: © 2022 The River Developers
-# SPDX-License-Identifier: 0BSD
-
-[Desktop Entry]
-Name=River
-Comment=A non-monolithic Wayland compositor
-Exec=river
-Type=Application
diff --git a/doc/river.1.scd b/doc/river.1.scd
deleted file mode 100644
index 61641c2..0000000
--- a/doc/river.1.scd
+++ /dev/null
@@ -1,73 +0,0 @@
-RIVER(1)
-
-; SPDX-FileCopyrightText: © 2020 The River Developers
-; SPDX-License-Identifier: CC-BY-SA-4.0
-
-# NAME
-
-river - non-monolithic Wayland compositor
-
-# SYNOPSIS
-
-*river* [_options_]
-
-# DESCRIPTION
-
-River is a non-monolithic Wayland compositor, it does not combine the compositor
-and window manager into one program. Instead, users can choose any window
-manager implementing the river-window-management-v1 protocol.
-
-This means that river is not useful without a compatible window manager. A list
-of compatible window managers can be found on the river wiki:
-https://codeberg.org/river/wiki/src/branch/main/pages/wm-list.md
-
-# OPTIONS
-
-*-h*
-	Print a help message and exit.
-
-*-version*
-	Print the version number and exit.
-
-*-c* _shell_command_
-	Override the default search paths for an init executable: instead
-	_shell_command_ will be run with _/bin/sh -c_. See the *CONFIGURATION*
-	section for more details.
-
-*-log-level* [*error*|*warning*|*info*|*debug*]
-	Set the log level of river. At the *error* log level, only errors
-	are logged.  At the *debug* log level, everything is logged including
-	verbose debug messages.
-
-*-no-xwayland*
-	Disable Xwayland at runtime even if river has been built with support.
-
-# KEYBINDINGS
-
-The following keybindings are always active:
-
-*Ctrl+Alt+F1* - *Ctrl+Alt+F12*
-	Switch to VT 1-12
-
-Window managers may add additional keybindings but cannot override these builtin
-keybindings.
-
-# CONFIGURATION
-
-On startup river will run an executable file at $XDG_CONFIG_HOME/river/init if
-such an executable exists. If $XDG_CONFIG_HOME is not set, ~/.config/river/init
-will be used instead.
-
-The executable init file will be run as a process group leader after river's
-Wayland server is initialized but before entering the main loop. On exit,
-river will send SIGTERM to this process group.
-
-Usually this executable is a shell script which starts the user's window manager
-and any other long-running programs. For example, *kanshi*(1) could be started
-to manage output configuration.
-
-# AUTHORS
-
-Maintained by Isaac Freund <[email protected]> who is assisted by open
-source contributors. For more information about river's development, see
-<https://isaacfreund.com/software/river>.
diff --git a/logo/logo.svg b/logo/logo.svg
deleted file mode 100644
index d931072..0000000
--- a/logo/logo.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2024 Isaac Freund
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120">
-  <path d="M59.29 10s11.268 5.436 17.676 13.69c6.408 8.253 5.704 23.923-3.403 31.018-9.107 7.095-14.078 5.716-18.811 12.521-4.734 6.805 3.992 12.184 10.064 15.97 6.072 3.785 14.698 10.964 18.593 16.425 2.676 3.753 4.284 8 5.07 10.376H110V10ZM10 17.505V110h33.6c4.678-5.879 6.736-12.343 2.832-17.87-4.197-5.94-6.741-8.77-10.06-15.633-2.655-5.492-.685-12.778 2.645-16.748 3.329-3.97 5.674-5.539 8.187-6.96 7.177-4.06 17.05-7.47 16.272-15.1-.778-7.629-5.004-10.508-15.275-14.574C37.93 19.048 10 17.505 10 17.505Z"/>
-</svg>
diff --git a/logo/logo_text.svg b/logo/logo_text.svg
deleted file mode 100644
index 96dabcd..0000000
--- a/logo/logo_text.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2024 Isaac Freund
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 120">
-  <path d="M59.29 10s11.268 5.436 17.676 13.69c6.408 8.253 5.704 23.923-3.403 31.018-9.107 7.095-14.078 5.716-18.811 12.521-4.734 6.805 3.992 12.184 10.064 15.97 6.072 3.785 14.698 10.964 18.593 16.425 2.676 3.753 4.284 8 5.07 10.376H110V10ZM10 17.505V110h33.6c4.678-5.879 6.736-12.343 2.832-17.87-4.197-5.94-6.741-8.77-10.06-15.633-2.655-5.492-.685-12.778 2.645-16.748 3.329-3.97 5.674-5.539 8.187-6.96 7.177-4.06 17.05-7.47 16.272-15.1-.778-7.629-5.004-10.508-15.275-14.574C37.93 19.048 10 17.505 10 17.505ZM146.012 70.478v20.566q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296t-3.887-1.296Q135 93.474 135 91.044V52.988q0-2.753 1.62-4.049 1.78-1.457 3.886-1.457 2.105 0 3.725 1.457 1.781 1.296 1.781 4.049v.648q2.267-3.077 5.83-5.02 3.725-1.944 7.773-1.944 1.944 0 3.887.486t2.915 1.943q.972 1.458.972 3.239 0 2.105-1.458 3.887-1.295 1.62-3.887 1.62-1.133 0-2.429-.325-1.133-.324-2.915-.324-4.696 0-7.773 3.563-2.915 3.563-2.915 9.717zm34.332 25.587q-2.105 0-3.886-1.296-1.62-1.295-1.62-3.725V52.178q0-2.753 1.62-4.048 1.78-1.458 3.886-1.458 2.105 0 3.725 1.458 1.781 1.295 1.781 4.048v38.866q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296zm.162-58.3q-3.077 0-5.344-1.943-2.105-2.105-2.105-5.02 0-2.753 2.105-4.697Q177.429 24 180.506 24q2.915 0 5.02 2.105 2.106 1.944 2.106 4.697 0 2.915-2.106 5.02-2.105 1.943-5.02 1.943zm37.57 58.3q-2.428 0-4.534-1.134-2.105-.972-3.076-3.077l-16.033-37.733q-.324-1.295-.324-1.943 0-2.591 1.782-4.048 1.781-1.458 3.886-1.458 1.458 0 2.753.81 1.458.81 2.106 2.59l11.336 29.313q.81 1.619 2.105 1.619 1.62 0 2.105-1.62l11.174-29.311q.648-1.782 1.943-2.591 1.458-.81 2.915-.81 2.268 0 3.887 1.458 1.781 1.457 1.781 3.886 0 .81-.323 2.105l-15.87 37.733q-.973 2.105-3.078 3.077-2.105 1.134-4.534 1.134zm52.794 0q-12.145 0-18.947-6.964-6.802-6.963-6.802-18.785 0-10.365 6.802-17.004 6.964-6.64 17.814-6.64 10.526 0 16.842 6.154 6.316 5.992 6.316 14.899 0 3.562-1.944 5.344-1.781 1.781-4.696 1.781h-28.178q-1.62 0-1.134 1.781 1.296 4.535 5.182 7.126 4.049 2.43 9.07 2.43 3.724 0 7.125-.973 3.562-.971 6.154-2.915.81-.647 2.105-.647 1.781 0 3.239 1.62 1.457 1.457 1.457 3.562 0 .972-.324 1.943-.324.972-1.457 1.944-4.21 3.076-9.07 4.21-4.695 1.134-9.554 1.134zm9.393-29.636q.972 0 .972-.972 0-3.724-3.401-6.477-3.239-2.753-8.097-2.753-4.21 0-8.097 2.267-3.725 2.267-4.535 5.992v.162q-.162.647-.162 1.133.162.486 1.458.486zm33.36 4.049v20.566q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296t-3.887-1.296q-1.619-1.295-1.619-3.725V52.988q0-2.753 1.62-4.049 1.78-1.457 3.886-1.457 2.105 0 3.725 1.457 1.781 1.296 1.781 4.049v.648q2.267-3.077 5.83-5.02 3.725-1.944 7.773-1.944 1.944 0 3.887.486t2.915 1.943Q335 50.56 335 52.34q0 2.105-1.458 3.887-1.295 1.62-3.887 1.62-1.133 0-2.429-.325-1.133-.324-2.915-.324-4.696 0-7.773 3.563-2.915 3.563-2.915 9.717z"/>
-</svg>
diff --git a/logo/logo_text_adaptive_color.svg b/logo/logo_text_adaptive_color.svg
deleted file mode 100644
index 54319b2..0000000
--- a/logo/logo_text_adaptive_color.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-<!--
-SPDX-FileCopyrightText: © 2024 Isaac Freund
-SPDX-License-Identifier: CC-BY-SA-4.0
--->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 120">
-  <style>
-    @media (prefers-color-scheme: dark) {
-      path { fill: #fff; }
-    }
-  </style>
-  <path d="M59.29 10s11.268 5.436 17.676 13.69c6.408 8.253 5.704 23.923-3.403 31.018-9.107 7.095-14.078 5.716-18.811 12.521-4.734 6.805 3.992 12.184 10.064 15.97 6.072 3.785 14.698 10.964 18.593 16.425 2.676 3.753 4.284 8 5.07 10.376H110V10ZM10 17.505V110h33.6c4.678-5.879 6.736-12.343 2.832-17.87-4.197-5.94-6.741-8.77-10.06-15.633-2.655-5.492-.685-12.778 2.645-16.748 3.329-3.97 5.674-5.539 8.187-6.96 7.177-4.06 17.05-7.47 16.272-15.1-.778-7.629-5.004-10.508-15.275-14.574C37.93 19.048 10 17.505 10 17.505ZM146.012 70.478v20.566q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296t-3.887-1.296Q135 93.474 135 91.044V52.988q0-2.753 1.62-4.049 1.78-1.457 3.886-1.457 2.105 0 3.725 1.457 1.781 1.296 1.781 4.049v.648q2.267-3.077 5.83-5.02 3.725-1.944 7.773-1.944 1.944 0 3.887.486t2.915 1.943q.972 1.458.972 3.239 0 2.105-1.458 3.887-1.295 1.62-3.887 1.62-1.133 0-2.429-.325-1.133-.324-2.915-.324-4.696 0-7.773 3.563-2.915 3.563-2.915 9.717zm34.332 25.587q-2.105 0-3.886-1.296-1.62-1.295-1.62-3.725V52.178q0-2.753 1.62-4.048 1.78-1.458 3.886-1.458 2.105 0 3.725 1.458 1.781 1.295 1.781 4.048v38.866q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296zm.162-58.3q-3.077 0-5.344-1.943-2.105-2.105-2.105-5.02 0-2.753 2.105-4.697Q177.429 24 180.506 24q2.915 0 5.02 2.105 2.106 1.944 2.106 4.697 0 2.915-2.106 5.02-2.105 1.943-5.02 1.943zm37.57 58.3q-2.428 0-4.534-1.134-2.105-.972-3.076-3.077l-16.033-37.733q-.324-1.295-.324-1.943 0-2.591 1.782-4.048 1.781-1.458 3.886-1.458 1.458 0 2.753.81 1.458.81 2.106 2.59l11.336 29.313q.81 1.619 2.105 1.619 1.62 0 2.105-1.62l11.174-29.311q.648-1.782 1.943-2.591 1.458-.81 2.915-.81 2.268 0 3.887 1.458 1.781 1.457 1.781 3.886 0 .81-.323 2.105l-15.87 37.733q-.973 2.105-3.078 3.077-2.105 1.134-4.534 1.134zm52.794 0q-12.145 0-18.947-6.964-6.802-6.963-6.802-18.785 0-10.365 6.802-17.004 6.964-6.64 17.814-6.64 10.526 0 16.842 6.154 6.316 5.992 6.316 14.899 0 3.562-1.944 5.344-1.781 1.781-4.696 1.781h-28.178q-1.62 0-1.134 1.781 1.296 4.535 5.182 7.126 4.049 2.43 9.07 2.43 3.724 0 7.125-.973 3.562-.971 6.154-2.915.81-.647 2.105-.647 1.781 0 3.239 1.62 1.457 1.457 1.457 3.562 0 .972-.324 1.943-.324.972-1.457 1.944-4.21 3.076-9.07 4.21-4.695 1.134-9.554 1.134zm9.393-29.636q.972 0 .972-.972 0-3.724-3.401-6.477-3.239-2.753-8.097-2.753-4.21 0-8.097 2.267-3.725 2.267-4.535 5.992v.162q-.162.647-.162 1.133.162.486 1.458.486zm33.36 4.049v20.566q0 2.43-1.781 3.725-1.62 1.296-3.725 1.296t-3.887-1.296q-1.619-1.295-1.619-3.725V52.988q0-2.753 1.62-4.049 1.78-1.457 3.886-1.457 2.105 0 3.725 1.457 1.781 1.296 1.781 4.049v.648q2.267-3.077 5.83-5.02 3.725-1.944 7.773-1.944 1.944 0 3.887.486t2.915 1.943Q335 50.56 335 52.34q0 2.105-1.458 3.887-1.295 1.62-3.887 1.62-1.133 0-2.429-.325-1.133-.324-2.915-.324-4.696 0-7.773 3.563-2.915 3.563-2.915 9.717z"/>
-</svg>
diff --git a/protocol/river-input-management-v1.xml b/protocol/river-input-management-v1.xml
deleted file mode 100644
index e0f5498..0000000
--- a/protocol/river-input-management-v1.xml
+++ /dev/null
@@ -1,244 +0,0 @@
-<?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="2">
-    <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="2">
-    <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>
-
-    <event name="done" since="2">
-      <description summary="all information has been sent">
-        This event is sent after all information about the input device has
-        been sent.
-
-        This allows changes to one or more river_input_device_v1 properties to
-        be seen as atomic, even if they happen via multiple events.
-      </description>
-    </event>
-  </interface>
-</protocol>
diff --git a/protocol/river-layer-shell-v1.xml b/protocol/river-layer-shell-v1.xml
deleted file mode 100644
index 0167e9d..0000000
--- a/protocol/river-layer-shell-v1.xml
+++ /dev/null
@@ -1,191 +0,0 @@
-<?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-libinput-config-v1.xml b/protocol/river-libinput-config-v1.xml
deleted file mode 100644
index 46fad57..0000000
--- a/protocol/river-libinput-config-v1.xml
+++ /dev/null
@@ -1,901 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="river_libinput_config_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="configure libinput devices">
-    This protocol exposes libinput device configuration APIs. The libinput
-    documentation should be referred to for detailed information on libinput's
-    behavior.
-
-    Note that the compositor will not be able to expose libinput devices through
-    this protocol when it does not have access to the hardware, for example when
-    running nested in another Wayland compositor or X11 session.
-
-    This protocol is designed so that (hopefully) any backwards compatible
-    change to libinput's API can be matched with a backwards compatible change
-    to this protocol.
-
-    Note: the libinput API uses floating point types (float and double in C)
-    which are not (yet?) natively supported by the Wayland protocol. However,
-    the Wayland protocol does support sending arbitrary bytes through the array
-    argument type. This protocol uses e.g. type="array" summary="double" to
-    indicate a native-endian IEEE-754 64-bit double value.
-
-    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_libinput_config_v1" version="2">
-    <description summary="libinput config global interface">
-      Global interface for configuring libinput devices. This global should
-      only be advertised if river_input_manager_v1 is advertised as well.
-    </description>
-
-    <enum name="error">
-      <entry name="invalid_arg" value="0"
-        summary="invalid enum value or similar"/>
-      <entry name="invalid_destroy" value="1"/>
-    </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_libinput_config_v1.finished event before destroying this
-        object.
-      </description>
-    </request>
-
-    <event name="finished">
-      <description summary="the server has finished with the object">
-        This event indicates that the server will send no further events on this
-        object. The client should destroy the object. See
-        river_libinput_config_v1.destroy for more information.
-      </description>
-    </event>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the river_libinput_config_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_libinput_config_v1.stop request and wait for a
-        river_libinput_config_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="libinput_device">
-      <description summary="new libinput device">
-        A new libinput device has been created. Not every river_input_device_v1
-        is necessarily a libinput device as well.
-      </description>
-      <arg name="id" type="new_id" interface="river_libinput_device_v1"/>
-    </event>
-
-    <request name="create_accel_config">
-      <description summary="create a acceleration config">
-        Create a acceleration config which can be applied
-        with river_libinput_device_v1.apply_accel_config.
-      </description>
-      <arg name="id" type="new_id"
-        interface="river_libinput_accel_config_v1"/>
-      <arg name="profile" type="uint"
-        enum="river_libinput_device_v1.accel_profile"/>
-    </request>
-  </interface>
-
-  <interface name="river_libinput_device_v1" version="2">
-    <description summary="a libinput device">
-      In general, *_support events will be sent exactly once directly after the
-      river_libinput_device_v1 is created. *_default events will be sent after
-      *_support events if the config option is supported, and *_current events
-      willl be sent after the *_default events and again whenever the config
-      option is changed.
-    </description>
-
-    <enum name="error">
-      <entry name="invalid_arg" value="0"
-        summary="invalid enum value or similar"/>
-    </enum>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the libinput 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 libinput device is removed">
-        This event indicates that the libinput device has been removed.
-
-        The server will send no further events on this object and ignore any
-        request (other than river_libinput_device_v1.destroy) made after this
-        event is sent. The client should destroy this object with the
-        river_libinput_device_v1.destroy request to free up resources.
-      </description>
-    </event>
-
-    <event name="input_device">
-      <description summary="corresponding river input device">
-        The river_input_device_v1 corresponding to this libinput device.
-        This event will always be the first event sent on the
-        river_libinput_device_v1 object, and it will be sent exactly once.
-      </description>
-      <arg name="device" type="object" interface="river_input_device_v1"/>
-    </event>
-
-    <enum name="send_events_modes" bitfield="true">
-      <entry name="enabled" value="0"/>
-      <entry name="disabled" value="1"/>
-      <entry name="disabled_on_external_mouse" value="2"/>
-    </enum>
-
-    <event name="send_events_support">
-      <description summary="supported send events modes">
-        Supported send events modes.
-      </description>
-      <arg name="modes" type="uint" enum="send_events_modes"/>
-    </event>
-
-    <event name="send_events_default">
-      <description summary="default send events mode">
-        Default send events mode.
-      </description>
-      <arg name="mode" type="uint" enum="send_events_modes"/>
-    </event>
-
-    <event name="send_events_current">
-      <description summary="current send events mode">
-        Current send events mode.
-      </description>
-      <arg name="mode" type="uint" enum="send_events_modes"/>
-    </event>
-
-    <request name="set_send_events">
-      <description summary="set send events mode">
-        Set the send events mode for the device.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="mode" type="uint" enum="send_events_modes"/>
-    </request>
-
-    <enum name="tap_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="tap_support">
-      <description summary="tap-to-click/drag support">
-        The number of fingers supported for tap-to-click/drag.
-        If finger_count is 0, tap-to-click and drag are unsupported.
-      </description>
-      <arg name="finger_count" type="int"/>
-    </event>
-
-    <event name="tap_default">
-      <description summary="default tap-to-click state">
-        Default tap-to-click state.
-      </description>
-      <arg name="state" type="uint" enum="tap_state"/>
-    </event>
-
-    <event name="tap_current">
-      <description summary="current tap-to-click state">
-        Current tap-to-click state.
-      </description>
-      <arg name="state" type="uint" enum="tap_state"/>
-    </event>
-
-    <request name="set_tap">
-      <description summary="enable/disable tap-to-click">
-        Configure tap-to-click on this device, with a default mapping of
-        1, 2, 3 finger tap mapping to left, right, middle click, respectively.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="tap_state"/>
-    </request>
-
-    <enum name="tap_button_map">
-      <entry name="lrm" value="0"
-        summary="1/2/3 finger tap maps to left/right/middle"/>
-      <entry name="lmr" value="1"
-        summary="1/2/3 finger tap maps to left/middle/right"/>
-    </enum>
-
-    <event name="tap_button_map_default">
-      <description summary="default tap-to-click button map">
-        Default tap-to-click button map.
-      </description>
-      <arg name="button_map" type="uint" enum="tap_button_map"/>
-    </event>
-
-    <event name="tap_button_map_current">
-      <description summary="current tap-to-click button map">
-        Current tap-to-click button map.
-      </description>
-      <arg name="button_map" type="uint" enum="tap_button_map"/>
-    </event>
-
-    <request name="set_tap_button_map">
-      <description summary="set tap-to-click button map">
-        Set the finger number to button number mapping for tap-to-click. The
-        default mapping on most devices is to have a 1, 2 and 3 finger tap to
-        map to the left, right and middle button, respectively.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="button_map" type="uint" enum="tap_button_map"/>
-    </request>
-
-    <enum name="drag_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="drag_default">
-      <description summary="default tap-and-drag state">
-        Default tap-and-drag state.
-      </description>
-      <arg name="state" type="uint" enum="drag_state"/>
-    </event>
-
-    <event name="drag_current">
-      <description summary="current tap-and-drag state">
-        Current tap-and-drag state.
-      </description>
-      <arg name="state" type="uint" enum="drag_state"/>
-    </event>
-
-    <request name="set_drag">
-      <description summary="set tap-and-drag state">
-        Configure tap-and-drag functionality on the device.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="drag_state"/>
-    </request>
-
-    <enum name="drag_lock_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled_timeout" value="1"/>
-      <entry name="enabled_sticky" value="2"/>
-    </enum>
-
-    <event name="drag_lock_default">
-      <description summary="default drag lock state">
-        Default drag lock state.
-      </description>
-      <arg name="state" type="uint" enum="drag_lock_state"/>
-    </event>
-
-    <event name="drag_lock_current">
-      <description summary="current drag lock state">
-        Current drag lock state.
-      </description>
-      <arg name="state" type="uint" enum="drag_lock_state"/>
-    </event>
-
-    <request name="set_drag_lock">
-      <description summary="set drag lock state">
-        Configure drag-lock during tapping on this device. When enabled, a
-        finger may be lifted and put back on the touchpad and the drag process
-        continues. A timeout for lifting the finger is optional. When disabled,
-        lifting the finger during a tap-and-drag will immediately stop the drag.
-        See the libinput documentation for more details.
-       </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="drag_lock_state"/>
-    </request>
-
-    <event name="three_finger_drag_support">
-      <description summary="three finger drag support">
-        The number of fingers supported for three/four finger drag.
-        If finger_count is less than 3, three finger drag is unsupported.
-      </description>
-      <arg name="finger_count" type="int"/>
-    </event>
-
-    <enum name="three_finger_drag_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled_3fg" value="1"/>
-      <entry name="enabled_4fg" value="2"/>
-    </enum>
-
-    <event name="three_finger_drag_default">
-      <description summary="default three finger drag state">
-        Default three finger drag state.
-      </description>
-      <arg name="state" type="uint" enum="three_finger_drag_state"/>
-    </event>
-
-    <event name="three_finger_drag_current">
-      <description summary="current three finger drag state">
-        Current three finger drag state.
-      </description>
-      <arg name="state" type="uint" enum="three_finger_drag_state"/>
-    </event>
-
-    <request name="set_three_finger_drag">
-      <description summary="set three finger drag state">
-        Configure three finger drag functionality for the device.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="three_finger_drag_state"/>
-    </request>
-
-    <event name="calibration_matrix_support">
-      <description summary="support for a calibration matrix">
-        A calibration matrix is supported if the supported argument is non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <event name="calibration_matrix_default">
-      <description summary="default calibration matrix">
-        Default calibration matrix.
-      </description>
-      <arg name="matrix" type="array" summary="array of 6 floats"/>
-    </event>
-
-    <event name="calibration_matrix_current">
-      <description summary="current calibration matrix">
-        Current calibration matrix.
-      </description>
-      <arg name="matrix" type="array" summary="array of 6 floats"/>
-    </event>
-
-    <request name="set_calibration_matrix">
-      <description summary="set calibration matrix">
-        Set calibration matrix.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="matrix" type="array" summary="array of 6 floats"/>
-    </request>
-
-    <enum name="accel_profile">
-      <entry name="none" value="0"/>
-      <entry name="flat" value="1"/>
-      <entry name="adaptive" value="2"/>
-      <entry name="custom" value="4"/>
-    </enum>
-
-    <enum name="accel_profiles" bitfield="true">
-      <entry name="none" value="0"/>
-      <entry name="flat" value="1"/>
-      <entry name="adaptive" value="2"/>
-      <entry name="custom" value="4"/>
-    </enum>
-
-    <event name="accel_profiles_support">
-      <description summary="supported acceleration profiles">
-        Supported acceleration profiles.
-      </description>
-      <arg name="profiles" type="uint" enum="accel_profiles"/>
-    </event>
-
-    <event name="accel_profile_default">
-      <description summary="default acceleration profile">
-        Default acceleration profile.
-      </description>
-      <arg name="profile" type="uint" enum="accel_profile"/>
-    </event>
-
-    <event name="accel_profile_current">
-      <description summary="current acceleration profile">
-        Current acceleration profile.
-      </description>
-      <arg name="profile" type="uint" enum="accel_profile"/>
-    </event>
-
-    <request name="set_accel_profile">
-      <description summary="set acceleration profile">
-        Set the acceleration profile.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="profile" type="uint" enum="accel_profile"/>
-    </request>
-
-    <event name="accel_speed_default">
-      <description summary="default acceleration speed">
-        Default acceleration speed.
-      </description>
-      <arg name="speed" type="array" summary="double"/>
-    </event>
-
-    <event name="accel_speed_current">
-      <description summary="current acceleration speed">
-        Current acceleration speed.
-      </description>
-      <arg name="speed" type="array" summary="double"/>
-    </event>
-
-    <request name="set_accel_speed">
-      <description summary="set acceleration speed">
-        Set the acceleration speed within a range of [-1, 1], where 0 is
-        the default acceleration for this device, -1 is the slowest acceleration
-        and 1 is the maximum acceleration available on this device.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="speed" type="array" summary="double"/>
-    </request>
-
-    <request name="apply_accel_config">
-      <description summary="apply acceleration config">
-        Apply a pointer accleration config.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="config" type="object" interface="river_libinput_accel_config_v1"/>
-    </request>
-
-    <event name="natural_scroll_support">
-      <description summary="support for natural scroll">
-        Natural scroll is supported if the supported argument is non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <enum name="natural_scroll_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="natural_scroll_default">
-      <description summary="default natural scroll">
-        Default natural scroll.
-      </description>
-      <arg name="state" type="uint" enum="natural_scroll_state"/>
-    </event>
-
-    <event name="natural_scroll_current">
-      <description summary="current natural scroll state">
-        Current natural scroll.
-      </description>
-      <arg name="state" type="uint" enum="natural_scroll_state"/>
-    </event>
-
-    <request name="set_natural_scroll">
-      <description summary="set natural scroll state">
-        Set natural scroll state.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="natural_scroll_state"/>
-    </request>
-
-    <event name="left_handed_support">
-      <description summary="support for left-handed mode">
-        Left-handed mode is supported if the supported argument is non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <enum name="left_handed_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="left_handed_default">
-      <description summary="default left-handed mode">
-        Default left-handed mode.
-      </description>
-      <arg name="state" type="uint" enum="left_handed_state"/>
-    </event>
-
-    <event name="left_handed_current">
-      <description summary="current left-handed mode state">
-        Current left-handed mode.
-      </description>
-      <arg name="state" type="uint" enum="left_handed_state"/>
-    </event>
-
-    <request name="set_left_handed">
-      <description summary="set left-handed mode state">
-        Set left-handed mode state.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="left_handed_state"/>
-    </request>
-
-    <enum name="click_method">
-      <entry name="none" value="0"/>
-      <entry name="button_areas" value="1"/>
-      <entry name="clickfinger" value="2"/>
-    </enum>
-
-    <enum name="click_methods" bitfield="true">
-      <entry name="none" value="0"/>
-      <entry name="button_areas" value="1"/>
-      <entry name="clickfinger" value="2"/>
-    </enum>
-
-    <event name="click_method_support">
-      <description summary="supported click methods">
-        The click methods supported by the device.
-      </description>
-      <arg name="methods" type="uint" enum="click_methods"/>
-    </event>
-
-    <event name="click_method_default">
-      <description summary="default click method">
-        Default click method.
-      </description>
-      <arg name="method" type="uint" enum="click_method"/>
-    </event>
-
-    <event name="click_method_current">
-      <description summary="current click method">
-        Current click method.
-      </description>
-      <arg name="method" type="uint" enum="click_method"/>
-    </event>
-
-    <request name="set_click_method">
-      <description summary="set click method">
-        Set click method.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="method" type="uint" enum="click_method"/>
-    </request>
-
-    <enum name="clickfinger_button_map">
-      <entry name="lrm" value="0"/>
-      <entry name="lmr" value="1"/>
-    </enum>
-
-    <event name="clickfinger_button_map_default">
-      <description summary="default clickfinger button map">
-        Default clickfinger button map.
-        Supported if click_methods.clickfinger is supported.
-      </description>
-      <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
-    </event>
-
-    <event name="clickfinger_button_map_current">
-      <description summary="current clickfinger button map">
-        Current clickfinger button map.
-        Supported if click_methods.clickfinger is supported.
-      </description>
-      <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
-    </event>
-
-    <request name="set_clickfinger_button_map">
-      <description summary="set clickfinger button map">
-        Set clickfinger button map.
-        Supported if click_methods.clickfinger is supported.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="button_map" type="uint" enum="clickfinger_button_map"/>
-    </request>
-
-    <event name="middle_emulation_support">
-      <description summary="support for middle mouse button emulation">
-        Middle mouse button emulation is supported if the supported argument is
-        non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <enum name="middle_emulation_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="middle_emulation_default">
-      <description summary="default middle mouse button emulation">
-        Default middle mouse button emulation.
-      </description>
-      <arg name="state" type="uint" enum="middle_emulation_state"/>
-    </event>
-
-    <event name="middle_emulation_current">
-      <description summary="current middle mouse button emulation state">
-        Current middle mouse button emulation.
-      </description>
-      <arg name="state" type="uint" enum="middle_emulation_state"/>
-    </event>
-
-    <request name="set_middle_emulation">
-      <description summary="set middle mouse button emulation state">
-        Set middle mouse button emulation state.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="middle_emulation_state"/>
-    </request>
-
-    <enum name="scroll_method">
-      <entry name="no_scroll" value="0"/>
-      <entry name="two_finger" value="1"/>
-      <entry name="edge" value="2"/>
-      <entry name="on_button_down" value="4"/>
-    </enum>
-
-    <enum name="scroll_methods" bitfield="true">
-      <entry name="no_scroll" value="0"/>
-      <entry name="two_finger" value="1"/>
-      <entry name="edge" value="2"/>
-      <entry name="on_button_down" value="4"/>
-    </enum>
-
-    <event name="scroll_method_support">
-      <description summary="supported scroll methods">
-        The scroll methods supported by the device.
-      </description>
-      <arg name="methods" type="uint" enum="scroll_methods"/>
-    </event>
-
-    <event name="scroll_method_default">
-      <description summary="default scroll method">
-        Default scroll method.
-      </description>
-      <arg name="method" type="uint" enum="scroll_method"/>
-    </event>
-
-    <event name="scroll_method_current">
-      <description summary="current scroll method">
-        Current scroll method.
-      </description>
-      <arg name="method" type="uint" enum="scroll_method"/>
-    </event>
-
-    <request name="set_scroll_method">
-      <description summary="set scroll method">
-        Set scroll method.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="method" type="uint" enum="scroll_method"/>
-    </request>
-
-    <event name="scroll_button_default">
-      <description summary="default scroll button">
-        Default scroll button.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="button" type="uint"/>
-    </event>
-
-    <event name="scroll_button_current">
-      <description summary="current scroll button">
-        Current scroll button.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="button" type="uint"/>
-    </event>
-
-    <request name="set_scroll_button">
-      <description summary="set scroll button">
-        Set scroll button.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="button" type="uint"/>
-    </request>
-
-    <enum name="scroll_button_lock_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="scroll_button_lock_default">
-      <description summary="default scroll button lock state">
-        Default scroll button lock state.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="state" type="uint" enum="scroll_button_lock_state"/>
-    </event>
-
-    <event name="scroll_button_lock_current">
-      <description summary="current scroll button lock state">
-        Current scroll button lock state.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="state" type="uint" enum="scroll_button_lock_state"/>
-    </event>
-
-    <request name="set_scroll_button_lock">
-      <description summary="set scroll button lock state">
-        Set scroll button lock state.
-        Supported if scroll_methods.on_button_down is supported.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="scroll_button_lock_state"/>
-    </request>
-
-    <event name="dwt_support">
-      <description summary="support for disable-while-typing">
-        Disable-while-typing is supported if the supported argument is
-        non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <enum name="dwt_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="dwt_default">
-      <description summary="default disable-while-typing state">
-        Default disable-while-typing state.
-      </description>
-      <arg name="state" type="uint" enum="dwt_state"/>
-    </event>
-
-    <event name="dwt_current">
-      <description summary="current disable-while-typing state">
-        Current disable-while-typing state.
-      </description>
-      <arg name="state" type="uint" enum="dwt_state"/>
-    </event>
-
-    <request name="set_dwt">
-      <description summary="set disable-while-typing state">
-        Set disable-while-typing state.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="dwt_state"/>
-    </request>
-
-    <event name="dwtp_support">
-      <description summary="support for disable-while-trackpointing">
-        Disable-while-trackpointing is supported if the supported argument is
-        non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <enum name="dwtp_state">
-      <entry name="disabled" value="0"/>
-      <entry name="enabled" value="1"/>
-    </enum>
-
-    <event name="dwtp_default">
-      <description summary="default disable-while-trackpointing state">
-        Default disable-while-trackpointing state.
-      </description>
-      <arg name="state" type="uint" enum="dwtp_state"/>
-    </event>
-
-    <event name="dwtp_current">
-      <description summary="current disable-while-trackpointing state">
-        Current disable-while-trackpointing state.
-      </description>
-      <arg name="state" type="uint" enum="dwtp_state"/>
-    </event>
-
-    <request name="set_dwtp">
-      <description summary="set disable-while-trackpointing state">
-        Set disable-while-trackpointing state.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="state" type="uint" enum="dwtp_state"/>
-    </request>
-
-    <event name="rotation_support">
-      <description summary="support for rotation">
-        Rotation is supported if the supported argument is non-zero.
-      </description>
-      <arg name="supported" type="int" summary="boolean"/>
-    </event>
-
-    <event name="rotation_default">
-      <description summary="default rotation angle">
-        Default rotation angle.
-      </description>
-      <arg name="angle" type="uint"/>
-    </event>
-
-    <event name="rotation_current">
-      <description summary="current rotation angle">
-        Current rotation angle.
-      </description>
-      <arg name="angle" type="uint"/>
-    </event>
-
-    <request name="set_rotation">
-      <description summary="set rotation angle">
-        Set rotation angle in degrees clockwise off the logical neutral
-        position. Angle must be in the range [0-360).
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="angle" type="uint"/>
-    </request>
-
-    <event name="done" since="2">
-      <description summary="all information has been sent">
-        This event is sent after all information about the libinput device has
-        been sent.
-
-        This allows changes to one or more river_libinput_device_v1 properties
-        to be seen as atomic, even if they happen via multiple events.
-      </description>
-    </event>
-  </interface>
-
-  <interface name="river_libinput_accel_config_v1" version="1">
-    <description summary="acceleration config">
-      The result returned by libinput on setting configuration for a device.
-    </description>
-
-    <enum name="error">
-      <entry name="invalid_arg" value="0"
-        summary="invalid enum value or similar"/>
-    </enum>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the accel object">
-        This request indicates that the client will no longer use the accel
-        config object and that it may be safely destroyed.
-      </description>
-    </request>
-
-    <enum name="accel_type">
-      <entry name="fallback" value="0"/>
-      <entry name="motion" value="1"/>
-      <entry name="scroll" value="2"/>
-    </enum>
-
-    <request name="set_points">
-      <description summary="define custom acceleration function">
-        Defines the acceleration function for a given movement type
-        in an acceleration configuration with custom accel profile.
-      </description>
-      <arg name="result" type="new_id" interface="river_libinput_result_v1"/>
-      <arg name="type" type="uint" enum="accel_type"/>
-      <arg name="step" type="array" summary="double"/>
-      <arg name="points" type="array" summary="array of doubles"/>
-    </request>
-  </interface>
-
-  <interface name="river_libinput_result_v1" version="1">
-    <description summary="config application result">
-      The result returned by libinput on setting configuration for a device.
-    </description>
-
-    <event name="success" type="destructor">
-      <description summary="config success">
-        The configuration was successfully applied to the device.
-      </description>
-    </event>
-
-    <event name="unsupported" type="destructor">
-      <description summary="config unsupported">
-        The configuration is unsupported by the device and was ignored.
-      </description>
-    </event>
-
-    <event name="invalid" type="destructor">
-      <description summary="config invalid">
-        The configuration is invalid and was ignored.
-      </description>
-    </event>
-  </interface>
-</protocol>
diff --git a/protocol/river-window-management-v1.xml b/protocol/river-window-management-v1.xml
deleted file mode 100644
index f835971..0000000
--- a/protocol/river-window-management-v1.xml
+++ /dev/null
@@ -1,1859 +0,0 @@
-<?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
-      compositor and individual windows (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 compositor and individual
-      windows. 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_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.
-
-        The valid range for the RGBA values is from 0x00000000 to 0xffffffff.
-        These values are interpreted as a percentage:
-        - 0x00000000 means 0% of the given color component
-        - 0xffffffff means 100% of the given color component
-
-        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 above the window in z-order">
-        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 below the window in z-order">
-        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_maximized 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.removed 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.removed 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
deleted file mode 100644
index 55fb72f..0000000
--- a/protocol/river-xkb-bindings-v1.xml
+++ /dev/null
@@ -1,314 +0,0 @@
-<?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="3">
-    <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="3">
-    <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 (possibly 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="3">
-    <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>
-
-    <request name="modifiers_watch" since="3">
-      <description summary="watch for change in active modifiers">
-        Request that the server send the modifiers_update event whenever a state
-        change occurs for at least one of the modifiers specified by the
-        modifiers argument.
-
-        The window manager should make this request with the modifiers argument
-        set to 0 when it no longer wishes to take action based on a change in
-        modifiers.
-
-        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="modifiers" type="uint" enum="river_seat_v1.modifiers"/>
-    </request>
-
-    <event name="modifiers_update" since="3">
-      <description summary="active modifiers for the seat changed">
-        The set of currently active modifiers for the seat changed. This event
-        is only sent when there is a change in state for modifiers marked as
-        watched using the modifiers_watch request.
-
-        The old and new arguments convey the set of modifiers active before and
-        after the change. All modifiers are included in the old and new
-        arguments, including modifiers that are not watched.
-
-        Since this event is only sent when there is a change in state for
-        watched modifiers, it follows that at least one watched modifier is
-        active in old but inactive in new or vice-versa.
-
-        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>
-      <arg name="old" type="uint" enum="river_seat_v1.modifiers"
-        summary="previously active modifiers"/>
-      <arg name="new" type="uint" enum="river_seat_v1.modifiers"
-        summary="currently active modifiers"/>
-    </event>
-  </interface>
-</protocol>
diff --git a/protocol/river-xkb-config-v1.xml b/protocol/river-xkb-config-v1.xml
deleted file mode 100644
index f73ec67..0000000
--- a/protocol/river-xkb-config-v1.xml
+++ /dev/null
@@ -1,287 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="river_xkb_config_v1">
-  <copyright>
-    SPDX-FileCopyrightText: © 2026 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="configure xkbcommon keyboards">
-    This protocol allow a client to set the xkbcommon keymap of individual
-    keyboard input devices. It also allows switching between the layouts of a
-    keymap and toggling capslock/numlock state.
-
-    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_config_v1" version="2">
-    <description summary="xkb config global interface">
-      Global interface for configuring xkb devices.
-
-      This global should only be advertised if river_input_manager_v1 is
-      advertised as well.
-    </description>
-
-    <enum name="error">
-      <entry name="invalid_destroy" value="0"/>
-      <entry name="invalid_format" value="1"/>
-    </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_xkb_config_v1.finished event before destroying this object.
-      </description>
-    </request>
-
-    <event name="finished">
-      <description summary="the server has finished with the object">
-        This event indicates that the server will send no further events on this
-        object. The client should destroy the object. See
-        river_xkb_config_v1.destroy for more information.
-      </description>
-    </event>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the river_xkb_config_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_xkb_config_v1.stop request and wait for a
-        river_xkb_config_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>
-
-    <enum name="keymap_format">
-      <entry name="text_v1" value="1" summary="XKB_KEYMAP_FORMAT_TEXT_V1"/>
-      <entry name="text_v2" value="2" summary="XKB_KEYMAP_FORMAT_TEXT_V2"/>
-    </enum>
-
-    <request name="create_keymap">
-      <description summary="create a keymap object">
-        The server must be able to mmap the fd with MAP_PRIVATE.
-        The server will fstat the fd to obtain the size of the keymap.
-        The client must not modify the contents of the fd after making this request.
-        The client should seal the fd with fcntl.
-      </description>
-      <arg name="id" type="new_id" interface="river_xkb_keymap_v1"/>
-      <arg name="fd" type="fd"/>
-      <arg name="format" type="uint" enum="keymap_format"/>
-    </request>
-
-    <event name="xkb_keyboard">
-      <description summary="new xkb keyboard">
-        A new xkbcommon keyboard has been created. Not every
-        river_input_device_v1 is necessarily an xkbcommon keyboard as well.
-      </description>
-      <arg name="id" type="new_id" interface="river_xkb_keyboard_v1"/>
-    </event>
-  </interface>
-
-  <interface name="river_xkb_keymap_v1" version="2">
-    <description summary="xkbcommon keymap">
-      This object is the result of attempting to create an xkbcommon keymap.
-    </description>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the keymap object">
-        This request indicates that the client will no longer use the keymap
-        object and that it may be safely destroyed.
-      </description>
-    </request>
-
-    <event name="success">
-      <description summary="keymap creation succeeded">
-        The keymap object was successfully created and may be used with the
-        river_xkb_keyboard_v1.set_keymap request.
-      </description>
-    </event>
-
-    <event name="failure">
-      <description summary="keymap creation failed">
-        The compositor failed to create a keymap from the given parameters.
-
-        It is a protocol error to use this keymap object with
-        river_xkb_keyboard_v1.set_keymap.
-      </description>
-      <arg name="error_msg" type="string"/>
-    </event>
-  </interface>
-
-  <interface name="river_xkb_keyboard_v1" version="2">
-    <description summary="xkbcommon keyboard device">
-      This object represent a physical keyboard which has its configuration and
-      state managed by xkbcommon.
-    </description>
-
-    <enum name="error">
-      <entry name="invalid_keymap" value="0"/>
-    </enum>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the xkb keyboard object">
-        This request indicates that the client will no longer use the keyboard
-        object and that it may be safely destroyed.
-      </description>
-    </request>
-
-    <event name="removed">
-      <description summary="the xkb keyboard is removed">
-        This event indicates that the xkb keyboard has been removed.
-
-        The server will send no further events on this object and ignore any
-        request (other than river_xkb_keyboard_v1.destroy) made after this event
-        is sent. The client should destroy this object with the
-        river_xkb_keyboard_v1.destroy request to free up resources.
-      </description>
-    </event>
-
-    <event name="input_device">
-      <description summary="corresponding river input device">
-        The river_input_device_v1 corresponding to this xkb keyboard. This event
-        will always be the first event sent on the river_xkb_keyboard_v1 object,
-        and it will be sent exactly once.
-      </description>
-      <arg name="device" type="object" interface="river_input_device_v1"/>
-    </event>
-
-    <request name="set_keymap">
-      <description summary="set the keymap">
-        Set the keymap for the keyboard.
-
-        Setting a keymap will reset all layout/modifier state.
-
-        It is a protocol error to pass a keymap object for which the
-        river_xkb_keymap_v1.success event was not received.
-      </description>
-      <arg name="keymap" type="object" interface="river_xkb_keymap_v1"/>
-    </request>
-
-    <request name="set_layout_by_index">
-      <description summary="set the active layout by index">
-        Set the active layout for the keyboard's keymap. Has no effect if the
-        layout index is out of bounds for the current keymap.
-      </description>
-      <arg name="index" type="int"/>
-    </request>
-
-    <request name="set_layout_by_name">
-      <description summary="set the active layout by name">
-        Set the active layout for the keyboard's keymap. Has no effect if there
-        is no layout with the give name for the keyboard's keymap.
-      </description>
-      <arg name="name" type="string"/>
-    </request>
-
-    <event name="layout">
-      <description summary="currently active layout">
-        The currently active layout index and name. The name arg may be null if
-        the active layout does not have a name.
-
-        This event is sent once when the river_xkb_keyboard_v1 is created and
-        again whenever the layout changes.
-      </description>
-      <arg name="index" type="uint"/>
-      <arg name="name" type="string" allow-null="true"/>
-    </event>
-
-    <request name="capslock_enable">
-      <description summary="enable capslock">
-        Enable capslock for the keyboard.
-      </description>
-    </request>
-
-    <request name="capslock_disable">
-      <description summary="disable capslock">
-        Disable capslock for the keyboard.
-      </description>
-    </request>
-
-    <event name="capslock_enabled">
-      <description summary="capslock is currently enabled">
-        Capslock is currently enabled for the keyboard.
-
-        This event is sent once when the river_xkb_keyboard_v1 is created and
-        again whenever the capslock state changes.
-      </description>
-    </event>
-
-    <event name="capslock_disabled">
-      <description summary="capslock is currently disabled">
-        Capslock is currently disabled for the keyboard.
-
-        This event is sent once when the river_xkb_keyboard_v1 is created and
-        again whenever the capslock state changes.
-      </description>
-    </event>
-
-    <request name="numlock_enable">
-      <description summary="enable numlock">
-        Enable numlock for the keyboard.
-      </description>
-    </request>
-
-    <request name="numlock_disable">
-      <description summary="disable numlock">
-        Disable numlock for the keyboard.
-      </description>
-    </request>
-
-    <event name="numlock_enabled">
-      <description summary="numlock is currently enabled">
-        Numlock is currently enabled for the keyboard.
-
-        This event is sent once when the river_xkb_keyboard_v1 is created and
-        again whenever the numlock state changes.
-      </description>
-    </event>
-
-    <event name="numlock_disabled">
-      <description summary="numlock is currently disabled">
-        Numlock is currently disabled for the keyboard.
-
-        This event is sent once when the river_xkb_keyboard_v1 is created and
-        again whenever the numlock state changes.
-      </description>
-    </event>
-
-    <event name="done" since="2">
-      <description summary="all information has been sent">
-        This event is sent after all information about the keyboard has been
-        sent.
-
-        This allows changes to one or more river_xkb_keyboard_v1 properties to
-        be seen as atomic, even if they happen via multiple events.
-      </description>
-    </event>
-  </interface>
-</protocol>
diff --git a/protocol/upstream/virtual-keyboard-unstable-v1.xml b/protocol/upstream/virtual-keyboard-unstable-v1.xml
deleted file mode 100644
index 1ac03fe..0000000
--- a/protocol/upstream/virtual-keyboard-unstable-v1.xml
+++ /dev/null
@@ -1,114 +0,0 @@
-<!-- SPDX-License-Identifier: MIT -->
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="virtual_keyboard_unstable_v1">
-  <copyright>
-    Copyright © 2008-2011  Kristian Høgsberg
-    Copyright © 2010-2013  Intel Corporation
-    Copyright © 2012-2013  Collabora, Ltd.
-    Copyright © 2018       Purism SPC
-
-    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 (including the next
-    paragraph) 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>
-
-  <interface name="zwp_virtual_keyboard_v1" version="1">
-    <description summary="virtual keyboard">
-      The virtual keyboard provides an application with requests which emulate
-      the behaviour of a physical keyboard.
-
-      This interface can be used by clients on its own to provide raw input
-      events, or it can accompany the input method protocol.
-    </description>
-
-    <request name="keymap">
-      <description summary="keyboard mapping">
-        Provide a file descriptor to the compositor which can be
-        memory-mapped to provide a keyboard mapping description.
-
-        Format carries a value from the keymap_format enumeration.
-      </description>
-      <arg name="format" type="uint" summary="keymap format"/>
-      <arg name="fd" type="fd" summary="keymap file descriptor"/>
-      <arg name="size" type="uint" summary="keymap size, in bytes"/>
-    </request>
-
-    <enum name="error">
-      <entry name="no_keymap" value="0" summary="No keymap was set"/>
-    </enum>
-
-    <request name="key">
-      <description summary="key event">
-        A key was pressed or released.
-        The time argument is a timestamp with millisecond granularity, with an
-        undefined base. All requests regarding a single object must share the
-        same clock.
-
-        Keymap must be set before issuing this request.
-
-        State carries a value from the key_state enumeration.
-      </description>
-      <arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
-      <arg name="key" type="uint" summary="key that produced the event"/>
-      <arg name="state" type="uint" summary="physical state of the key"/>
-    </request>
-
-    <request name="modifiers">
-      <description summary="modifier and group state">
-        Notifies the compositor that the modifier and/or group state has
-        changed, and it should update state.
-
-        The client should use wl_keyboard.modifiers event to synchronize its
-        internal state with seat state.
-
-        Keymap must be set before issuing this request.
-      </description>
-      <arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
-      <arg name="mods_latched" type="uint" summary="latched modifiers"/>
-      <arg name="mods_locked" type="uint" summary="locked modifiers"/>
-      <arg name="group" type="uint" summary="keyboard layout"/>
-    </request>
-
-    <request name="destroy" type="destructor" since="1">
-      <description summary="destroy the virtual keyboard keyboard object"/>
-    </request>
-  </interface>
-
-  <interface name="zwp_virtual_keyboard_manager_v1" version="1">
-    <description summary="virtual keyboard manager">
-      A virtual keyboard manager allows an application to provide keyboard
-      input events as if they came from a physical keyboard.
-    </description>
-
-    <enum name="error">
-      <entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
-    </enum>
-
-    <request name="create_virtual_keyboard">
-      <description summary="Create a new virtual keyboard">
-        Creates a new virtual keyboard associated to a seat.
-
-        If the compositor enables a keyboard to perform arbitrary actions, it
-        should present an error when an untrusted client requests a new
-        keyboard.
-      </description>
-      <arg name="seat" type="object" interface="wl_seat"/>
-      <arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
-    </request>
-  </interface>
-</protocol>
diff --git a/protocol/upstream/wlr-layer-shell-unstable-v1.xml b/protocol/upstream/wlr-layer-shell-unstable-v1.xml
deleted file mode 100644
index 450970e..0000000
--- a/protocol/upstream/wlr-layer-shell-unstable-v1.xml
+++ /dev/null
@@ -1,408 +0,0 @@
-<!-- SPDX-License-Identifier: MIT -->
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="wlr_layer_shell_unstable_v1">
-  <copyright>
-    Copyright © 2017 Drew DeVault
-
-    Permission to use, copy, modify, distribute, and sell this
-    software and its documentation for any purpose is hereby granted
-    without fee, provided that the above copyright notice appear in
-    all copies and that both that copyright notice and this permission
-    notice appear in supporting documentation, and that the name of
-    the copyright holders not be used in advertising or publicity
-    pertaining to distribution of the software without specific,
-    written prior permission.  The copyright holders make no
-    representations about the suitability of this software for any
-    purpose.  It is provided "as is" without express or implied
-    warranty.
-
-    THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
-    SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
-    FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
-    SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
-    AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-    THIS SOFTWARE.
-  </copyright>
-
-  <interface name="zwlr_layer_shell_v1" version="5">
-    <description summary="create surfaces that are layers of the desktop">
-      Clients can use this interface to assign the surface_layer role to
-      wl_surfaces. Such surfaces are assigned to a "layer" of the output and
-      rendered with a defined z-depth respective to each other. They may also be
-      anchored to the edges and corners of a screen and specify input handling
-      semantics. This interface should be suitable for the implementation of
-      many desktop shell components, and a broad number of other applications
-      that interact with the desktop.
-    </description>
-
-    <request name="get_layer_surface">
-      <description summary="create a layer_surface from a surface">
-        Create a layer surface for an existing surface. This assigns the role of
-        layer_surface, or raises a protocol error if another role is already
-        assigned.
-
-        Creating a layer surface from a wl_surface which has a buffer attached
-        or committed is a client error, and any attempts by a client to attach
-        or manipulate a buffer prior to the first layer_surface.configure call
-        must also be treated as errors.
-
-        After creating a layer_surface object and setting it up, the client
-        must perform an initial commit without any buffer attached.
-        The compositor will reply with a layer_surface.configure event.
-        The client must acknowledge it and is then allowed to attach a buffer
-        to map the surface.
-
-        You may pass NULL for output to allow the compositor to decide which
-        output to use. Generally this will be the one that the user most
-        recently interacted with.
-
-        Clients can specify a namespace that defines the purpose of the layer
-        surface.
-      </description>
-      <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
-      <arg name="surface" type="object" interface="wl_surface"/>
-      <arg name="output" type="object" interface="wl_output" allow-null="true"/>
-      <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
-      <arg name="namespace" type="string" summary="namespace for the layer surface"/>
-    </request>
-
-    <enum name="error">
-      <entry name="role" value="0" summary="wl_surface has another role"/>
-      <entry name="invalid_layer" value="1" summary="layer value is invalid"/>
-      <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
-    </enum>
-
-    <enum name="layer">
-      <description summary="available layers for surfaces">
-        These values indicate which layers a surface can be rendered in. They
-        are ordered by z depth, bottom-most first. Traditional shell surfaces
-        will typically be rendered between the bottom and top layers.
-        Fullscreen shell surfaces are typically rendered at the top layer.
-        Multiple surfaces can share a single layer, and ordering within a
-        single layer is undefined.
-      </description>
-
-      <entry name="background" value="0"/>
-      <entry name="bottom" value="1"/>
-      <entry name="top" value="2"/>
-      <entry name="overlay" value="3"/>
-    </enum>
-
-    <!-- Version 3 additions -->
-
-    <request name="destroy" type="destructor" since="3">
-      <description summary="destroy the layer_shell object">
-        This request indicates that the client will not use the layer_shell
-        object any more. Objects that have been created through this instance
-        are not affected.
-      </description>
-    </request>
-  </interface>
-
-  <interface name="zwlr_layer_surface_v1" version="5">
-    <description summary="layer metadata interface">
-      An interface that may be implemented by a wl_surface, for surfaces that
-      are designed to be rendered as a layer of a stacked desktop-like
-      environment.
-
-      Layer surface state (layer, size, anchor, exclusive zone,
-      margin, interactivity) is double-buffered, and will be applied at the
-      time wl_surface.commit of the corresponding wl_surface is called.
-
-      Attaching a null buffer to a layer surface unmaps it.
-
-      Unmapping a layer_surface means that the surface cannot be shown by the
-      compositor until it is explicitly mapped again. The layer_surface
-      returns to the state it had right after layer_shell.get_layer_surface.
-      The client can re-map the surface by performing a commit without any
-      buffer attached, waiting for a configure event and handling it as usual.
-    </description>
-
-    <request name="set_size">
-      <description summary="sets the size of the surface">
-        Sets the size of the surface in surface-local coordinates. The
-        compositor will display the surface centered with respect to its
-        anchors.
-
-        If you pass 0 for either value, the compositor will assign it and
-        inform you of the assignment in the configure event. You must set your
-        anchor to opposite edges in the dimensions you omit; not doing so is a
-        protocol error. Both values are 0 by default.
-
-        Size is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="width" type="uint"/>
-      <arg name="height" type="uint"/>
-    </request>
-
-    <request name="set_anchor">
-      <description summary="configures the anchor point of the surface">
-        Requests that the compositor anchor the surface to the specified edges
-        and corners. If two orthogonal edges are specified (e.g. 'top' and
-        'left'), then the anchor point will be the intersection of the edges
-        (e.g. the top left corner of the output); otherwise the anchor point
-        will be centered on that edge, or in the center if none is specified.
-
-        Anchor is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="anchor" type="uint" enum="anchor"/>
-    </request>
-
-    <request name="set_exclusive_zone">
-      <description summary="configures the exclusive geometry of this surface">
-        Requests that the compositor avoids occluding an area with other
-        surfaces. The compositor's use of this information is
-        implementation-dependent - do not assume that this region will not
-        actually be occluded.
-
-        A positive value is only meaningful if the surface is anchored to one
-        edge or an edge and both perpendicular edges. If the surface is not
-        anchored, anchored to only two perpendicular edges (a corner), anchored
-        to only two parallel edges or anchored to all edges, a positive value
-        will be treated the same as zero.
-
-        A positive zone is the distance from the edge in surface-local
-        coordinates to consider exclusive.
-
-        Surfaces that do not wish to have an exclusive zone may instead specify
-        how they should interact with surfaces that do. If set to zero, the
-        surface indicates that it would like to be moved to avoid occluding
-        surfaces with a positive exclusive zone. If set to -1, the surface
-        indicates that it would not like to be moved to accommodate for other
-        surfaces, and the compositor should extend it all the way to the edges
-        it is anchored to.
-
-        For example, a panel might set its exclusive zone to 10, so that
-        maximized shell surfaces are not shown on top of it. A notification
-        might set its exclusive zone to 0, so that it is moved to avoid
-        occluding the panel, but shell surfaces are shown underneath it. A
-        wallpaper or lock screen might set their exclusive zone to -1, so that
-        they stretch below or over the panel.
-
-        The default value is 0.
-
-        Exclusive zone is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="zone" type="int"/>
-    </request>
-
-    <request name="set_margin">
-      <description summary="sets a margin from the anchor point">
-        Requests that the surface be placed some distance away from the anchor
-        point on the output, in surface-local coordinates. Setting this value
-        for edges you are not anchored to has no effect.
-
-        The exclusive zone includes the margin.
-
-        Margin is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="top" type="int"/>
-      <arg name="right" type="int"/>
-      <arg name="bottom" type="int"/>
-      <arg name="left" type="int"/>
-    </request>
-
-    <enum name="keyboard_interactivity">
-      <description summary="types of keyboard interaction possible for a layer shell surface">
-        Types of keyboard interaction possible for layer shell surfaces. The
-        rationale for this is twofold: (1) some applications are not interested
-        in keyboard events and not allowing them to be focused can improve the
-        desktop experience; (2) some applications will want to take exclusive
-        keyboard focus.
-      </description>
-
-      <entry name="none" value="0">
-        <description summary="no keyboard focus is possible">
-          This value indicates that this surface is not interested in keyboard
-          events and the compositor should never assign it the keyboard focus.
-
-          This is the default value, set for newly created layer shell surfaces.
-
-          This is useful for e.g. desktop widgets that display information or
-          only have interaction with non-keyboard input devices.
-        </description>
-      </entry>
-      <entry name="exclusive" value="1">
-        <description summary="request exclusive keyboard focus">
-          Request exclusive keyboard focus if this surface is above the shell surface layer.
-
-          For the top and overlay layers, the seat will always give
-          exclusive keyboard focus to the top-most layer which has keyboard
-          interactivity set to exclusive. If this layer contains multiple
-          surfaces with keyboard interactivity set to exclusive, the compositor
-          determines the one receiving keyboard events in an implementation-
-          defined manner. In this case, no guarantee is made when this surface
-          will receive keyboard focus (if ever).
-
-          For the bottom and background layers, the compositor is allowed to use
-          normal focus semantics.
-
-          This setting is mainly intended for applications that need to ensure
-          they receive all keyboard events, such as a lock screen or a password
-          prompt.
-        </description>
-      </entry>
-      <entry name="on_demand" value="2" since="4">
-        <description summary="request regular keyboard focus semantics">
-          This requests the compositor to allow this surface to be focused and
-          unfocused by the user in an implementation-defined manner. The user
-          should be able to unfocus this surface even regardless of the layer
-          it is on.
-
-          Typically, the compositor will want to use its normal mechanism to
-          manage keyboard focus between layer shell surfaces with this setting
-          and regular toplevels on the desktop layer (e.g. click to focus).
-          Nevertheless, it is possible for a compositor to require a special
-          interaction to focus or unfocus layer shell surfaces (e.g. requiring
-          a click even if focus follows the mouse normally, or providing a
-          keybinding to switch focus between layers).
-
-          This setting is mainly intended for desktop shell components (e.g.
-          panels) that allow keyboard interaction. Using this option can allow
-          implementing a desktop shell that can be fully usable without the
-          mouse.
-        </description>
-      </entry>
-    </enum>
-
-    <request name="set_keyboard_interactivity">
-      <description summary="requests keyboard events">
-        Set how keyboard events are delivered to this surface. By default,
-        layer shell surfaces do not receive keyboard events; this request can
-        be used to change this.
-
-        This setting is inherited by child surfaces set by the get_popup
-        request.
-
-        Layer surfaces receive pointer, touch, and tablet events normally. If
-        you do not want to receive them, set the input region on your surface
-        to an empty region.
-
-        Keyboard interactivity is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
-    </request>
-
-    <request name="get_popup">
-      <description summary="assign this layer_surface as an xdg_popup parent">
-        This assigns an xdg_popup's parent to this layer_surface.  This popup
-        should have been created via xdg_surface::get_popup with the parent set
-        to NULL, and this request must be invoked before committing the popup's
-        initial state.
-
-        See the documentation of xdg_popup for more details about what an
-        xdg_popup is and how it is used.
-      </description>
-      <arg name="popup" type="object" interface="xdg_popup"/>
-    </request>
-
-    <request name="ack_configure">
-      <description summary="ack a configure event">
-        When a configure event is received, if a client commits the
-        surface in response to the configure event, then the client
-        must make an ack_configure request sometime before the commit
-        request, passing along the serial of the configure event.
-
-        If the client receives multiple configure events before it
-        can respond to one, it only has to ack the last configure event.
-
-        A client is not required to commit immediately after sending
-        an ack_configure request - it may even ack_configure several times
-        before its next surface commit.
-
-        A client may send multiple ack_configure requests before committing, but
-        only the last request sent before a commit indicates which configure
-        event the client really is responding to.
-      </description>
-      <arg name="serial" type="uint" summary="the serial from the configure event"/>
-    </request>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the layer_surface">
-        This request destroys the layer surface.
-      </description>
-    </request>
-
-    <event name="configure">
-      <description summary="suggest a surface change">
-        The configure event asks the client to resize its surface.
-
-        Clients should arrange their surface for the new states, and then send
-        an ack_configure request with the serial sent in this configure event at
-        some point before committing the new surface.
-
-        The client is free to dismiss all but the last configure event it
-        received.
-
-        The width and height arguments specify the size of the window in
-        surface-local coordinates.
-
-        The size is a hint, in the sense that the client is free to ignore it if
-        it doesn't resize, pick a smaller size (to satisfy aspect ratio or
-        resize in steps of NxM pixels). If the client picks a smaller size and
-        is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
-        surface will be centered on this axis.
-
-        If the width or height arguments are zero, it means the client should
-        decide its own window dimension.
-      </description>
-      <arg name="serial" type="uint"/>
-      <arg name="width" type="uint"/>
-      <arg name="height" type="uint"/>
-    </event>
-
-    <event name="closed">
-      <description summary="surface should be closed">
-        The closed event is sent by the compositor when the surface will no
-        longer be shown. The output may have been destroyed or the user may
-        have asked for it to be removed. Further changes to the surface will be
-        ignored. The client should destroy the resource after receiving this
-        event, and create a new surface if they so choose.
-      </description>
-    </event>
-
-    <enum name="error">
-      <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
-      <entry name="invalid_size" value="1" summary="size is invalid"/>
-      <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
-      <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
-      <entry name="invalid_exclusive_edge" value="4" summary="exclusive edge is invalid given the surface anchors"/>
-    </enum>
-
-    <enum name="anchor" bitfield="true">
-      <entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
-      <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
-      <entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
-      <entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
-    </enum>
-
-    <!-- Version 2 additions -->
-
-    <request name="set_layer" since="2">
-      <description summary="change the layer of the surface">
-        Change the layer that the surface is rendered on.
-
-        Layer is double-buffered, see wl_surface.commit.
-      </description>
-      <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
-    </request>
-
-    <!-- Version 5 additions -->
-
-    <request name="set_exclusive_edge" since="5">
-      <description summary="set the edge the exclusive zone will be applied to">
-        Requests an edge for the exclusive zone to apply. The exclusive
-        edge will be automatically deduced from anchor points when possible,
-        but when the surface is anchored to a corner, it will be necessary
-        to set it explicitly to disambiguate, as it is not possible to deduce
-        which one of the two corner edges should be used.
-
-        The edge must be one the surface is anchored to, otherwise the
-        invalid_exclusive_edge protocol error will be raised.
-      </description>
-      <arg name="edge" type="uint" enum="anchor"/>
-    </request>
-  </interface>
-</protocol>
diff --git a/protocol/upstream/wlr-output-power-management-unstable-v1.xml b/protocol/upstream/wlr-output-power-management-unstable-v1.xml
deleted file mode 100644
index 9416579..0000000
--- a/protocol/upstream/wlr-output-power-management-unstable-v1.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-<!-- SPDX-License-Identifier: MIT -->
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="wlr_output_power_management_unstable_v1">
-  <copyright>
-    Copyright © 2019 Purism SPC
-
-    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 (including the next
-    paragraph) 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="Control power management modes of outputs">
-    This protocol allows clients to control power management modes
-    of outputs that are currently part of the compositor space. The
-    intent is to allow special clients like desktop shells to power
-    down outputs when the system is idle.
-
-    To modify outputs not currently part of the compositor space see
-    wlr-output-management.
-
-    Warning! The protocol described in this file is experimental and
-    backward incompatible changes may be made. Backward compatible changes
-    may be added together with the corresponding interface version bump.
-    Backward incompatible changes are done by bumping the version number in
-    the protocol and interface names and resetting the interface version.
-    Once the protocol is to be declared stable, the 'z' prefix and the
-    version number in the protocol and interface names are removed and the
-    interface version number is reset.
-  </description>
-
-  <interface name="zwlr_output_power_manager_v1" version="1">
-    <description summary="manager to create per-output power management">
-      This interface is a manager that allows creating per-output power
-      management mode controls.
-    </description>
-
-    <request name="get_output_power">
-      <description summary="get a power management for an output">
-        Create an output power management mode control that can be used to
-        adjust the power management mode for a given output.
-      </description>
-      <arg name="id" type="new_id" interface="zwlr_output_power_v1"/>
-      <arg name="output" type="object" interface="wl_output"/>
-    </request>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy the manager">
-        All objects created by the manager will still remain valid, until their
-        appropriate destroy request has been called.
-      </description>
-    </request>
-  </interface>
-
-  <interface name="zwlr_output_power_v1" version="1">
-    <description summary="adjust power management mode for an output">
-      This object offers requests to set the power management mode of
-      an output.
-    </description>
-
-    <enum name="mode">
-      <entry name="off" value="0"
-             summary="Output is turned off."/>
-      <entry name="on" value="1"
-             summary="Output is turned on, no power saving"/>
-    </enum>
-
-    <enum name="error">
-      <entry name="invalid_mode" value="1" summary="nonexistent power save mode"/>
-    </enum>
-
-    <request name="set_mode">
-      <description summary="Set an outputs power save mode">
-        Set an output's power save mode to the given mode. The mode change
-        is effective immediately. If the output does not support the given
-        mode a failed event is sent.
-      </description>
-      <arg name="mode" type="uint" enum="mode" summary="the power save mode to set"/>
-    </request>
-
-    <event name="mode">
-      <description summary="Report a power management mode change">
-        Report the power management mode change of an output.
-
-        The mode event is sent after an output changed its power
-        management mode. The reason can be a client using set_mode or the
-        compositor deciding to change an output's mode.
-        This event is also sent immediately when the object is created
-        so the client is informed about the current power management mode.
-      </description>
-      <arg name="mode" type="uint" enum="mode"
-           summary="the output's new power management mode"/>
-    </event>
-
-    <event name="failed">
-      <description summary="object no longer valid">
-        This event indicates that the output power management mode control
-        is no longer valid. This can happen for a number of reasons,
-        including:
-        - The output doesn't support power management
-        - Another client already has exclusive power management mode control
-          for this output
-        - The output disappeared
-
-        Upon receiving this event, the client should destroy this object.
-      </description>
-    </event>
-
-    <request name="destroy" type="destructor">
-      <description summary="destroy this power management">
-        Destroys the output power management mode control object.
-      </description>
-    </request>
-  </interface>
-</protocol>
diff --git a/river/Cursor.zig b/river/Cursor.zig
deleted file mode 100644
index d8c1744..0000000
--- a/river/Cursor.zig
+++ /dev/null
@@ -1,953 +0,0 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const Cursor = @This();
-
-const build_options = @import("build_options");
-const std = @import("std");
-const assert = std.debug.assert;
-const posix = std.posix;
-const math = std.math;
-const wlr = @import("wlroots");
-const wayland = @import("wayland");
-const wl = wayland.server.wl;
-const zwlr = wayland.server.zwlr;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const DragIcon = @import("DragIcon.zig");
-const InputDevice = @import("InputDevice.zig");
-const LockSurface = @import("LockSurface.zig");
-const Output = @import("Output.zig");
-const PointerBinding = @import("PointerBinding.zig");
-const PointerConstraint = @import("PointerConstraint.zig");
-const Scene = @import("Scene.zig");
-const Seat = @import("Seat.zig");
-const Tablet = @import("Tablet.zig");
-const TabletTool = @import("TabletTool.zig");
-const Window = @import("Window.zig");
-const XwaylandOverrideRedirect = @import("XwaylandOverrideRedirect.zig");
-
-const log = std.log.scoped(.input);
-
-const Mode = union(enum) {
-    passthrough,
-    /// This mode is entered when a binding is triggered and exited when there
-    /// are no longer any buttons pressed.
-    ignore,
-    /// A drag and drop is in progress.
-    drag,
-    down: struct {
-        // TODO: To handle the surface with pointer focus being moved during
-        // down mode we need to store the starting location of the surface as
-        // well and take that into account. This is currently not at all easy
-        // to do, but moing to the wlroots scene graph will allow us to fix this.
-
-        // Initial cursor position in layout coordinates
-        lx: f64,
-        ly: f64,
-        // Initial cursor position in surface-local coordinates
-        sx: f64,
-        sy: f64,
-    },
-    op: struct {
-        /// Window coordinates are stored as i32s as they are in logical pixels.
-        /// However, it is possible to move the cursor by a fraction of a
-        /// logical pixel and this happens in practice with low dpi, high
-        /// polling rate mice. Therefore we must accumulate the current
-        /// fractional offset of the mouse to avoid rounding down tiny
-        /// motions to 0.
-        delta_x: f64 = 0,
-        delta_y: f64 = 0,
-    },
-};
-
-const default_size = 24;
-
-const Image = union(enum) {
-    /// No cursor image
-    none,
-    /// Name of the current Xcursor shape
-    xcursor: [*:0]const u8,
-    /// Cursor surface configured by the client
-    client: struct {
-        surface: *wlr.Surface,
-        hotspot_x: i32,
-        hotspot_y: i32,
-    },
-};
-
-const LayoutPoint = struct {
-    lx: f64,
-    ly: f64,
-};
-
-/// Current cursor mode as well as any state needed to implement that mode
-mode: Mode = .passthrough,
-
-seat: *Seat,
-wlr_cursor: *wlr.Cursor,
-
-/// Xcursor manager for the currently configured Xcursor theme.
-xcursor_manager: *wlr.XcursorManager,
-/// The currently rendered cursor image
-image: Image = .none,
-image_surface_destroy: wl.Listener(*wlr.Surface) = .init(handleImageSurfaceDestroy),
-/// The most recent cursor image set by the window manager client
-wm_image: Image = .{ .xcursor = "default" },
-wm_image_surface_destroy: wl.Listener(*wlr.Surface) = .init(handleWmImageSurfaceDestroy),
-
-/// The set of currently pressed pointer buttons and the corresponding pointer mapping if any.
-pressed: std.AutoHashMapUnmanaged(u32, ?*PointerBinding) = .{},
-
-/// The pointer constraint for the surface that currently has keyboard focus, if any.
-/// This constraint is not necessarily active, activation only occurs once the cursor
-/// has been moved inside the constraint region.
-constraint: ?*PointerConstraint = null,
-
-/// Keeps track of the last known location of all touch points in layout coordinates.
-/// This information is necessary for proper touch dnd support if there are multiple touch points.
-touch_points: std.AutoHashMapUnmanaged(i32, LayoutPoint) = .{},
-
-request_set_cursor: wl.Listener(*wlr.Seat.event.RequestSetCursor) = .init(handleRequestSetCursor),
-
-motion_relative: wl.Listener(*wlr.Pointer.event.Motion) = .init(queueMotionRelative),
-motion_absolute: wl.Listener(*wlr.Pointer.event.MotionAbsolute) = .init(queueMotionAbsolute),
-button: wl.Listener(*wlr.Pointer.event.Button) = .init(queueButton),
-axis: wl.Listener(*wlr.Pointer.event.Axis) = .init(queueAxis),
-frame: wl.Listener(*wlr.Cursor) = .init(queueFrame),
-
-swipe_begin: wl.Listener(*wlr.Pointer.event.SwipeBegin) = .init(queueSwipeBegin),
-swipe_update: wl.Listener(*wlr.Pointer.event.SwipeUpdate) = .init(queueSwipeUpdate),
-swipe_end: wl.Listener(*wlr.Pointer.event.SwipeEnd) = .init(queueSwipeEnd),
-
-pinch_begin: wl.Listener(*wlr.Pointer.event.PinchBegin) = .init(queuePinchBegin),
-pinch_update: wl.Listener(*wlr.Pointer.event.PinchUpdate) = .init(queuePinchUpdate),
-pinch_end: wl.Listener(*wlr.Pointer.event.PinchEnd) = .init(queuePinchEnd),
-
-hold_begin: wl.Listener(*wlr.Pointer.event.HoldBegin) = .init(queueHoldBegin),
-hold_end: wl.Listener(*wlr.Pointer.event.HoldEnd) = .init(queueHoldEnd),
-
-touch_down: wl.Listener(*wlr.Touch.event.Down) = .init(handleTouchDown),
-touch_motion: wl.Listener(*wlr.Touch.event.Motion) = .init(handleTouchMotion),
-touch_up: wl.Listener(*wlr.Touch.event.Up) = .init(handleTouchUp),
-touch_cancel: wl.Listener(*wlr.Touch.event.Cancel) = .init(handleTouchCancel),
-touch_frame: wl.Listener(void) = .init(handleTouchFrame),
-
-tablet_tool_axis: wl.Listener(*wlr.Tablet.event.Axis) = .init(handleTabletToolAxis),
-tablet_tool_proximity: wl.Listener(*wlr.Tablet.event.Proximity) = .init(handleTabletToolProximity),
-tablet_tool_tip: wl.Listener(*wlr.Tablet.event.Tip) = .init(handleTabletToolTip),
-tablet_tool_button: wl.Listener(*wlr.Tablet.event.Button) = .init(handleTabletToolButton),
-
-pub fn init(cursor: *Cursor, seat: *Seat) !void {
-    const wlr_cursor = try wlr.Cursor.create();
-    errdefer wlr_cursor.destroy();
-    wlr_cursor.attachOutputLayout(server.om.output_layout);
-
-    // This is here so that cursor.xcursor_manager doesn't need to be an
-    // optional pointer. This isn't optimal as it does a needless allocation,
-    // but this is not a hot path.
-    const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
-    errdefer xcursor_manager.destroy();
-
-    cursor.* = .{
-        .seat = seat,
-        .wlr_cursor = wlr_cursor,
-        .xcursor_manager = xcursor_manager,
-    };
-    try cursor.setTheme(null, null);
-
-    seat.wlr_seat.events.request_set_cursor.add(&cursor.request_set_cursor);
-
-    wlr_cursor.events.motion.add(&cursor.motion_relative);
-    wlr_cursor.events.motion_absolute.add(&cursor.motion_absolute);
-    wlr_cursor.events.button.add(&cursor.button);
-    wlr_cursor.events.axis.add(&cursor.axis);
-    wlr_cursor.events.frame.add(&cursor.frame);
-
-    wlr_cursor.events.swipe_begin.add(&cursor.swipe_begin);
-    wlr_cursor.events.swipe_update.add(&cursor.swipe_update);
-    wlr_cursor.events.swipe_end.add(&cursor.swipe_end);
-
-    wlr_cursor.events.pinch_begin.add(&cursor.pinch_begin);
-    wlr_cursor.events.pinch_update.add(&cursor.pinch_update);
-    wlr_cursor.events.pinch_end.add(&cursor.pinch_end);
-
-    wlr_cursor.events.hold_begin.add(&cursor.hold_begin);
-    wlr_cursor.events.hold_end.add(&cursor.hold_end);
-
-    wlr_cursor.events.touch_down.add(&cursor.touch_down);
-    wlr_cursor.events.touch_motion.add(&cursor.touch_motion);
-    wlr_cursor.events.touch_up.add(&cursor.touch_up);
-    wlr_cursor.events.touch_cancel.add(&cursor.touch_cancel);
-    wlr_cursor.events.touch_frame.add(&cursor.touch_frame);
-
-    wlr_cursor.events.tablet_tool_axis.add(&cursor.tablet_tool_axis);
-    wlr_cursor.events.tablet_tool_proximity.add(&cursor.tablet_tool_proximity);
-    wlr_cursor.events.tablet_tool_tip.add(&cursor.tablet_tool_tip);
-    wlr_cursor.events.tablet_tool_button.add(&cursor.tablet_tool_button);
-}
-
-pub fn deinit(cursor: *Cursor) void {
-    cursor.axis.link.remove();
-    cursor.button.link.remove();
-    cursor.frame.link.remove();
-    cursor.motion_absolute.link.remove();
-    cursor.motion_relative.link.remove();
-    cursor.swipe_begin.link.remove();
-    cursor.swipe_update.link.remove();
-    cursor.swipe_end.link.remove();
-    cursor.pinch_begin.link.remove();
-    cursor.pinch_update.link.remove();
-    cursor.pinch_end.link.remove();
-    cursor.hold_begin.link.remove();
-    cursor.hold_end.link.remove();
-    cursor.request_set_cursor.link.remove();
-
-    cursor.touch_down.link.remove();
-    cursor.touch_motion.link.remove();
-    cursor.touch_up.link.remove();
-    cursor.touch_cancel.link.remove();
-    cursor.touch_frame.link.remove();
-
-    cursor.tablet_tool_axis.link.remove();
-    cursor.tablet_tool_proximity.link.remove();
-    cursor.tablet_tool_tip.link.remove();
-    cursor.tablet_tool_button.link.remove();
-
-    cursor.xcursor_manager.destroy();
-    cursor.wlr_cursor.destroy();
-    cursor.pressed.deinit(util.gpa);
-    cursor.touch_points.deinit(util.gpa);
-}
-
-/// Set the cursor theme for the given seat, as well as the xwayland theme if
-/// this is the default seat. Either argument may be null, in which case a
-/// default will be used.
-pub fn setTheme(cursor: *Cursor, theme: ?[*:0]const u8, _size: ?u32) !void {
-    const size = _size orelse default_size;
-
-    const xcursor_manager = try wlr.XcursorManager.create(theme, size);
-    errdefer xcursor_manager.destroy();
-
-    // If this cursor belongs to the default seat, update the Xwayland cursor to match the theme.
-    // This cursor is communicated to Xwayland clients through the XCB_CW_CURSOR window attribute.
-    if (cursor.seat == server.input_manager.defaultSeat()) {
-        if (build_options.xwayland) {
-            if (server.xwayland) |xwayland| {
-                try xcursor_manager.load(1);
-                const wlr_xcursor = xcursor_manager.getXcursor("default", 1).?;
-                const image = wlr_xcursor.images[0];
-                xwayland.setCursor(
-                    image.getBuffer(),
-                    @intCast(image.hotspot_x),
-                    @intCast(image.hotspot_y),
-                );
-            }
-        }
-    }
-
-    // Everything fallible is now done so the the old xcursor_manager can be destroyed.
-    cursor.xcursor_manager.destroy();
-    cursor.xcursor_manager = xcursor_manager;
-
-    switch (cursor.image) {
-        .none, .client => {},
-        .xcursor => |name| cursor.wlr_cursor.setXcursor(xcursor_manager, name),
-    }
-}
-
-pub fn setImage(cursor: *Cursor, image: Image) void {
-    if (cursor.image == .client) {
-        cursor.image_surface_destroy.link.remove();
-    }
-    cursor.image = image;
-    switch (cursor.image) {
-        .none => cursor.wlr_cursor.unsetImage(),
-        .xcursor => |name| cursor.wlr_cursor.setXcursor(cursor.xcursor_manager, name),
-        .client => |client| {
-            client.surface.events.destroy.add(&cursor.image_surface_destroy);
-            cursor.wlr_cursor.setSurface(client.surface, client.hotspot_x, client.hotspot_y);
-        },
-    }
-}
-
-fn handleImageSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
-    const cursor: *Cursor = @fieldParentPtr("image_surface_destroy", listener);
-    // wlroots calls wlr_cursor_unset_image() automatically
-    // when the cursor surface is destroyed.
-    cursor.image = .none;
-    cursor.image_surface_destroy.link.remove();
-}
-
-pub fn setWmImage(cursor: *Cursor, wm_image: Image) void {
-    if (cursor.wm_image == .client) {
-        cursor.wm_image_surface_destroy.link.remove();
-    }
-    cursor.wm_image = wm_image;
-    if (cursor.wm_image == .client) {
-        cursor.wm_image.client.surface.events.destroy.add(&cursor.wm_image_surface_destroy);
-    }
-    if (cursor.seat.wlr_seat.pointer_state.focused_client == null) {
-        cursor.setImage(wm_image);
-    }
-}
-
-fn handleWmImageSurfaceDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
-    const cursor: *Cursor = @fieldParentPtr("wm_image_surface_destroy", listener);
-    cursor.wm_image = .none;
-    cursor.wm_image_surface_destroy.link.remove();
-}
-
-fn handleRequestSetCursor(
-    listener: *wl.Listener(*wlr.Seat.event.RequestSetCursor),
-    event: *wlr.Seat.event.RequestSetCursor,
-) void {
-    const cursor: *Cursor = @fieldParentPtr("request_set_cursor", listener);
-    const focused_client = cursor.seat.wlr_seat.pointer_state.focused_client;
-
-    // Only the client with pointer focus is allowed to set the cursor
-    if (event.seat_client == focused_client) {
-        log.debug("focused client set cursor", .{});
-        if (event.surface) |surface| {
-            cursor.setImage(.{ .client = .{
-                .surface = surface,
-                .hotspot_x = event.hotspot_x,
-                .hotspot_y = event.hotspot_y,
-            } });
-        } else {
-            cursor.setImage(.none);
-        }
-    }
-    // Except for the window manager client
-    if (server.wm.object) |object| {
-        if (event.seat_client.client == object.getClient() and
-            object.getVersion() >= 4)
-        {
-            if (event.surface) |surface| {
-                cursor.setWmImage(.{ .client = .{
-                    .surface = surface,
-                    .hotspot_x = event.hotspot_x,
-                    .hotspot_y = event.hotspot_y,
-                } });
-            } else {
-                cursor.setWmImage(.none);
-            }
-        }
-    }
-}
-
-fn clearFocus(cursor: *Cursor) void {
-    cursor.setImage(cursor.wm_image);
-    cursor.seat.wlr_seat.pointerNotifyClearFocus();
-}
-
-pub fn opStartPointer(cursor: *Cursor) void {
-    if (cursor.constraint) |constraint| {
-        if (constraint.state == .active) constraint.deactivate();
-    }
-
-    log.debug("entering cursor mode op", .{});
-    cursor.mode = .{ .op = .{} };
-
-    cursor.clearFocus();
-}
-
-pub fn opEndPointer(cursor: *Cursor) void {
-    if (cursor.pressed.count() == 0) {
-        log.debug("entering cursor mode passthrough", .{});
-        cursor.mode = .passthrough;
-        cursor.updateState();
-    } else {
-        log.debug("entering cursor mode ignore", .{});
-        cursor.mode = .ignore;
-    }
-}
-
-pub fn processMotionRelative(cursor: *Cursor, event: *const Seat.Event.PointerMotionRelative) void {
-    server.input_manager.relative_pointer_manager.sendRelativeMotion(
-        cursor.seat.wlr_seat,
-        @as(u64, event.time_msec) * 1000,
-        event.delta_x,
-        event.delta_y,
-        event.unaccel_dx,
-        event.unaccel_dy,
-    );
-
-    var dx: f64 = event.delta_x;
-    var dy: f64 = event.delta_y;
-
-    if (cursor.constraint) |constraint| {
-        if (constraint.state == .active) {
-            switch (constraint.wlr_constraint.type) {
-                .locked => return,
-                .confined => constraint.confine(&dx, &dy),
-            }
-        }
-    }
-
-    switch (cursor.mode) {
-        .passthrough, .drag, .ignore, .down => {
-            cursor.move(&event.mapping, dx, dy);
-
-            switch (cursor.mode) {
-                .passthrough, .drag => {
-                    cursor.updateHovered();
-                    cursor.passthrough(event.time_msec);
-                },
-                .ignore => {},
-                .down => |data| {
-                    cursor.seat.wlr_seat.pointerNotifyMotion(
-                        event.time_msec,
-                        data.sx + (cursor.wlr_cursor.x - data.lx),
-                        data.sy + (cursor.wlr_cursor.y - data.ly),
-                    );
-                },
-                else => unreachable,
-            }
-
-            cursor.updateDragIcons();
-
-            if (cursor.constraint) |constraint| {
-                constraint.maybeActivate();
-            }
-        },
-        .op => |*data| {
-            dx += data.delta_x;
-            dy += data.delta_y;
-            data.delta_x = dx - @trunc(dx);
-            data.delta_y = dy - @trunc(dy);
-
-            cursor.move(&event.mapping, dx, dy);
-            cursor.seat.opUpdate(@intFromFloat(cursor.wlr_cursor.x), @intFromFloat(cursor.wlr_cursor.y));
-        },
-    }
-}
-
-fn move(cursor: *const Cursor, mapping: *const wlr.Box, dx: f64, dy: f64) void {
-    var lx: f64 = cursor.wlr_cursor.x + dx;
-    var ly: f64 = cursor.wlr_cursor.y + dy;
-    if (!mapping.empty()) {
-        mapping.closestPoint(lx, ly, &lx, &ly);
-    }
-    cursor.wlr_cursor.warpClosest(null, lx, ly);
-}
-
-fn updateHovered(cursor: *Cursor) void {
-    const old = cursor.seat.wm_scheduled.hovered;
-    if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
-        switch (result.data) {
-            .window => |window| {
-                switch (window.impl) {
-                    .toplevel => |toplevel| {
-                        // Exclude input regions of the toplevel that extend beyond the window
-                        if (result.surface != null and result.surface.?.getRootSurface() == toplevel.wlr_toplevel.base.surface) {
-                            if (window.box.containsPoint(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) {
-                                cursor.seat.wm_scheduled.hovered = window.ref;
-                            }
-                        } else {
-                            cursor.seat.wm_scheduled.hovered = window.ref;
-                        }
-                    },
-                    .xwayland => cursor.seat.wm_scheduled.hovered = window.ref,
-                    .destroying => {},
-                }
-            },
-            .shell_surface, .lock_surface, .layer_surface => {
-                cursor.seat.wm_scheduled.hovered = null;
-            },
-            .override_redirect => {
-                assert(build_options.xwayland);
-                assert(server.xwayland != null);
-                cursor.seat.wm_scheduled.hovered = null;
-            },
-        }
-    } else {
-        cursor.seat.wm_scheduled.hovered = null;
-    }
-
-    if (cursor.seat.wm_scheduled.hovered != old) {
-        server.wm.dirtyWindowing();
-    }
-}
-
-pub fn processMotionAbsolute(cursor: *Cursor, event: *const Seat.Event.PointerMotionAbsolute) void {
-    var mapping = event.mapping;
-    if (mapping.empty()) {
-        server.om.output_layout.getBox(null, &mapping);
-    }
-    const lx = @as(f64, @floatFromInt(mapping.x)) + @as(f64, @floatFromInt(mapping.width)) * event.x;
-    const ly = @as(f64, @floatFromInt(mapping.y)) + @as(f64, @floatFromInt(mapping.height)) * event.y;
-    const dx = lx - cursor.wlr_cursor.x;
-    const dy = ly - cursor.wlr_cursor.y;
-    cursor.processMotionRelative(&.{
-        .mapping = event.mapping,
-        .time_msec = event.time_msec,
-        .delta_x = dx,
-        .delta_y = dy,
-        .unaccel_dx = dx,
-        .unaccel_dy = dy,
-    });
-}
-
-pub fn processButton(cursor: *Cursor, event: *const Seat.Event.PointerButton) void {
-    if (event.state == .pressed) {
-        const result = cursor.pressed.getOrPut(util.gpa, event.button) catch {
-            log.err("out of memory", .{});
-            return;
-        };
-        if (result.found_existing) {
-            log.err("ignoring duplicate pointer button {d} press", .{event.button});
-            return;
-        }
-
-        if (cursor.seat.matchPointerBinding(event.button)) |binding| {
-            result.value_ptr.* = binding;
-            binding.pressed();
-            switch (cursor.mode) {
-                .passthrough, .drag, .down => {
-                    log.debug("entering cursor mode ignore", .{});
-                    cursor.mode = .ignore;
-                    cursor.clearFocus();
-                },
-                // It is important that we do not enter ignore mode if an op is in progress,
-                // doing so would result in op_release never being sent for example.
-                .op, .ignore => {},
-            }
-            return;
-        }
-
-        result.value_ptr.* = null;
-
-        switch (cursor.mode) {
-            .passthrough => {
-                if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |at| {
-                    cursor.interact(at);
-
-                    if (at.surface != null) {
-                        _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
-                        log.debug("entering cursor mode down", .{});
-                        cursor.mode = .{
-                            .down = .{
-                                .lx = cursor.wlr_cursor.x,
-                                .ly = cursor.wlr_cursor.y,
-                                .sx = at.sx,
-                                .sy = at.sy,
-                            },
-                        };
-                        return;
-                    }
-                }
-
-                log.debug("entering cursor mode ignore", .{});
-                cursor.mode = .ignore;
-                cursor.clearFocus();
-                return;
-            },
-            .drag => {
-                if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |at| {
-                    cursor.interact(at);
-                    if (at.surface != null) {
-                        _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
-                        return;
-                    }
-                }
-                cursor.clearFocus();
-            },
-            // Pointer focus does not change while in down mode.
-            .down => {
-                _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
-            },
-            // No client has pointer focus while in ignore/op mode.
-            .ignore, .op => {},
-        }
-    } else {
-        assert(event.state == .released);
-        const result = cursor.pressed.fetchRemove(event.button);
-        if (result) |kv| {
-            if (kv.value) |binding| {
-                binding.released();
-            }
-
-            switch (cursor.mode) {
-                .passthrough => unreachable,
-                .drag, .down, .ignore => {
-                    if (cursor.mode != .ignore) {
-                        _ = cursor.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
-                    }
-                    if (cursor.pressed.count() == 0) {
-                        log.debug("exiting cursor mode {s}", .{@tagName(cursor.mode)});
-                        cursor.mode = .passthrough;
-                        cursor.passthrough(event.time_msec);
-                    }
-                },
-                .op => {
-                    if (cursor.pressed.count() == 0) {
-                        cursor.seat.wm_scheduled.op_release = true;
-                        server.wm.dirtyWindowing();
-                    }
-                },
-            }
-        } else {
-            log.err("ignoring duplicate pointer button {d} release", .{event.button});
-            return;
-        }
-    }
-}
-
-pub fn processAxis(cursor: *Cursor, event: *const Seat.Event.PointerAxis) void {
-    cursor.seat.wlr_seat.pointerNotifyAxis(
-        event.time_msec,
-        event.orientation,
-        event.delta,
-        event.delta_discrete,
-        event.source,
-        event.relative_direction,
-    );
-}
-
-fn interact(cursor: Cursor, result: Scene.AtResult) void {
-    switch (result.data) {
-        .window => |window| {
-            cursor.seat.wm_scheduled.interaction = .{ .window = window.ref };
-            server.wm.dirtyWindowing();
-        },
-        .shell_surface => |shell_surface| {
-            cursor.seat.wm_scheduled.interaction = .{ .shell_surface = shell_surface };
-            server.wm.dirtyWindowing();
-        },
-        .lock_surface => |lock_surface| {
-            assert(server.lock_manager.state != .unlocked);
-            cursor.seat.focus(.{ .lock_surface = lock_surface });
-        },
-        .layer_surface => |layer_surface| {
-            switch (cursor.seat.layer_shell.scheduled.focus) {
-                .none, .non_exclusive => {
-                    if (layer_surface.wlr_layer_surface.current.keyboard_interactive == .on_demand) {
-                        cursor.seat.layer_shell.scheduled.focus = .{
-                            .non_exclusive = layer_surface.ref,
-                        };
-                        server.wm.dirtyWindowing();
-                    }
-                },
-                .exclusive => {},
-            }
-        },
-        .override_redirect => |override_redirect| {
-            assert(server.lock_manager.state != .locked);
-            override_redirect.focusIfDesired();
-        },
-    }
-}
-
-fn handleTouchDown(
-    listener: *wl.Listener(*wlr.Touch.event.Down),
-    event: *wlr.Touch.event.Down,
-) void {
-    const cursor: *Cursor = @fieldParentPtr("touch_down", listener);
-
-    cursor.seat.handleActivity();
-
-    var lx: f64 = undefined;
-    var ly: f64 = undefined;
-    cursor.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
-
-    cursor.touch_points.putNoClobber(util.gpa, event.touch_id, .{ .lx = lx, .ly = ly }) catch {
-        log.err("out of memory", .{});
-        return;
-    };
-
-    if (server.scene.at(lx, ly)) |result| {
-        cursor.interact(result);
-
-        if (result.surface) |surface| {
-            _ = cursor.seat.wlr_seat.touchNotifyDown(
-                surface,
-                event.time_msec,
-                event.touch_id,
-                result.sx,
-                result.sy,
-            );
-        }
-    }
-}
-
-fn handleTouchMotion(
-    listener: *wl.Listener(*wlr.Touch.event.Motion),
-    event: *wlr.Touch.event.Motion,
-) void {
-    const cursor: *Cursor = @fieldParentPtr("touch_motion", listener);
-
-    cursor.seat.handleActivity();
-
-    if (cursor.touch_points.getPtr(event.touch_id)) |point| {
-        cursor.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &point.lx, &point.ly);
-
-        cursor.updateDragIcons();
-
-        if (server.scene.at(point.lx, point.ly)) |result| {
-            cursor.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy);
-        }
-    }
-}
-
-fn handleTouchUp(
-    listener: *wl.Listener(*wlr.Touch.event.Up),
-    event: *wlr.Touch.event.Up,
-) void {
-    const cursor: *Cursor = @fieldParentPtr("touch_up", listener);
-
-    cursor.seat.handleActivity();
-
-    if (cursor.touch_points.remove(event.touch_id)) {
-        _ = cursor.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id);
-    }
-}
-
-fn handleTouchCancel(
-    listener: *wl.Listener(*wlr.Touch.event.Cancel),
-    _: *wlr.Touch.event.Cancel,
-) void {
-    const cursor: *Cursor = @fieldParentPtr("touch_cancel", listener);
-
-    cursor.seat.handleActivity();
-
-    cursor.touch_points.clearRetainingCapacity();
-
-    const wlr_seat = cursor.seat.wlr_seat;
-    while (wlr_seat.touch_state.touch_points.first()) |touch_point| {
-        wlr_seat.touchNotifyCancel(touch_point.client);
-    }
-}
-
-fn handleTouchFrame(listener: *wl.Listener(void)) void {
-    const cursor: *Cursor = @fieldParentPtr("touch_frame", listener);
-
-    cursor.seat.handleActivity();
-
-    cursor.seat.wlr_seat.touchNotifyFrame();
-}
-
-fn handleTabletToolAxis(
-    _: *wl.Listener(*wlr.Tablet.event.Axis),
-    event: *wlr.Tablet.event.Axis,
-) void {
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    const tablet: *Tablet = @fieldParentPtr("device", device);
-
-    device.seat.handleActivity();
-
-    const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
-
-    tool.axis(tablet, event);
-}
-
-fn handleTabletToolProximity(
-    _: *wl.Listener(*wlr.Tablet.event.Proximity),
-    event: *wlr.Tablet.event.Proximity,
-) void {
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    const tablet: *Tablet = @fieldParentPtr("device", device);
-
-    device.seat.handleActivity();
-
-    const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
-
-    tool.proximity(tablet, event);
-}
-
-fn handleTabletToolTip(
-    _: *wl.Listener(*wlr.Tablet.event.Tip),
-    event: *wlr.Tablet.event.Tip,
-) void {
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    const tablet: *Tablet = @fieldParentPtr("device", device);
-
-    device.seat.handleActivity();
-
-    const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
-
-    tool.tip(tablet, event);
-}
-
-fn handleTabletToolButton(
-    _: *wl.Listener(*wlr.Tablet.event.Button),
-    event: *wlr.Tablet.event.Button,
-) void {
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    const tablet: *Tablet = @fieldParentPtr("device", device);
-
-    device.seat.handleActivity();
-
-    const tool = TabletTool.get(device.seat.wlr_seat, event.tool) catch return;
-
-    tool.button(tablet, event);
-}
-
-pub fn updateState(cursor: *Cursor) void {
-    if (cursor.constraint) |constraint| {
-        constraint.updateState();
-    }
-
-    switch (cursor.mode) {
-        .passthrough, .drag => {
-            cursor.updateHovered();
-            cursor.passthrough(util.msecTimestamp());
-        },
-        .ignore, .down, .op => {},
-    }
-}
-
-/// Pass an event on to the surface under the cursor, if any.
-fn passthrough(cursor: *Cursor, time: u32) void {
-    assert(cursor.mode == .passthrough or cursor.mode == .drag);
-
-    if (server.scene.at(cursor.wlr_cursor.x, cursor.wlr_cursor.y)) |result| {
-        if (result.data == .lock_surface) {
-            assert(server.lock_manager.state != .unlocked);
-        } else {
-            assert(server.lock_manager.state != .locked);
-        }
-
-        if (result.surface) |surface| {
-            cursor.seat.wlr_seat.pointerNotifyEnter(surface, result.sx, result.sy);
-            cursor.seat.wlr_seat.pointerNotifyMotion(time, result.sx, result.sy);
-            return;
-        }
-    }
-
-    cursor.clearFocus();
-}
-
-fn updateDragIcons(cursor: *Cursor) void {
-    var it = server.scene.drag_icons.children.iterator(.forward);
-    while (it.next()) |node| {
-        const icon = @as(*DragIcon, @ptrCast(@alignCast(node.data)));
-
-        if (icon.wlr_drag_icon.drag.seat == cursor.seat.wlr_seat) {
-            icon.updatePosition(cursor);
-        }
-    }
-}
-
-fn queueMotionRelative(listener: *wl.Listener(*wlr.Pointer.event.Motion), event: *wlr.Pointer.event.Motion) void {
-    const cursor: *Cursor = @fieldParentPtr("motion_relative", listener);
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    cursor.seat.queueEvent(.{ .pointer_motion_relative = .{
-        .mapping = device.activeMapping(),
-        .time_msec = event.time_msec,
-        .delta_x = event.delta_x,
-        .delta_y = event.delta_y,
-        .unaccel_dx = event.unaccel_dx,
-        .unaccel_dy = event.unaccel_dy,
-    } }) catch {};
-}
-
-fn queueMotionAbsolute(listener: *wl.Listener(*wlr.Pointer.event.MotionAbsolute), event: *wlr.Pointer.event.MotionAbsolute) void {
-    const cursor: *Cursor = @fieldParentPtr("motion_absolute", listener);
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    cursor.seat.queueEvent(.{ .pointer_motion_absolute = .{
-        .mapping = device.activeMapping(),
-        .time_msec = event.time_msec,
-        .x = event.x,
-        .y = event.y,
-    } }) catch {};
-}
-
-fn queueButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.Pointer.event.Button) void {
-    const cursor: *Cursor = @fieldParentPtr("button", listener);
-    cursor.seat.queueEvent(.{ .pointer_button = .{
-        .time_msec = event.time_msec,
-        .button = event.button,
-        .state = event.state,
-    } }) catch {};
-}
-
-fn queueAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Pointer.event.Axis) void {
-    const cursor: *Cursor = @fieldParentPtr("axis", listener);
-    const device: *InputDevice = @ptrCast(@alignCast(event.device.data));
-    cursor.seat.queueEvent(.{ .pointer_axis = .{
-        .time_msec = event.time_msec,
-        .source = event.source,
-        .orientation = event.orientation,
-        .relative_direction = event.relative_direction,
-        .delta = event.delta * device.config.scroll_factor,
-        .delta_discrete = math.lossyCast(i32, @as(f64, @floatFromInt(event.delta_discrete)) * device.config.scroll_factor),
-    } }) catch {};
-}
-
-fn queueFrame(listener: *wl.Listener(*wlr.Cursor), _: *wlr.Cursor) void {
-    const cursor: *Cursor = @fieldParentPtr("frame", listener);
-    cursor.seat.queueEvent(.pointer_frame) catch {};
-}
-
-fn queuePinchBegin(listener: *wl.Listener(*wlr.Pointer.event.PinchBegin), event: *wlr.Pointer.event.PinchBegin) void {
-    const cursor: *Cursor = @fieldParentPtr("pinch_begin", listener);
-    cursor.seat.queueEvent(.{ .pointer_pinch_begin = .{
-        .time_msec = event.time_msec,
-        .fingers = event.fingers,
-    } }) catch {};
-}
-
-fn queuePinchUpdate(listener: *wl.Listener(*wlr.Pointer.event.PinchUpdate), event: *wlr.Pointer.event.PinchUpdate) void {
-    const cursor: *Cursor = @fieldParentPtr("pinch_update", listener);
-    cursor.seat.queueEvent(.{ .pointer_pinch_update = .{
-        .time_msec = event.time_msec,
-        .fingers = event.fingers,
-        .dx = event.dx,
-        .dy = event.dy,
-        .scale = event.scale,
-        .rotation = event.rotation,
-    } }) catch {};
-}
-
-fn queuePinchEnd(listener: *wl.Listener(*wlr.Pointer.event.PinchEnd), event: *wlr.Pointer.event.PinchEnd) void {
-    const cursor: *Cursor = @fieldParentPtr("pinch_end", listener);
-    cursor.seat.queueEvent(.{ .pointer_pinch_end = .{
-        .time_msec = event.time_msec,
-        .cancelled = event.cancelled,
-    } }) catch {};
-}
-
-fn queueSwipeBegin(listener: *wl.Listener(*wlr.Pointer.event.SwipeBegin), event: *wlr.Pointer.event.SwipeBegin) void {
-    const cursor: *Cursor = @fieldParentPtr("swipe_begin", listener);
-    cursor.seat.queueEvent(.{ .pointer_swipe_begin = .{
-        .time_msec = event.time_msec,
-        .fingers = event.fingers,
-    } }) catch {};
-}
-
-fn queueSwipeUpdate(listener: *wl.Listener(*wlr.Pointer.event.SwipeUpdate), event: *wlr.Pointer.event.SwipeUpdate) void {
-    const cursor: *Cursor = @fieldParentPtr("swipe_update", listener);
-    cursor.seat.queueEvent(.{ .pointer_swipe_update = .{
-        .time_msec = event.time_msec,
-        .fingers = event.fingers,
-        .dx = event.dx,
-        .dy = event.dy,
-    } }) catch {};
-}
-
-fn queueSwipeEnd(listener: *wl.Listener(*wlr.Pointer.event.SwipeEnd), event: *wlr.Pointer.event.SwipeEnd) void {
-    const cursor: *Cursor = @fieldParentPtr("swipe_end", listener);
-    cursor.seat.queueEvent(.{ .pointer_swipe_end = .{
-        .time_msec = event.time_msec,
-        .cancelled = event.cancelled,
-    } }) catch {};
-}
-
-fn queueHoldBegin(listener: *wl.Listener(*wlr.Pointer.event.HoldBegin), event: *wlr.Pointer.event.HoldBegin) void {
-    const cursor: *Cursor = @fieldParentPtr("hold_begin", listener);
-    cursor.seat.queueEvent(.{ .pointer_hold_begin = .{
-        .time_msec = event.time_msec,
-        .fingers = event.fingers,
-    } }) catch {};
-}
-
-fn queueHoldEnd(listener: *wl.Listener(*wlr.Pointer.event.HoldEnd), event: *wlr.Pointer.event.HoldEnd) void {
-    const cursor: *Cursor = @fieldParentPtr("hold_end", listener);
-    cursor.seat.queueEvent(.{ .pointer_hold_end = .{
-        .time_msec = event.time_msec,
-        .cancelled = event.cancelled,
-    } }) catch {};
-}
diff --git a/river/Decoration.zig b/river/Decoration.zig
deleted file mode 100644
index 34538d0..0000000
--- a/river/Decoration.zig
+++ /dev/null
@@ -1,165 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const Decoration = @This();
-
-const build_options = @import("build_options");
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const river = @import("wayland").server.river;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Scene = @import("Scene.zig");
-
-const log = std.log.scoped(.wm);
-
-const role: wlr.Surface.Role = .{
-    .name = "river_decoration_v1",
-    .client_commit = clientCommit,
-    .commit = commit,
-    .unmap = null,
-    .destroy = null,
-};
-
-object: ?*river.DecorationV1,
-surface: *wlr.Surface,
-tree: *wlr.SceneTree,
-surfaces: Scene.SaveableSurfaces,
-/// Window.decorations_above/below
-link: wl.list.Link,
-
-rendering_requested: struct {
-    offset_x: i32 = 0,
-    offset_y: i32 = 0,
-    sync_next_commit: bool = false,
-} = .{},
-
-pub fn create(
-    client: *wl.Client,
-    version: u32,
-    id: u32,
-    surface: *wlr.Surface,
-    parent: *wlr.SceneTree,
-) !*Decoration {
-    const decoration_v1 = try river.DecorationV1.create(client, version, id);
-
-    if (!surface.setRole(&role, @ptrCast(decoration_v1), @intFromEnum(river.WindowManagerV1.Error.role))) {
-        return error.AlreadyHasRole;
-    }
-    surface.setRoleObject(@ptrCast(decoration_v1));
-
-    const decoration = try util.gpa.create(Decoration);
-    errdefer util.gpa.destroy(decoration);
-
-    const tree = try parent.createSceneTree();
-    errdefer tree.node.destroy();
-
-    const surfaces = try Scene.SaveableSurfaces.init(tree);
-    _ = try surfaces.tree.createSceneSubsurfaceTree(surface);
-
-    decoration.* = .{
-        .object = decoration_v1,
-        .surface = surface,
-        .tree = tree,
-        .surfaces = surfaces,
-        .link = undefined,
-    };
-
-    decoration_v1.setHandler(*Decoration, handleRequest, handleDestroy, decoration);
-
-    return decoration;
-}
-
-pub fn destroy(decoration: *Decoration) void {
-    assert(decoration.object == null);
-    decoration.tree.node.destroy();
-    decoration.link.remove();
-    util.gpa.destroy(decoration);
-}
-
-pub fn makeInert(decoration: *Decoration) void {
-    if (decoration.object) |object| {
-        object.setHandler(?*anyopaque, handleRequestInert, null, null);
-        decoration.object = null;
-    }
-    decoration.surfaces.save();
-}
-
-fn handleRequestInert(
-    node_v1: *river.DecorationV1,
-    request: river.DecorationV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) node_v1.destroy();
-}
-
-fn handleDestroy(_: *river.DecorationV1, decoration: *Decoration) void {
-    decoration.object = null;
-    decoration.destroy();
-}
-
-fn handleRequest(
-    decoration_v1: *river.DecorationV1,
-    request: river.DecorationV1.Request,
-    decoration: *Decoration,
-) void {
-    assert(decoration.object == decoration_v1);
-    switch (request) {
-        .destroy => decoration_v1.destroy(),
-        .set_offset => |args| {
-            if (!server.wm.ensureRendering()) return;
-            decoration.rendering_requested.offset_x = args.x;
-            decoration.rendering_requested.offset_y = args.y;
-        },
-        .sync_next_commit => {
-            if (!server.wm.ensureRendering()) return;
-            decoration.rendering_requested.sync_next_commit = true;
-        },
-    }
-}
-
-fn clientCommit(wlr_surface: *wlr.Surface) callconv(.c) void {
-    if (wlr_surface.role != &role) return;
-    const resource = wlr_surface.role_resource orelse return;
-    const decoration: *Decoration = @ptrCast(@alignCast(resource.getUserData()));
-    if (decoration.rendering_requested.sync_next_commit) {
-        decoration.surfaces.save();
-    }
-}
-
-fn commit(wlr_surface: *wlr.Surface) callconv(.c) void {
-    if (wlr_surface.hasBuffer()) {
-        wlr_surface.map();
-    }
-}
-
-pub fn renderFinish(decoration: *Decoration, window_clip: *const wlr.Box) void {
-    const rendering_requested = &decoration.rendering_requested;
-    if (rendering_requested.sync_next_commit) {
-        rendering_requested.sync_next_commit = false;
-
-        if (!decoration.surfaces.saved) {
-            if (decoration.object) |object| {
-                object.postError(.no_commit,
-                    \\no wl_surface.commit after sync_next_commit and before update_rendering_finish
-                );
-            }
-        }
-    }
-
-    decoration.surfaces.dropSaved();
-
-    decoration.tree.node.setPosition(rendering_requested.offset_x, rendering_requested.offset_y);
-
-    // wlroots asserts that a subsurface tree is present.
-    if (!decoration.surfaces.tree.children.empty()) {
-        var clip = window_clip.*;
-        clip.x -= rendering_requested.offset_x;
-        clip.y -= rendering_requested.offset_y;
-        decoration.surfaces.tree.node.subsurfaceTreeSetClip(&clip);
-    }
-}
diff --git a/river/DragIcon.zig b/river/DragIcon.zig
deleted file mode 100644
index afcb90d..0000000
--- a/river/DragIcon.zig
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const DragIcon = @This();
-
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Cursor = @import("Cursor.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-
-wlr_drag_icon: *wlr.Drag.Icon,
-scene_drag_icon: *wlr.SceneTree,
-
-destroy: wl.Listener(*wlr.Drag.Icon) = .init(handleDestroy),
-
-pub fn create(wlr_drag_icon: *wlr.Drag.Icon, cursor: *Cursor) error{OutOfMemory}!void {
-    const scene_drag_icon = try server.scene.drag_icons.createSceneDragIcon(wlr_drag_icon);
-    errdefer scene_drag_icon.node.destroy();
-
-    const drag_icon = try util.gpa.create(DragIcon);
-    errdefer util.gpa.destroy(drag_icon);
-
-    drag_icon.* = .{
-        .wlr_drag_icon = wlr_drag_icon,
-        .scene_drag_icon = scene_drag_icon,
-    };
-    scene_drag_icon.node.data = drag_icon;
-
-    drag_icon.updatePosition(cursor);
-
-    wlr_drag_icon.events.destroy.add(&drag_icon.destroy);
-}
-
-pub fn updatePosition(drag_icon: *DragIcon, cursor: *Cursor) void {
-    switch (drag_icon.wlr_drag_icon.drag.grab_type) {
-        .keyboard => unreachable,
-        .keyboard_pointer => {
-            drag_icon.scene_drag_icon.node.setPosition(
-                @intFromFloat(cursor.wlr_cursor.x),
-                @intFromFloat(cursor.wlr_cursor.y),
-            );
-        },
-        .keyboard_touch => {
-            const touch_id = drag_icon.wlr_drag_icon.drag.touch_id;
-            if (cursor.touch_points.get(touch_id)) |point| {
-                drag_icon.scene_drag_icon.node.setPosition(
-                    @intFromFloat(point.lx),
-                    @intFromFloat(point.ly),
-                );
-            }
-        },
-    }
-}
-
-fn handleDestroy(listener: *wl.Listener(*wlr.Drag.Icon), _: *wlr.Drag.Icon) void {
-    const drag_icon: *DragIcon = @fieldParentPtr("destroy", listener);
-
-    drag_icon.destroy.link.remove();
-
-    util.gpa.destroy(drag_icon);
-}
diff --git a/river/IdleInhibitManager.zig b/river/IdleInhibitManager.zig
deleted file mode 100644
index 2f1dee8..0000000
--- a/river/IdleInhibitManager.zig
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-FileCopyrightText: © 2022 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const IdleInhibitManager = @This();
-
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const IdleInhibitor = @import("IdleInhibitor.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-const Window = @import("Window.zig");
-
-wlr_manager: *wlr.IdleInhibitManagerV1,
-new_idle_inhibitor: wl.Listener(*wlr.IdleInhibitorV1) = .init(handleNewIdleInhibitor),
-inhibitors: wl.list.Head(IdleInhibitor, .link),
-
-pub fn init(inhibit_manager: *IdleInhibitManager) !void {
-    inhibit_manager.* = .{
-        .wlr_manager = try wlr.IdleInhibitManagerV1.create(server.wl_server),
-        .inhibitors = undefined,
-    };
-    inhibit_manager.inhibitors.init();
-
-    inhibit_manager.wlr_manager.events.new_inhibitor.add(&inhibit_manager.new_idle_inhibitor);
-}
-
-pub fn deinit(inhibit_manager: *IdleInhibitManager) void {
-    while (inhibit_manager.inhibitors.first()) |inhibitor| {
-        inhibitor.destroy();
-    }
-    inhibit_manager.new_idle_inhibitor.link.remove();
-}
-
-pub fn checkActive(inhibit_manager: *IdleInhibitManager) void {
-    var inhibited = false;
-    var it = inhibit_manager.inhibitors.iterator(.forward);
-    while (it.next()) |inhibitor| {
-        const node_data = SceneNodeData.fromSurface(inhibitor.wlr_inhibitor.surface) orelse continue;
-        switch (node_data.data) {
-            // TODO be stricter here so that clients don't abuse the protocol
-            .window, .shell_surface, .lock_surface, .layer_surface, .override_redirect => {
-                inhibited = true;
-                break;
-            },
-        }
-    }
-
-    server.input_manager.idle_notifier.setInhibited(inhibited);
-}
-
-fn handleNewIdleInhibitor(listener: *wl.Listener(*wlr.IdleInhibitorV1), inhibitor: *wlr.IdleInhibitorV1) void {
-    const inhibit_manager: *IdleInhibitManager = @fieldParentPtr("new_idle_inhibitor", listener);
-    IdleInhibitor.create(inhibitor, inhibit_manager) catch {
-        std.log.err("out of memory", .{});
-        return;
-    };
-}
diff --git a/river/IdleInhibitor.zig b/river/IdleInhibitor.zig
deleted file mode 100644
index 191d690..0000000
--- a/river/IdleInhibitor.zig
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-FileCopyrightText: © 2022 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const IdleInhibitor = @This();
-
-const std = @import("std");
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const IdleInhibitManager = @import("IdleInhibitManager.zig");
-
-inhibit_manager: *IdleInhibitManager,
-wlr_inhibitor: *wlr.IdleInhibitorV1,
-
-listen_destroy: wl.Listener(*wlr.Surface) = .init(handleDestroy),
-
-link: wl.list.Link,
-
-pub fn create(wlr_inhibitor: *wlr.IdleInhibitorV1, inhibit_manager: *IdleInhibitManager) !void {
-    const inhibitor = try util.gpa.create(IdleInhibitor);
-    errdefer util.gpa.destroy(inhibitor);
-
-    inhibitor.* = .{
-        .inhibit_manager = inhibit_manager,
-        .wlr_inhibitor = wlr_inhibitor,
-        .link = undefined,
-    };
-    wlr_inhibitor.events.destroy.add(&inhibitor.listen_destroy);
-
-    inhibit_manager.inhibitors.append(inhibitor);
-
-    inhibit_manager.checkActive();
-}
-
-pub fn destroy(inhibitor: *IdleInhibitor) void {
-    inhibitor.listen_destroy.link.remove();
-
-    inhibitor.link.remove();
-
-    inhibitor.inhibit_manager.checkActive();
-
-    util.gpa.destroy(inhibitor);
-}
-
-fn handleDestroy(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
-    const inhibitor: *IdleInhibitor = @fieldParentPtr("listen_destroy", listener);
-
-    inhibitor.destroy();
-}
diff --git a/river/InputDevice.zig b/river/InputDevice.zig
deleted file mode 100644
index 63e68bd..0000000
--- a/river/InputDevice.zig
+++ /dev/null
@@ -1,298 +0,0 @@
-// SPDX-FileCopyrightText: © 2022 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const InputDevice = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const mem = std.mem;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const river = @import("wayland").server.river;
-
-const c = @import("c");
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Keyboard = @import("Keyboard.zig");
-const LibinputDevice = @import("LibinputDevice.zig");
-const Seat = @import("Seat.zig");
-const Tablet = @import("Tablet.zig");
-const XkbKeyboard = @import("XkbKeyboard.zig");
-
-const log = std.log.scoped(.input);
-
-seat: *Seat,
-wlr_device: *wlr.InputDevice,
-virtual: bool,
-objects: wl.list.Head(river.InputDeviceV1, null),
-
-libinput: LibinputDevice,
-xkb_keyboard: XkbKeyboard,
-
-remove: wl.Listener(*wlr.InputDevice) = .init(handleRemove),
-
-config: struct {
-    scroll_factor: f64 = 1.0,
-    map_to_output: ?*wlr.Output = null,
-    map_to_rectangle: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
-} = .{},
-
-/// InputManager.devices
-link: wl.list.Link,
-
-pub fn init(
-    device: *InputDevice,
-    seat: *Seat,
-    wlr_device: *wlr.InputDevice,
-    virtual: bool,
-) !void {
-    device.* = .{
-        .seat = seat,
-        .wlr_device = wlr_device,
-        .virtual = virtual,
-        .libinput = undefined,
-        .xkb_keyboard = undefined,
-        .objects = undefined,
-        .link = undefined,
-    };
-    device.objects.init();
-    server.input_manager.devices.append(device);
-
-    wlr_device.data = device;
-    wlr_device.events.destroy.add(&device.remove);
-
-    log.debug("new {s}input device: {s}-{s}", .{
-        if (virtual) "virtual " else "",
-        @tagName(wlr_device.type),
-        wlr_device.name orelse "unknown",
-    });
-
-    if (!virtual) {
-        var it = server.input_manager.objects.safeIterator(.forward);
-        while (it.next()) |im_v1| {
-            device.createObject(im_v1);
-        }
-        if (wlr_device.getLibinputDevice()) |handle| {
-            device.libinput.init(@ptrCast(handle));
-        }
-        if (wlr_device.type == .keyboard) {
-            device.xkb_keyboard.init();
-        }
-    }
-
-    // The wlroots Wayland and X11 backends support multiple outputs
-    // exposed as multiple windows in the host session. However, this
-    // requires mapping pointer/touch devices to the outputs suggested
-    // by the backend to make input work as expected.
-    if (switch (wlr_device.type) {
-        .pointer => wlr_device.toPointer().output_name,
-        .touch => wlr_device.toTouch().output_name,
-        else => null,
-    }) |output_name| {
-        var it = server.om.outputs.iterator(.forward);
-        while (it.next()) |output| {
-            const wlr_output = output.wlr_output orelse continue;
-            if (mem.orderZ(u8, output_name, wlr_output.name) == .eq) {
-                device.config.map_to_output = wlr_output;
-                break;
-            }
-        }
-    }
-}
-
-pub fn createObject(device: *InputDevice, im_v1: *river.InputManagerV1) void {
-    assert(!device.virtual);
-    const device_type: river.InputDeviceV1.Type = switch (device.wlr_device.type) {
-        .keyboard => .keyboard,
-        .pointer => .pointer,
-        .touch => .touch,
-        .tablet => .tablet,
-        .@"switch", .tablet_pad => return,
-    };
-    const object = river.InputDeviceV1.create(im_v1.getClient(), im_v1.getVersion(), 0) catch {
-        log.err("out of memory", .{});
-        im_v1.postNoMemory();
-        return;
-    };
-    im_v1.sendInputDevice(object);
-    device.objects.append(object);
-    object.setHandler(*InputDevice, handleRequest, handleDestroy, device);
-    object.sendType(device_type);
-    object.sendName(device.wlr_device.name orelse "");
-    if (object.getVersion() >= 2) {
-        object.sendDone();
-    }
-}
-
-pub fn deinit(device: *InputDevice) void {
-    if (!device.virtual) {
-        {
-            var it = device.objects.iterator(.forward);
-            while (it.next()) |object| {
-                object.getLink().remove();
-                object.sendRemoved();
-                object.setHandler(?*anyopaque, handleRequestInert, null, null);
-            }
-        }
-        if (device.wlr_device.getLibinputDevice() != null) {
-            device.libinput.deinit();
-        }
-        if (device.wlr_device.type == .keyboard) {
-            device.xkb_keyboard.deinit();
-        }
-    }
-
-    device.remove.link.remove();
-    device.link.remove();
-    device.seat.updateCapabilities();
-
-    device.wlr_device.data = null;
-
-    device.* = undefined;
-}
-
-pub fn assignToSeat(device: *InputDevice, new: *Seat) void {
-    const old = device.seat;
-    if (new == old) return;
-    old.detachDevice(device);
-    new.attachDevice(device);
-    old.updateCapabilities();
-    new.updateCapabilities();
-}
-
-/// Retuns the curretly active mapping for the device, or an empty box if
-/// the movement of the device is unrestricted.
-pub fn activeMapping(device: *const InputDevice) wlr.Box {
-    var mapping = device.config.map_to_rectangle;
-    if (!mapping.empty()) {
-        return mapping;
-    }
-    if (device.config.map_to_output) |output| {
-        server.om.output_layout.getBox(output, &mapping);
-    }
-    return mapping;
-}
-
-fn handleRemove(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void {
-    const device: *InputDevice = @fieldParentPtr("remove", listener);
-
-    log.debug("removed input device: {s}-{s}", .{
-        @tagName(device.wlr_device.type),
-        device.wlr_device.name orelse "unknown",
-    });
-
-    switch (device.wlr_device.type) {
-        .keyboard => {
-            const keyboard: *Keyboard = @fieldParentPtr("device", device);
-            keyboard.deviceDestroy();
-        },
-        .pointer, .touch => {
-            device.deinit();
-            util.gpa.destroy(device);
-        },
-        .tablet => {
-            const tablet: *Tablet = @fieldParentPtr("device", device);
-            tablet.destroy();
-        },
-        .@"switch", .tablet_pad => unreachable,
-    }
-}
-
-fn handleRequestInert(
-    object: *river.InputDeviceV1,
-    request: river.InputDeviceV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) object.destroy();
-}
-
-fn handleDestroy(object: *river.InputDeviceV1, _: *InputDevice) void {
-    object.getLink().remove();
-}
-
-fn handleRequest(
-    object: *river.InputDeviceV1,
-    request: river.InputDeviceV1.Request,
-    device: *InputDevice,
-) void {
-    switch (request) {
-        .destroy => object.destroy(),
-        .assign_to_seat => |args| {
-            var it = server.input_manager.seats.iterator(.forward);
-            while (it.next()) |seat| {
-                if (mem.orderZ(u8, args.name, seat.wlr_seat.name) == .eq) {
-                    device.assignToSeat(seat);
-                }
-            }
-            log.info("client requested input device be assigned to non-existant seat '{s}'", .{args.name});
-        },
-        .set_repeat_info => |args| {
-            if (args.rate < 0 or args.delay < 0) {
-                object.postError(.invalid_repeat_info, "negative rate/delay");
-                return;
-            }
-            if (device.wlr_device.type == .keyboard) {
-                const keyboard: *Keyboard = @fieldParentPtr("device", device);
-                keyboard.setRepeatInfo(@intCast(args.rate), @intCast(args.delay));
-            }
-        },
-        .set_scroll_factor => |args| {
-            const factor = args.factor.toDouble();
-            if (factor < 0) {
-                object.postError(.invalid_scroll_factor, "negative scroll factor");
-                return;
-            }
-            device.config.scroll_factor = factor;
-        },
-        .map_to_output => |args| {
-            switch (device.wlr_device.type) {
-                .pointer, .touch, .tablet => {},
-                .keyboard, .@"switch", .tablet_pad => return,
-            }
-            if (args.output) |output| {
-                device.config.map_to_output = wlr.Output.fromWlOutput(output) orelse return;
-            } else {
-                device.config.map_to_output = null;
-            }
-            switch (device.wlr_device.type) {
-                .touch, .tablet => {
-                    device.seat.cursor.wlr_cursor.mapInputToOutput(
-                        device.wlr_device,
-                        device.config.map_to_output,
-                    );
-                },
-                // River implements pointer mappings without help from wlroots
-                .pointer => {},
-                .keyboard, .@"switch", .tablet_pad => unreachable,
-            }
-        },
-        .map_to_rectangle => |args| {
-            if (args.width < 0 or args.height < 0) {
-                object.postError(.invalid_map_to_rectangle, "negative rectangle width/height");
-                return;
-            }
-            switch (device.wlr_device.type) {
-                .pointer, .touch, .tablet => {},
-                .keyboard, .@"switch", .tablet_pad => return,
-            }
-            device.config.map_to_rectangle = .{
-                .x = args.x,
-                .y = args.y,
-                .width = args.width,
-                .height = args.height,
-            };
-            switch (device.wlr_device.type) {
-                .touch, .tablet => {
-                    device.seat.cursor.wlr_cursor.mapInputToRegion(
-                        device.wlr_device,
-                        &device.config.map_to_rectangle,
-                    );
-                },
-                // River implements pointer mappings without help from wlroots
-                .pointer => {},
-                .keyboard, .@"switch", .tablet_pad => unreachable,
-            }
-        },
-    }
-}
diff --git a/river/InputManager.zig b/river/InputManager.zig
deleted file mode 100644
index 960b36e..0000000
--- a/river/InputManager.zig
+++ /dev/null
@@ -1,287 +0,0 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const InputManager = @This();
-
-const build_options = @import("build_options");
-const std = @import("std");
-const assert = std.debug.assert;
-const mem = std.mem;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const river = @import("wayland").server.river;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const InputDevice = @import("InputDevice.zig");
-const InputRelay = @import("InputRelay.zig");
-const Keyboard = @import("Keyboard.zig");
-const PointerConstraint = @import("PointerConstraint.zig");
-const Seat = @import("Seat.zig");
-const TextInput = @import("TextInput.zig");
-
-const default_seat_name = "default";
-
-const log = std.log.scoped(.input);
-
-global: *wl.Global,
-objects: wl.list.Head(river.InputManagerV1, null),
-
-new_input: wl.Listener(*wlr.InputDevice) = .init(handleNewInput),
-
-idle_notifier: *wlr.IdleNotifierV1,
-relative_pointer_manager: *wlr.RelativePointerManagerV1,
-pointer_gestures: *wlr.PointerGesturesV1,
-virtual_pointer_manager: *wlr.VirtualPointerManagerV1,
-virtual_keyboard_manager: *wlr.VirtualKeyboardManagerV1,
-pointer_constraints: *wlr.PointerConstraintsV1,
-input_method_manager: *wlr.InputMethodManagerV2,
-text_input_manager: *wlr.TextInputManagerV3,
-tablet_manager: *wlr.TabletManagerV2,
-
-devices: wl.list.Head(InputDevice, .link),
-seats: wl.list.Head(Seat, .link),
-
-new_virtual_pointer: wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer) = .init(handleNewVirtualPointer),
-new_virtual_keyboard: wl.Listener(*wlr.VirtualKeyboardV1) = .init(handleNewVirtualKeyboard),
-new_constraint: wl.Listener(*wlr.PointerConstraintV1) = .init(handleNewConstraint),
-new_input_method: wl.Listener(*wlr.InputMethodV2) = .init(handleNewInputMethod),
-new_text_input: wl.Listener(*wlr.TextInputV3) = .init(handleNewTextInput),
-
-pub fn init(input_manager: *InputManager) !void {
-    input_manager.* = .{
-        .global = try wl.Global.create(server.wl_server, river.InputManagerV1, 2, *InputManager, input_manager, bind),
-        // These are automatically freed when the display is destroyed
-        .idle_notifier = try wlr.IdleNotifierV1.create(server.wl_server),
-        .relative_pointer_manager = try wlr.RelativePointerManagerV1.create(server.wl_server),
-        .pointer_gestures = try wlr.PointerGesturesV1.create(server.wl_server),
-        .virtual_pointer_manager = try wlr.VirtualPointerManagerV1.create(server.wl_server),
-        .virtual_keyboard_manager = try wlr.VirtualKeyboardManagerV1.create(server.wl_server),
-        .pointer_constraints = try wlr.PointerConstraintsV1.create(server.wl_server),
-        .input_method_manager = try wlr.InputMethodManagerV2.create(server.wl_server),
-        .text_input_manager = try wlr.TextInputManagerV3.create(server.wl_server),
-        .tablet_manager = try wlr.TabletManagerV2.create(server.wl_server),
-
-        .objects = undefined,
-        .devices = undefined,
-        .seats = undefined,
-    };
-    input_manager.objects.init();
-    input_manager.devices.init();
-    input_manager.seats.init();
-
-    try Seat.create(default_seat_name);
-
-    if (build_options.xwayland) {
-        if (server.xwayland) |xwayland| {
-            xwayland.setSeat(input_manager.defaultSeat().wlr_seat);
-        }
-    }
-
-    server.backend.events.new_input.add(&input_manager.new_input);
-    input_manager.virtual_pointer_manager.events.new_virtual_pointer.add(&input_manager.new_virtual_pointer);
-    input_manager.virtual_keyboard_manager.events.new_virtual_keyboard.add(&input_manager.new_virtual_keyboard);
-    input_manager.pointer_constraints.events.new_constraint.add(&input_manager.new_constraint);
-    input_manager.input_method_manager.events.new_input_method.add(&input_manager.new_input_method);
-    input_manager.text_input_manager.events.new_text_input.add(&input_manager.new_text_input);
-}
-
-pub fn deinit(input_manager: *InputManager) void {
-    input_manager.global.destroy();
-
-    // This function must be called after the backend has been destroyed
-    assert(input_manager.objects.empty());
-    assert(input_manager.devices.empty());
-
-    input_manager.new_virtual_pointer.link.remove();
-    input_manager.new_virtual_keyboard.link.remove();
-    input_manager.new_constraint.link.remove();
-    input_manager.new_input_method.link.remove();
-    input_manager.new_text_input.link.remove();
-
-    while (input_manager.seats.first()) |seat| {
-        seat.destroy();
-    }
-}
-
-fn bind(client: *wl.Client, im: *InputManager, version: u32, id: u32) void {
-    const im_v1 = river.InputManagerV1.create(client, version, id) catch {
-        client.postNoMemory();
-        log.err("out of memory", .{});
-        return;
-    };
-    im_v1.setHandler(*InputManager, handleRequest, handleDestroy, im);
-    im.objects.append(im_v1);
-    {
-        var it = im.devices.iterator(.forward);
-        while (it.next()) |device| {
-            if (!device.virtual) {
-                device.createObject(im_v1);
-            }
-        }
-    }
-}
-
-fn handleRequestInert(
-    im_v1: *river.InputManagerV1,
-    request: river.InputManagerV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) im_v1.destroy();
-}
-
-fn handleDestroy(im_v1: *river.InputManagerV1, _: *InputManager) void {
-    im_v1.getLink().remove();
-}
-
-fn handleRequest(
-    im_v1: *river.InputManagerV1,
-    request: river.InputManagerV1.Request,
-    im: *InputManager,
-) void {
-    switch (request) {
-        .stop => {
-            im_v1.getLink().remove();
-            im_v1.sendFinished();
-            im_v1.setHandler(?*anyopaque, handleRequestInert, null, null);
-        },
-        .destroy => {
-            im_v1.postError(.invalid_destroy, "destroy before finished event sent");
-        },
-        .create_seat => |args| {
-            var it = im.seats.iterator(.forward);
-            while (it.next()) |seat| {
-                if (mem.orderZ(u8, args.name, seat.wlr_seat.name) == .eq) {
-                    break;
-                }
-            } else {
-                Seat.create(args.name) catch |err| switch (err) {
-                    error.OutOfMemory => {
-                        im_v1.getClient().postNoMemory();
-                        log.err("out of memory", .{});
-                        return;
-                    },
-                };
-            }
-        },
-        .destroy_seat => |args| {
-            var it = im.seats.iterator(.forward);
-            _ = it.next(); // skip default seat
-            while (it.next()) |seat| {
-                if (mem.orderZ(u8, args.name, seat.wlr_seat.name) == .eq) {
-                    seat.destroying = true;
-                    server.wm.dirtyWindowing();
-                    break;
-                }
-            }
-        },
-    }
-}
-
-pub fn defaultSeat(input_manager: *InputManager) *Seat {
-    return input_manager.seats.first().?;
-}
-
-pub fn processEvents(input_manager: *InputManager) void {
-    assert(server.wm.state == .idle);
-
-    var it = input_manager.seats.iterator(.forward);
-    while (it.next()) |seat| {
-        seat.processEvents();
-    }
-}
-
-fn handleNewInput(listener: *wl.Listener(*wlr.InputDevice), wlr_device: *wlr.InputDevice) void {
-    const input_manager: *InputManager = @fieldParentPtr("new_input", listener);
-
-    input_manager.defaultSeat().attachNewDevice(wlr_device, false);
-}
-
-fn handleNewVirtualPointer(
-    listener: *wl.Listener(*wlr.VirtualPointerManagerV1.event.NewPointer),
-    event: *wlr.VirtualPointerManagerV1.event.NewPointer,
-) void {
-    const input_manager: *InputManager = @fieldParentPtr("new_virtual_pointer", listener);
-
-    // TODO Support multiple seats and don't ignore
-    if (event.suggested_seat != null) {
-        log.debug("Ignoring seat suggestion from virtual pointer", .{});
-    }
-    // TODO dont ignore output suggestion
-    if (event.suggested_output != null) {
-        log.debug("Ignoring output suggestion from virtual pointer", .{});
-    }
-
-    input_manager.defaultSeat().attachNewDevice(&event.new_pointer.pointer.base, true);
-}
-
-fn handleNewVirtualKeyboard(
-    _: *wl.Listener(*wlr.VirtualKeyboardV1),
-    virtual_keyboard: *wlr.VirtualKeyboardV1,
-) void {
-    const no_keymap = util.gpa.create(NoKeymapVirtKeyboard) catch {
-        log.err("out of memory", .{});
-        return;
-    };
-    errdefer util.gpa.destroy(no_keymap);
-
-    no_keymap.* = .{
-        .virtual_keyboard = virtual_keyboard,
-    };
-    virtual_keyboard.keyboard.base.events.destroy.add(&no_keymap.destroy);
-    virtual_keyboard.keyboard.events.keymap.add(&no_keymap.keymap);
-}
-
-/// Ignore virtual keyboards completely until the client sets a keymap
-/// Yes, wlroots should probably do this for us.
-const NoKeymapVirtKeyboard = struct {
-    virtual_keyboard: *wlr.VirtualKeyboardV1,
-    destroy: wl.Listener(*wlr.InputDevice) = .init(handleVirtKeyboardDestroy),
-    keymap: wl.Listener(*wlr.Keyboard) = .init(handleKeymap),
-
-    fn handleVirtKeyboardDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice) void {
-        const no_keymap: *NoKeymapVirtKeyboard = @fieldParentPtr("destroy", listener);
-
-        no_keymap.destroy.link.remove();
-        no_keymap.keymap.link.remove();
-
-        util.gpa.destroy(no_keymap);
-    }
-
-    fn handleKeymap(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
-        const no_keymap: *NoKeymapVirtKeyboard = @fieldParentPtr("keymap", listener);
-        const virtual_keyboard = no_keymap.virtual_keyboard;
-
-        handleVirtKeyboardDestroy(&no_keymap.destroy, &virtual_keyboard.keyboard.base);
-
-        const seat: *Seat = @ptrCast(@alignCast(virtual_keyboard.seat.data));
-        seat.attachNewDevice(&virtual_keyboard.keyboard.base, true);
-    }
-};
-
-fn handleNewConstraint(
-    _: *wl.Listener(*wlr.PointerConstraintV1),
-    wlr_constraint: *wlr.PointerConstraintV1,
-) void {
-    PointerConstraint.create(wlr_constraint) catch {
-        log.err("out of memory", .{});
-        wlr_constraint.resource.postNoMemory();
-    };
-}
-
-fn handleNewInputMethod(_: *wl.Listener(*wlr.InputMethodV2), input_method: *wlr.InputMethodV2) void {
-    const seat: *Seat = @ptrCast(@alignCast(input_method.seat.data));
-
-    log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
-
-    seat.relay.newInputMethod(input_method);
-}
-
-fn handleNewTextInput(_: *wl.Listener(*wlr.TextInputV3), wlr_text_input: *wlr.TextInputV3) void {
-    TextInput.create(wlr_text_input) catch {
-        log.err("out of memory", .{});
-        wlr_text_input.resource.postNoMemory();
-        return;
-    };
-}
diff --git a/river/InputPopup.zig b/river/InputPopup.zig
deleted file mode 100644
index c7c5dc2..0000000
--- a/river/InputPopup.zig
+++ /dev/null
@@ -1,173 +0,0 @@
-// SPDX-FileCopyrightText: © 2024 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const InputPopup = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const InputRelay = @import("InputRelay.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-
-link: wl.list.Link,
-input_relay: *InputRelay,
-
-wlr_popup: *wlr.InputPopupSurfaceV2,
-surface_tree: *wlr.SceneTree,
-
-destroy: wl.Listener(void) = .init(handleDestroy),
-map: wl.Listener(void) = .init(handleMap),
-unmap: wl.Listener(void) = .init(handleUnmap),
-commit: wl.Listener(*wlr.Surface) = .init(handleCommit),
-
-pub fn create(wlr_popup: *wlr.InputPopupSurfaceV2, input_relay: *InputRelay) !void {
-    const input_popup = try util.gpa.create(InputPopup);
-    errdefer util.gpa.destroy(input_popup);
-
-    input_popup.* = .{
-        .link = undefined,
-        .input_relay = input_relay,
-        .wlr_popup = wlr_popup,
-        .surface_tree = try server.scene.hidden_tree.createSceneSubsurfaceTree(wlr_popup.surface),
-    };
-
-    input_relay.input_popups.append(input_popup);
-
-    input_popup.wlr_popup.events.destroy.add(&input_popup.destroy);
-    input_popup.wlr_popup.surface.events.map.add(&input_popup.map);
-    input_popup.wlr_popup.surface.events.unmap.add(&input_popup.unmap);
-    input_popup.wlr_popup.surface.events.commit.add(&input_popup.commit);
-
-    input_popup.update();
-}
-
-fn handleDestroy(listener: *wl.Listener(void)) void {
-    const input_popup: *InputPopup = @fieldParentPtr("destroy", listener);
-
-    input_popup.destroy.link.remove();
-    input_popup.map.link.remove();
-    input_popup.unmap.link.remove();
-    input_popup.commit.link.remove();
-
-    input_popup.link.remove();
-
-    util.gpa.destroy(input_popup);
-}
-
-fn handleMap(listener: *wl.Listener(void)) void {
-    const input_popup: *InputPopup = @fieldParentPtr("map", listener);
-
-    input_popup.update();
-}
-
-fn handleUnmap(listener: *wl.Listener(void)) void {
-    const input_popup: *InputPopup = @fieldParentPtr("unmap", listener);
-
-    input_popup.surface_tree.node.reparent(server.scene.hidden_tree);
-}
-
-fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
-    const input_popup: *InputPopup = @fieldParentPtr("commit", listener);
-
-    input_popup.update();
-}
-
-pub fn update(input_popup: *InputPopup) void {
-    const text_input = input_popup.input_relay.text_input orelse {
-        input_popup.surface_tree.node.reparent(server.scene.hidden_tree);
-        return;
-    };
-
-    if (!input_popup.wlr_popup.surface.mapped) return;
-
-    // This seems like it could be null if the focused surface is destroyed
-    const focused_surface = text_input.wlr_text_input.focused_surface orelse return;
-
-    // Focus should never be sent to subsurfaces
-    assert(focused_surface.getRootSurface() == focused_surface);
-
-    const focused = SceneNodeData.fromSurface(focused_surface) orelse return;
-
-    const popup_tree = switch (focused.data) {
-        .window => |window| window.popup_tree,
-        .shell_surface => |shell_surface| shell_surface.popup_tree,
-        // TODO fix positioning for lock surfaces not at 0,0 in layout coords
-        .lock_surface => server.scene.layers.popups,
-        .layer_surface => |layer_surface| layer_surface.popup_tree,
-        // Xwayland doesn't use the text-input protocol
-        .override_redirect => unreachable,
-    };
-
-    input_popup.surface_tree.node.reparent(popup_tree);
-
-    if (!text_input.wlr_text_input.current.features.cursor_rectangle) {
-        // If the text-input client does not inform us where in the surface
-        // the active text input is there's not much we can do. Placing the
-        // popup at the top left corner of the window is nice and simple
-        // while not looking terrible.
-        input_popup.surface_tree.node.setPosition(0, 0);
-        return;
-    }
-
-    var focused_x: c_int = undefined;
-    var focused_y: c_int = undefined;
-    _ = focused.node.coords(&focused_x, &focused_y);
-
-    // Relative to the surface with the active text input
-    var cursor_box = text_input.wlr_text_input.current.cursor_rectangle;
-
-    const wlr_output = server.om.outputAt(
-        @floatFromInt(focused_x + cursor_box.x),
-        @floatFromInt(focused_y + cursor_box.y),
-    ) orelse return;
-
-    var output_box: wlr.Box = undefined;
-    server.om.output_layout.getBox(wlr_output, &output_box);
-
-    // Adjust to be relative to the output
-    cursor_box.x += focused_x - output_box.x;
-    cursor_box.y += focused_y - output_box.y;
-
-    // Choose popup x/y relative to the output:
-
-    // Align the left edge of the popup with the left edge of the cursor.
-    // If the popup wouldn't fit on the output instead align the right edge
-    // of the popup with the right edge of the cursor.
-    const popup_x = blk: {
-        const popup_width = input_popup.wlr_popup.surface.current.width;
-        if (output_box.width - cursor_box.x >= popup_width) {
-            break :blk cursor_box.x;
-        } else {
-            break :blk cursor_box.x + cursor_box.width - popup_width;
-        }
-    };
-
-    // Align the top edge of the popup with the bottom edge of the cursor.
-    // If the popup wouldn't fit on the output instead align the bottom edge
-    // of the popup with the top edge of the cursor.
-    const popup_y = blk: {
-        const popup_height = input_popup.wlr_popup.surface.current.height;
-        if (output_box.height - (cursor_box.y + cursor_box.height) >= popup_height) {
-            break :blk cursor_box.y + cursor_box.height;
-        } else {
-            break :blk cursor_box.y - popup_height;
-        }
-    };
-
-    // Scene node position is relative to the parent so adjust popup x/y to
-    // be relative to the focused surface.
-    input_popup.surface_tree.node.setPosition(
-        popup_x - focused_x + output_box.x,
-        popup_y - focused_y + output_box.y,
-    );
-
-    // The text input rectangle sent to the input method is relative to the popup.
-    cursor_box.x -= popup_x;
-    cursor_box.y -= popup_y;
-    input_popup.wlr_popup.sendTextInputRectangle(&cursor_box);
-}
diff --git a/river/InputRelay.zig b/river/InputRelay.zig
deleted file mode 100644
index e920c48..0000000
--- a/river/InputRelay.zig
+++ /dev/null
@@ -1,233 +0,0 @@
-// SPDX-FileCopyrightText: © 2021 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const InputRelay = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const mem = std.mem;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const util = @import("util.zig");
-
-const TextInput = @import("TextInput.zig");
-const InputPopup = @import("InputPopup.zig");
-const Seat = @import("Seat.zig");
-
-const log = std.log.scoped(.input);
-
-/// List of all text input objects for the seat.
-/// Multiple text input objects may be created per seat, even multiple from the same client.
-/// However, only one text input per seat may be enabled at a time.
-text_inputs: wl.list.Head(TextInput, .link),
-
-/// The input method currently in use for this seat.
-/// Only one input method per seat may be used at a time and if one is
-/// already in use new input methods are ignored.
-/// If this is null, no text input enter events will be sent.
-input_method: ?*wlr.InputMethodV2 = null,
-input_popups: wl.list.Head(InputPopup, .link),
-/// The currently enabled text input for the currently focused surface.
-/// Always null if there is no input method.
-text_input: ?*TextInput = null,
-
-input_method_commit: wl.Listener(void) = .init(handleInputMethodCommit),
-grab_keyboard: wl.Listener(*wlr.InputMethodV2.KeyboardGrab) = .init(handleInputMethodGrabKeyboard),
-input_method_destroy: wl.Listener(void) = .init(handleInputMethodDestroy),
-input_method_new_popup: wl.Listener(*wlr.InputPopupSurfaceV2) = .init(handleInputMethodNewPopup),
-
-grab_keyboard_destroy: wl.Listener(void) = .init(handleInputMethodGrabKeyboardDestroy),
-
-pub fn init(relay: *InputRelay) void {
-    relay.* = .{ .text_inputs = undefined, .input_popups = undefined };
-
-    relay.text_inputs.init();
-    relay.input_popups.init();
-}
-
-pub fn newInputMethod(relay: *InputRelay, input_method: *wlr.InputMethodV2) void {
-    const seat: *Seat = @fieldParentPtr("relay", relay);
-
-    log.debug("new input method on seat {s}", .{seat.wlr_seat.name});
-
-    // Only one input_method can be bound to a seat.
-    if (relay.input_method != null) {
-        log.info("seat {s} already has an input method", .{seat.wlr_seat.name});
-        input_method.sendUnavailable();
-        return;
-    }
-
-    relay.input_method = input_method;
-
-    input_method.events.commit.add(&relay.input_method_commit);
-    input_method.events.grab_keyboard.add(&relay.grab_keyboard);
-    input_method.events.destroy.add(&relay.input_method_destroy);
-    input_method.events.new_popup_surface.add(&relay.input_method_new_popup);
-
-    if (seat.focused.surface()) |surface| {
-        relay.focus(surface);
-    }
-}
-
-fn handleInputMethodCommit(listener: *wl.Listener(void)) void {
-    const relay: *InputRelay = @fieldParentPtr("input_method_commit", listener);
-    const input_method = relay.input_method.?;
-
-    if (!input_method.client_active) return;
-    const text_input = relay.text_input orelse return;
-
-    if (input_method.current.preedit.text) |preedit_text| {
-        text_input.wlr_text_input.sendPreeditString(
-            preedit_text,
-            input_method.current.preedit.cursor_begin,
-            input_method.current.preedit.cursor_end,
-        );
-    }
-
-    if (input_method.current.commit_text) |commit_text| {
-        text_input.wlr_text_input.sendCommitString(commit_text);
-    }
-
-    if (input_method.current.delete.before_length != 0 or
-        input_method.current.delete.after_length != 0)
-    {
-        text_input.wlr_text_input.sendDeleteSurroundingText(
-            input_method.current.delete.before_length,
-            input_method.current.delete.after_length,
-        );
-    }
-
-    text_input.wlr_text_input.sendDone();
-}
-
-fn handleInputMethodDestroy(listener: *wl.Listener(void)) void {
-    const relay: *InputRelay = @fieldParentPtr("input_method_destroy", listener);
-
-    relay.input_method_commit.link.remove();
-    relay.grab_keyboard.link.remove();
-    relay.input_method_destroy.link.remove();
-    relay.input_method_new_popup.link.remove();
-    relay.input_method = null;
-
-    relay.focus(null);
-
-    assert(relay.text_input == null);
-}
-
-fn handleInputMethodGrabKeyboard(
-    listener: *wl.Listener(*wlr.InputMethodV2.KeyboardGrab),
-    keyboard_grab: *wlr.InputMethodV2.KeyboardGrab,
-) void {
-    const relay: *InputRelay = @fieldParentPtr("grab_keyboard", listener);
-    const seat: *Seat = @fieldParentPtr("relay", relay);
-
-    const active_keyboard = seat.wlr_seat.getKeyboard();
-    keyboard_grab.setKeyboard(active_keyboard);
-
-    keyboard_grab.events.destroy.add(&relay.grab_keyboard_destroy);
-}
-
-fn handleInputMethodNewPopup(
-    listener: *wl.Listener(*wlr.InputPopupSurfaceV2),
-    wlr_popup: *wlr.InputPopupSurfaceV2,
-) void {
-    const relay: *InputRelay = @fieldParentPtr("input_method_new_popup", listener);
-
-    InputPopup.create(wlr_popup, relay) catch {
-        log.err("out of memory", .{});
-        return;
-    };
-}
-
-fn handleInputMethodGrabKeyboardDestroy(listener: *wl.Listener(void)) void {
-    const relay: *InputRelay = @fieldParentPtr("grab_keyboard_destroy", listener);
-    const input_method = relay.input_method.?;
-    const keyboard_grab = input_method.keyboard_grab.?;
-    relay.grab_keyboard_destroy.link.remove();
-
-    if (keyboard_grab.keyboard) |keyboard| {
-        input_method.seat.keyboardNotifyModifiers(&keyboard.modifiers);
-    }
-}
-
-pub fn disableTextInput(relay: *InputRelay) void {
-    assert(relay.text_input != null);
-    relay.text_input = null;
-
-    if (relay.input_method) |input_method| {
-        {
-            var it = relay.input_popups.iterator(.forward);
-            while (it.next()) |popup| popup.update();
-        }
-        input_method.sendDeactivate();
-        input_method.sendDone();
-    }
-}
-
-pub fn sendInputMethodState(relay: *InputRelay) void {
-    const input_method = relay.input_method.?;
-    const wlr_text_input = relay.text_input.?.wlr_text_input;
-
-    // TODO Send these events only if something changed.
-    // On activation all events must be sent for all active features.
-
-    if (wlr_text_input.active_features.surrounding_text) {
-        if (wlr_text_input.current.surrounding.text) |text| {
-            input_method.sendSurroundingText(
-                text,
-                wlr_text_input.current.surrounding.cursor,
-                wlr_text_input.current.surrounding.anchor,
-            );
-        }
-    }
-
-    input_method.sendTextChangeCause(wlr_text_input.current.text_change_cause);
-
-    if (wlr_text_input.active_features.content_type) {
-        input_method.sendContentType(
-            wlr_text_input.current.content_type.hint,
-            wlr_text_input.current.content_type.purpose,
-        );
-    }
-
-    {
-        var it = relay.input_popups.iterator(.forward);
-        while (it.next()) |popup| popup.update();
-    }
-
-    input_method.sendDone();
-}
-
-pub fn focus(relay: *InputRelay, new_focus: ?*wlr.Surface) void {
-    // Send leave events
-    {
-        var it = relay.text_inputs.iterator(.forward);
-        while (it.next()) |text_input| {
-            if (text_input.wlr_text_input.focused_surface) |surface| {
-                // This function should not be called unless focus changes
-                assert(surface != new_focus);
-                text_input.wlr_text_input.sendLeave();
-            }
-        }
-    }
-
-    // Clear currently enabled text input
-    if (relay.text_input != null) {
-        relay.disableTextInput();
-    }
-
-    // Send enter events if we have an input method.
-    // No text input for the new surface should be enabled yet as the client
-    // should wait until it receives an enter event.
-    if (new_focus) |surface| {
-        if (relay.input_method != null) {
-            var it = relay.text_inputs.iterator(.forward);
-            while (it.next()) |text_input| {
-                if (text_input.wlr_text_input.resource.getClient() == surface.resource.getClient()) {
-                    text_input.wlr_text_input.sendEnter(surface);
-                }
-            }
-        }
-    }
-}
diff --git a/river/Keyboard.zig b/river/Keyboard.zig
deleted file mode 100644
index b31892c..0000000
--- a/river/Keyboard.zig
+++ /dev/null
@@ -1,250 +0,0 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const Keyboard = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const xkb = @import("xkbcommon");
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const InputDevice = @import("InputDevice.zig");
-const KeyboardGroup = @import("KeyboardGroup.zig");
-const Seat = @import("Seat.zig");
-
-const log = std.log.scoped(.input);
-
-pub const Config = struct {
-    keymap: ?*xkb.Keymap,
-    /// Repeat rate in characters per second
-    repeat_rate: u31 = 40,
-    /// Repeat delay in milliseconds
-    repeat_delay: u31 = 400,
-};
-
-device: InputDevice,
-device_destroyed: bool = false,
-queued_events: u32 = 0,
-
-config: Config,
-
-/// Set of pressed keys that have been processed by processKey().
-/// Not equivalent to wlr_keyboard.keycodes.
-/// This state is necessary to handle removing keyboards from groups properly.
-pressed: std.AutoArrayHashMapUnmanaged(u32, void) = .empty,
-/// Only null during initialization or due to allocation failure.
-group: ?*KeyboardGroup = null,
-
-key: wl.Listener(*wlr.Keyboard.event.Key) = .init(queueKey),
-modifiers: wl.Listener(*wlr.Keyboard) = .init(queueModifiers),
-keymap: wl.Listener(*wlr.Keyboard) = .init(queueKeymap),
-
-pub fn create(seat: *Seat, wlr_device: *wlr.InputDevice, virtual: bool) !*Keyboard {
-    const wlr_keyboard = wlr_device.toKeyboard();
-
-    const keyboard = try util.gpa.create(Keyboard);
-    errdefer util.gpa.destroy(keyboard);
-
-    keyboard.* = .{
-        .config = .{
-            .keymap = blk: {
-                if (virtual) {
-                    if (wlr_keyboard.keymap) |keymap| {
-                        break :blk keymap.ref();
-                    } else {
-                        break :blk null;
-                    }
-                } else {
-                    break :blk server.xkb_config.default_keymap.ref();
-                }
-            },
-        },
-        .device = undefined,
-    };
-    errdefer if (keyboard.config.keymap) |keymap| keymap.unref();
-
-    try keyboard.pressed.ensureTotalCapacity(util.gpa, KeyboardGroup.pressed_count_max);
-    errdefer keyboard.pressed.deinit(util.gpa);
-
-    try keyboard.device.init(seat, wlr_device, virtual);
-    errdefer keyboard.device.deinit();
-
-    wlr_keyboard.data = keyboard;
-
-    wlr_keyboard.events.key.add(&keyboard.key);
-    wlr_keyboard.events.modifiers.add(&keyboard.modifiers);
-
-    if (virtual) {
-        wlr_keyboard.events.keymap.add(&keyboard.keymap);
-    } else {
-        keyboard.keymap.link.init();
-        if (shouldSetKeymap()) {
-            _ = wlr_keyboard.setKeymap(keyboard.config.keymap);
-        }
-    }
-
-    return keyboard;
-}
-
-// We don't want to ever set a keymap for backend-created wlr.Keyboards.
-// Setting a keymap means that modifiers will be buggy and LEDs will be out of sync.
-// However, if we don't set a keymap we don't get modifiers events from the backend
-// at all currently due to a wlroots bug. This is even worse, so set a keymap
-// despite the bugginess for backends that generate keyboard modifiers events.
-// TODO(wlroots) https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5324
-fn shouldSetKeymap() bool {
-    var wayland_or_x11: bool = false;
-    server.backend.multiForEachBackend(*bool, shouldSetKeymapIter, &wayland_or_x11);
-    return wayland_or_x11;
-}
-
-fn shouldSetKeymapIter(backend: *wlr.Backend, wayland_or_x11: *bool) void {
-    if (backend.isWl() or (wlr.config.has_x11_backend and backend.isX11())) {
-        wayland_or_x11.* = true;
-    }
-}
-
-pub fn setGroup(keyboard: *Keyboard) void {
-    assert(keyboard.group == null);
-    const seat = keyboard.device.seat;
-    // Virtual keyboards set their own keymap and require independent modifier state.
-    // Therefore, they are always placed in their own group of one.
-    if (!keyboard.device.virtual) {
-        var it = seat.keyboard_groups.iterator(.forward);
-        while (it.next()) |group| {
-            if (group.match(&keyboard.config)) {
-                keyboard.group = group.ref();
-                return;
-            }
-        }
-    }
-    keyboard.group = KeyboardGroup.create(seat, keyboard.config, keyboard.device.virtual) catch |err| switch (err) {
-        error.OutOfMemory => {
-            log.err("out of memory", .{});
-            return;
-        },
-    };
-}
-
-pub fn setRepeatInfo(keyboard: *Keyboard, rate: u31, delay: u31) void {
-    assert(!keyboard.device.virtual);
-    keyboard.config.repeat_rate = rate;
-    keyboard.config.repeat_delay = delay;
-    if (keyboard.group) |group| {
-        group.unref(keyboard.pressed.keys());
-        keyboard.group = null;
-    }
-    keyboard.setGroup();
-}
-
-pub fn setKeymap(keyboard: *Keyboard, keymap: *xkb.Keymap) void {
-    assert(!keyboard.device.virtual);
-    if (shouldSetKeymap()) {
-        _ = keyboard.device.wlr_device.toKeyboard().setKeymap(keyboard.config.keymap);
-    }
-    if (keyboard.config.keymap) |old| old.unref();
-    keyboard.config.keymap = keymap.ref();
-    if (keyboard.group) |group| {
-        group.unref(keyboard.pressed.keys());
-        keyboard.group = null;
-    }
-    keyboard.setGroup();
-}
-
-pub fn deviceDestroy(keyboard: *Keyboard) void {
-    assert(!keyboard.device_destroyed);
-    keyboard.device_destroyed = true;
-
-    keyboard.key.link.remove();
-    keyboard.modifiers.link.remove();
-    keyboard.keymap.link.remove();
-
-    keyboard.device.deinit();
-
-    keyboard.maybeDestroy();
-}
-
-fn maybeDestroy(keyboard: *Keyboard) void {
-    if (!keyboard.device_destroyed or keyboard.queued_events > 0) {
-        return;
-    }
-
-    if (keyboard.config.keymap) |keymap| keymap.unref();
-    if (keyboard.group) |group| group.unref(keyboard.pressed.keys());
-
-    keyboard.pressed.deinit(util.gpa);
-    util.gpa.destroy(keyboard);
-}
-
-pub fn dropEvent(keyboard: *Keyboard) void {
-    keyboard.queued_events -= 1;
-    keyboard.maybeDestroy();
-}
-
-pub fn processKey(keyboard: *Keyboard, key: *const wlr.Keyboard.event.Key) void {
-    if (key.state == .released) {
-        _ = keyboard.pressed.swapRemove(key.keycode);
-    } else {
-        assert(key.state == .pressed);
-        if (keyboard.pressed.count() < KeyboardGroup.pressed_count_max) {
-            keyboard.pressed.putAssumeCapacity(key.keycode, {});
-        }
-    }
-    if (keyboard.group) |group| group.processKey(key);
-    keyboard.dropEvent();
-}
-
-pub fn processModifiers(keyboard: *Keyboard, modifiers: wlr.Keyboard.Modifiers) void {
-    if (keyboard.group) |group| group.processModifiers(modifiers);
-    keyboard.dropEvent();
-}
-
-pub fn processKeymap(keyboard: *Keyboard, keymap: *xkb.Keymap) void {
-    defer keymap.unref();
-    if (keyboard.group) |group| group.processKeymap(keymap);
-    keyboard.dropEvent();
-}
-
-fn queueKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
-    const keyboard: *Keyboard = @fieldParentPtr("key", listener);
-    assert(!keyboard.device_destroyed);
-    keyboard.queued_events += 1;
-    keyboard.device.seat.queueEvent(.{ .keyboard_key = .{
-        .keyboard = keyboard,
-        .key = event.*,
-    } }) catch {
-        keyboard.queued_events -= 1;
-    };
-}
-
-fn queueModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
-    const keyboard: *Keyboard = @fieldParentPtr("modifiers", listener);
-    assert(!keyboard.device_destroyed);
-    const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
-    keyboard.queued_events += 1;
-    keyboard.device.seat.queueEvent(.{ .keyboard_modifiers = .{
-        .keyboard = keyboard,
-        .modifiers = wlr_keyboard.modifiers,
-    } }) catch {
-        keyboard.queued_events -= 1;
-    };
-}
-
-fn queueKeymap(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
-    const keyboard: *Keyboard = @fieldParentPtr("keymap", listener);
-    assert(!keyboard.device_destroyed);
-    const wlr_keyboard = keyboard.device.wlr_device.toKeyboard();
-    const keymap = wlr_keyboard.keymap orelse return;
-    keyboard.queued_events += 1;
-    keyboard.device.seat.queueEvent(.{ .keyboard_keymap = .{
-        .keyboard = keyboard,
-        .keymap = keymap.ref(),
-    } }) catch {
-        keyboard.queued_events -= 1;
-        keymap.unref();
-    };
-}
diff --git a/river/KeyboardGroup.zig b/river/KeyboardGroup.zig
deleted file mode 100644
index 177b5d6..0000000
--- a/river/KeyboardGroup.zig
+++ /dev/null
@@ -1,457 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const KeyboardGroup = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const xkb = @import("xkbcommon");
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Keyboard = @import("Keyboard.zig");
-const Seat = @import("Seat.zig");
-const XkbBinding = @import("XkbBinding.zig");
-const InputDevice = @import("InputDevice.zig");
-
-const log = std.log.scoped(.input);
-
-const KeyConsumer = union(enum) {
-    /// Builtin compositor binding, e.g. VT switching
-    builtin,
-    /// A null value indicates that the xkb_binding_v1 was destroyed or that
-    /// a press event was already sent due to a press on a different keyboard.
-    binding: ?*XkbBinding,
-    /// The river_xkb_bindings_seat_v1.ensure_next_key_eaten request caused
-    /// the key to be eaten.
-    ensure_eaten,
-    im_grab,
-    /// Seat's focused client
-    focus,
-};
-
-const Press = struct {
-    consumer: KeyConsumer,
-    count: u32,
-};
-
-pub const pressed_count_max = 32;
-comptime {
-    // wlroots uses a buffer of length 32 to track pressed keys and does not track pressed
-    // keys beyond that limit. It seems likely that this can cause some inconsistency within
-    // wlroots in the case that someone has 32 fingers and the hardware supports N-key rollover.
-    //
-    // Furthermore, wlroots will continue to forward key press/release events to river if more
-    // than 32 keys are pressed. Therefore river chooses to ignore keypresses that would take
-    // the keyboard beyond 32 simultaneously pressed keys.
-    assert(pressed_count_max == @typeInfo(std.meta.fieldInfo(wlr.Keyboard, .keycodes).type).array.len);
-}
-
-ref_count: u32 = 1,
-
-seat: *Seat,
-/// Seat.keyboard_groups
-link: wl.list.Link,
-
-virtual: bool,
-
-config: Keyboard.Config,
-
-/// This is the keyboard that actually gets passed to wlr_seat functions for
-/// setting keyboard focus.
-state: wlr.Keyboard,
-modifiers_old: wlr.Keyboard.ModifierMask = .{},
-
-/// Maps from pressed libinput keycode (not xkb keycode) to information
-/// about where the press event has been sent.
-pressed: std.AutoArrayHashMapUnmanaged(u32, Press) = .empty,
-
-key: wl.Listener(*wlr.Keyboard.event.Key) = .init(handleKey),
-modifiers: wl.Listener(*wlr.Keyboard) = .init(handleModifiers),
-
-pub fn create(seat: *Seat, config: Keyboard.Config, virtual: bool) !*KeyboardGroup {
-    const group = try util.gpa.create(KeyboardGroup);
-    errdefer util.gpa.destroy(group);
-    group.* = .{
-        .seat = seat,
-        .virtual = virtual,
-        .config = config,
-        .state = undefined,
-        .link = undefined,
-    };
-
-    try group.pressed.ensureTotalCapacity(util.gpa, pressed_count_max);
-    errdefer comptime unreachable;
-
-    seat.keyboard_groups.append(group);
-
-    group.state.init(&.{
-        .name = "river.KeyboardGroup",
-        .led_update = ledUpdate,
-    }, "river.KeyboardGroup");
-    group.state.data = group;
-
-    // wlroots will log an error on failure, there's not much we can do to recover unfortunately.
-    _ = group.state.setKeymap(config.keymap);
-    group.state.setRepeatInfo(config.repeat_rate, config.repeat_delay);
-
-    group.state.events.key.add(&group.key);
-    group.state.events.modifiers.add(&group.modifiers);
-
-    return group;
-}
-
-pub fn ref(group: *KeyboardGroup) *KeyboardGroup {
-    group.ref_count += 1;
-    return group;
-}
-
-pub fn unref(group: *KeyboardGroup, to_release: []u32) void {
-    for (to_release) |keycode| {
-        group.processKey(&.{
-            .time_msec = util.msecTimestamp(),
-            .keycode = keycode,
-            .update_state = true,
-            .state = .released,
-        });
-    }
-
-    group.ref_count -= 1;
-    if (group.ref_count > 0) {
-        return;
-    }
-
-    group.link.remove();
-
-    group.key.link.remove();
-    group.modifiers.link.remove();
-
-    // If the currently active keyboard of a seat is destroyed we need to set
-    // a new active keyboard. Otherwise wlroots may send an enter event without
-    // first having sent a keymap event if Seat.keyboardNotifyEnter() is called
-    // before a new active keyboard is set.
-    if (group.seat.wlr_seat.getKeyboard() == &group.state) {
-        if (group.seat.keyboard_groups.first()) |other| {
-            group.seat.wlr_seat.setKeyboard(&other.state);
-        }
-    }
-
-    group.state.finish();
-
-    group.pressed.deinit(util.gpa);
-
-    util.gpa.destroy(group);
-}
-
-pub fn match(group: *const KeyboardGroup, config: *Keyboard.Config) bool {
-    const a = &group.config;
-    const b = config;
-    if (a.repeat_rate != b.repeat_rate) return false;
-    if (a.repeat_delay != b.repeat_delay) return false;
-
-    if (a.keymap == b.keymap) return true;
-    if (a.keymap == null or b.keymap == null) return false;
-
-    // Can't get away with a cheap pointer comparison.
-    // TODO implement a non-terrible way to do this upstream in xkbcommon
-    const a_string = a.keymap.?.getAsString2(.use_original_format, .{});
-    defer std.c.free(a_string);
-    const b_string = b.keymap.?.getAsString2(.use_original_format, .{});
-    defer std.c.free(b_string);
-    if (a_string == null or b_string == null) {
-        // Ugh, no good options here, we don't know why the function failed.
-        // xkbcommon really needs a better API for this.
-        log.err("xkb_keymap_get_as_string2() failed", .{});
-        return false;
-    }
-    if (std.mem.orderZ(u8, a_string.?, b_string.?) == .eq) {
-        // Consolidate so we don't have to do this expensive/silly comparison again
-        config.keymap.?.unref();
-        config.keymap = group.config.keymap.?.ref();
-        return true;
-    }
-    return false;
-}
-
-pub fn processKey(group: *KeyboardGroup, event: *const wlr.Keyboard.event.Key) void {
-    if (group.pressed.getPtr(event.keycode)) |key| {
-        assert(key.count > 0);
-        if (event.state == .pressed) {
-            key.count += 1;
-        } else {
-            key.count -= 1;
-            if (key.count == 0) {
-                var key_event: wlr.Keyboard.event.Key = .{
-                    .time_msec = event.time_msec,
-                    .keycode = event.keycode,
-                    .update_state = true,
-                    .state = .released,
-                };
-                // Calls handleKey(), which will remove from pressed
-                group.state.notifyKey(&key_event);
-            }
-        }
-    } else if (event.state == .pressed) {
-        if (group.pressed.count() < pressed_count_max) {
-            var key_event: wlr.Keyboard.event.Key = .{
-                .time_msec = event.time_msec,
-                .keycode = event.keycode,
-                .update_state = true,
-                .state = .pressed,
-            };
-            // Calls handleKey(), which will add to pressed
-            group.state.notifyKey(&key_event);
-        }
-    }
-    // Release events without a prior press event are ignored.
-}
-
-fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboard.event.Key) void {
-    const group: *KeyboardGroup = @fieldParentPtr("key", listener);
-
-    const xkb_state = group.state.xkb_state orelse {
-        log.err("no xkb_state available", .{});
-        return;
-    };
-
-    {
-        var it = group.seat.keyboard_groups.iterator(.forward);
-        while (it.next()) |g| {
-            for (g.pressed.values()) |press| {
-                if (press.consumer != .binding) continue;
-                const binding = press.consumer.binding orelse continue;
-                binding.stopRepeat();
-            }
-        }
-    }
-
-    // Every sent press event, to a regular client or the input method, should have
-    // the corresponding release event sent to the same client.
-    // Similarly, no press event means no release event.
-    const consumer: KeyConsumer = blk: {
-        if (event.state == .released) {
-            // Decision is made on press; release only follows it
-            const kv = group.pressed.fetchSwapRemove(event.keycode).?;
-            assert(kv.value.count == 0);
-            break :blk kv.value.consumer;
-        }
-        // Translate libinput keycode -> xkbcommon
-        const xkb_keycode = event.keycode + 8;
-        const modifiers = group.state.getModifiers();
-        for (xkb_state.keyGetSyms(xkb_keycode)) |sym| {
-            if (handleBuiltinBinding(sym)) {
-                log.debug("matched builtin binding", .{});
-                break :blk .builtin;
-            }
-        }
-        if (group.seat.matchXkbBinding(xkb_keycode, modifiers, xkb_state)) |binding| {
-            log.debug("matched xkb binding", .{});
-            group.seat.xkb_bindings_seat.ensure_next_key_eaten = false;
-            break :blk .{
-                .binding = if (binding.sent_pressed) null else binding,
-            };
-        }
-        if (group.seat.xkb_bindings_seat.ensure_next_key_eaten) {
-            // This approach for filtering out modifiers feels like a hack.
-            // Open questions:
-            // - Are there keycodes that should be considered a modifier which
-            //   are not yet checked by keysymIsModifier()?
-            // - Is it possible to test whether keysymIsModifier() is complete?
-            // - Is there a way to test the effect the keycode would have on
-            //   the active modifiers of the xkb_state?
-            // - Could we add a function to libxkbcommon to make that possible?
-            for (xkb_state.keyGetSyms(xkb_keycode)) |sym| {
-                if (!keysymIsModifier(sym)) {
-                    group.seat.xkb_bindings_seat.ensure_next_key_eaten = false;
-                    break :blk .ensure_eaten;
-                }
-            }
-        }
-        if (group.getInputMethodGrab() != null) {
-            break :blk .im_grab;
-        }
-        break :blk .focus;
-    };
-
-    if (event.state == .pressed) {
-        group.pressed.putAssumeCapacityNoClobber(event.keycode, .{
-            .consumer = consumer,
-            .count = 1,
-        });
-    }
-
-    switch (consumer) {
-        .builtin => {},
-        .binding => |b| if (b) |binding| {
-            if (event.state == .pressed) {
-                binding.pressed();
-            } else {
-                binding.released();
-            }
-        },
-        .ensure_eaten => {
-            if (event.state == .pressed) {
-                group.seat.xkb_bindings_seat.scheduled.ate_unbound_key = true;
-                server.wm.dirtyWindowing();
-            }
-        },
-        .im_grab => if (group.getInputMethodGrab()) |keyboard_grab| {
-            keyboard_grab.setKeyboard(&group.state);
-            keyboard_grab.sendKey(event.time_msec, event.keycode, event.state);
-        },
-        .focus => {
-            group.seat.wlr_seat.setKeyboard(&group.state);
-            group.seat.wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
-        },
-    }
-
-    group.sendState();
-}
-
-fn keysymIsModifier(keysym: xkb.Keysym) bool {
-    switch (keysym) {
-        xkb.Keysym.Shift_L,
-        xkb.Keysym.Shift_R,
-        xkb.Keysym.Control_L,
-        xkb.Keysym.Control_R,
-        xkb.Keysym.Caps_Lock,
-        xkb.Keysym.Shift_Lock,
-
-        xkb.Keysym.Meta_L,
-        xkb.Keysym.Meta_R,
-        xkb.Keysym.Alt_L,
-        xkb.Keysym.Alt_R,
-        xkb.Keysym.Super_L,
-        xkb.Keysym.Super_R,
-        xkb.Keysym.Hyper_L,
-        xkb.Keysym.Hyper_R,
-
-        xkb.Keysym.Num_Lock,
-
-        xkb.Keysym.ISO_Lock,
-        xkb.Keysym.ISO_Level2_Latch,
-        xkb.Keysym.ISO_Level3_Shift,
-        xkb.Keysym.ISO_Level3_Latch,
-        xkb.Keysym.ISO_Level3_Lock,
-        xkb.Keysym.ISO_Level5_Shift,
-        xkb.Keysym.ISO_Level5_Latch,
-        xkb.Keysym.ISO_Level5_Lock,
-        xkb.Keysym.ISO_Group_Shift,
-        xkb.Keysym.ISO_Group_Latch,
-        xkb.Keysym.ISO_Group_Lock,
-        xkb.Keysym.ISO_Next_Group,
-        xkb.Keysym.ISO_Next_Group_Lock,
-        xkb.Keysym.ISO_Prev_Group,
-        xkb.Keysym.ISO_Prev_Group_Lock,
-        xkb.Keysym.ISO_First_Group,
-        xkb.Keysym.ISO_First_Group_Lock,
-        xkb.Keysym.ISO_Last_Group,
-        xkb.Keysym.ISO_Last_Group_Lock,
-        => return true,
-        else => return false,
-    }
-}
-
-pub fn processModifiers(group: *KeyboardGroup, modifiers: wlr.Keyboard.Modifiers) void {
-    group.state.notifyModifiers(modifiers);
-}
-
-fn handleModifiers(listener: *wl.Listener(*wlr.Keyboard), _: *wlr.Keyboard) void {
-    const group: *KeyboardGroup = @fieldParentPtr("modifiers", listener);
-
-    {
-        const old: u32 = @bitCast(group.modifiers_old);
-        const new: u32 = @bitCast(group.state.getModifiers());
-        const watched: u32 = @bitCast(group.seat.xkb_bindings_seat.requested.mods_watched);
-        if (old & watched != new & watched) {
-            group.seat.xkb_bindings_seat.scheduled.mods_update = .{
-                .old = @bitCast(old),
-                .new = @bitCast(new),
-            };
-            server.wm.dirtyWindowing();
-        }
-        group.modifiers_old = @bitCast(new);
-    }
-
-    if (group.getInputMethodGrab()) |keyboard_grab| {
-        keyboard_grab.setKeyboard(&group.state);
-        keyboard_grab.sendModifiers(&group.state.modifiers);
-    } else {
-        group.seat.wlr_seat.setKeyboard(&group.state);
-        group.seat.wlr_seat.keyboardNotifyModifiers(&group.state.modifiers);
-    }
-    group.sendState();
-}
-
-/// Handle any builtin, hardcoded compositor keybindings such as VT switching.
-/// Returns true if the keysym was handled.
-fn handleBuiltinBinding(keysym: xkb.Keysym) bool {
-    switch (@intFromEnum(keysym)) {
-        @intFromEnum(xkb.Keysym.XF86Switch_VT_1)...@intFromEnum(xkb.Keysym.XF86Switch_VT_12) => {
-            log.debug("switch VT keysym received", .{});
-            if (server.session) |session| {
-                const vt = @intFromEnum(keysym) - @intFromEnum(xkb.Keysym.XF86Switch_VT_1) + 1;
-                std.log.info("switching to VT {}", .{vt});
-                session.changeVt(vt) catch std.log.err("changing VT failed", .{});
-            }
-            return true;
-        },
-        else => return false,
-    }
-}
-
-/// Returns null if the keyboard is not grabbed by an input method,
-/// or if the group is for a virtual keyboard.
-/// TODO: it would be good if virtual keyboards that are not associated with the
-/// input method client would pass through the input method grab.
-/// See https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/2322
-fn getInputMethodGrab(group: *KeyboardGroup) ?*wlr.InputMethodV2.KeyboardGrab {
-    if (group.virtual) {
-        return null;
-    }
-    if (group.seat.relay.input_method) |input_method| {
-        if (input_method.keyboard_grab) |keyboard_grab| {
-            return keyboard_grab;
-        }
-    }
-    return null;
-}
-
-pub fn processKeymap(group: *KeyboardGroup, keymap: *xkb.Keymap) void {
-    // wlroots will log an error on failure, there's not much we can do to recover unfortunately.
-    _ = group.state.setKeymap(keymap);
-}
-
-pub fn sendState(group: *KeyboardGroup) void {
-    const keymap = group.config.keymap.?;
-    const layout_index = group.state.modifiers.group;
-    const layout_name = keymap.layoutGetName(layout_index);
-    const caps_mask = keymap.modGetMask(xkb.names.mod.caps);
-    const capslock = group.state.modifiers.locked & caps_mask != 0;
-    const num_mask = keymap.modGetMask(xkb.names.vmod.num);
-    const numlock = group.state.modifiers.locked & num_mask != 0;
-
-    var it = server.xkb_config.keyboards.iterator(.forward);
-    while (it.next()) |xkb_keyboard| {
-        const device: *InputDevice = @fieldParentPtr("xkb_keyboard", xkb_keyboard);
-        const keyboard: *Keyboard = @fieldParentPtr("device", device);
-        if (keyboard.group != group) continue;
-
-        xkb_keyboard.sendState(layout_index, layout_name, capslock, numlock);
-    }
-}
-
-fn ledUpdate(state: *wlr.Keyboard, leds: u32) callconv(.c) void {
-    const group: *KeyboardGroup = @fieldParentPtr("state", state);
-    var it = server.input_manager.devices.iterator(.forward);
-    while (it.next()) |device| {
-        if (device.wlr_device.type != .keyboard) continue;
-        const keyboard: *Keyboard = @fieldParentPtr("device", device);
-        if (keyboard.group != group) continue;
-        const wlr_keyboard = device.wlr_device.toKeyboard();
-        wlr_keyboard.ledUpdate(leds);
-    }
-}
diff --git a/river/LayerShell.zig b/river/LayerShell.zig
deleted file mode 100644
index 6578f1f..0000000
--- a/river/LayerShell.zig
+++ /dev/null
@@ -1,193 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LayerShell = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const math = std.math;
-const wlr = @import("wlroots");
-const wayland = @import("wayland");
-const wl = wayland.server.wl;
-const river = wayland.server.river;
-const zwlr = wayland.server.zwlr;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const LayerShellOutput = @import("LayerShellOutput.zig");
-const LayerShellSeat = @import("LayerShellSeat.zig");
-const LayerSurface = @import("LayerSurface.zig");
-const Output = @import("Output.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-const Seat = @import("Seat.zig");
-const SlotMap = @import("slotmap").SlotMap;
-
-const log = std.log.scoped(.wm);
-
-global: *wl.Global,
-wlr_shell: *wlr.LayerShellV1,
-
-/// The layer shell object of the active window manager, if any
-objects: wl.list.Head(river.LayerShellV1, null),
-
-surfaces: SlotMap(*LayerSurface) = .empty,
-
-new_surface: wl.Listener(*wlr.LayerSurfaceV1) = .init(handleNewSurface),
-
-pub fn init(layer_shell: *LayerShell) !void {
-    layer_shell.* = .{
-        .global = try wl.Global.create(server.wl_server, river.LayerShellV1, 1, *LayerShell, layer_shell, bind),
-        .wlr_shell = try wlr.LayerShellV1.create(server.wl_server, 4),
-        .objects = undefined,
-    };
-    layer_shell.objects.init();
-    layer_shell.wlr_shell.events.new_surface.add(&layer_shell.new_surface);
-}
-
-// Use a deinit function rather than listening for the wl_server to be destroyed
-// in order to avoid a signal ordering issue. The wlr.LayerShellV1 also listens
-// for the wl_server to be destroyed and asserts that the new_surface event has
-// no remaining listeners.
-pub fn deinit(layer_shell: *LayerShell) void {
-    layer_shell.global.destroy();
-    layer_shell.new_surface.link.remove();
-}
-
-fn bind(client: *wl.Client, layer_shell: *LayerShell, version: u32, id: u32) void {
-    const object = river.LayerShellV1.create(client, version, id) catch {
-        client.postNoMemory();
-        log.err("out of memory", .{});
-        return;
-    };
-    object.setHandler(?*anyopaque, handleRequest, handleDestroy, null);
-    layer_shell.objects.append(object);
-}
-
-fn handleDestroy(object: *river.LayerShellV1, _: ?*anyopaque) void {
-    object.getLink().remove();
-}
-
-fn handleRequest(
-    object: *river.LayerShellV1,
-    request: river.LayerShellV1.Request,
-    _: ?*anyopaque,
-) void {
-    switch (request) {
-        .destroy => object.destroy(),
-        .get_output => |args| {
-            const output_data = args.output.getUserData() orelse return;
-            const output: *Output = @ptrCast(@alignCast(output_data));
-            if (output.layer_shell.object != null) {
-                object.postError(
-                    .object_already_created,
-                    "river_layer_shell_output_v1 already created",
-                );
-                return;
-            }
-            output.layer_shell.createObject(object.getClient(), object.getVersion(), args.id);
-        },
-        .get_seat => |args| {
-            const seat_data = args.seat.getUserData() orelse return;
-            const seat: *Seat = @ptrCast(@alignCast(seat_data));
-            if (seat.layer_shell.object != null) {
-                object.postError(
-                    .object_already_created,
-                    "river_layer_shell_seat_v1 already created",
-                );
-                return;
-            }
-            seat.layer_shell.createObject(object.getClient(), object.getVersion(), args.id);
-        },
-    }
-}
-
-fn supported(layer_shell: *LayerShell) bool {
-    const wm_v1 = server.wm.object orelse return false;
-    var it = layer_shell.objects.iterator(.forward);
-    while (it.next()) |object| {
-        if (object.getClient() == wm_v1.getClient()) return true;
-    }
-    return false;
-}
-
-fn handleNewSurface(_: *wl.Listener(*wlr.LayerSurfaceV1), wlr_layer_surface: *wlr.LayerSurfaceV1) void {
-    log.debug(
-        "new layer surface: namespace {s}, layer {s}, anchor {b:0>4}, size {},{}, margin {},{},{},{}, exclusive_zone {}",
-        .{
-            wlr_layer_surface.namespace,
-            @tagName(wlr_layer_surface.current.layer),
-            @as(u32, @bitCast(wlr_layer_surface.current.anchor)),
-            wlr_layer_surface.current.desired_width,
-            wlr_layer_surface.current.desired_height,
-            wlr_layer_surface.current.margin.top,
-            wlr_layer_surface.current.margin.right,
-            wlr_layer_surface.current.margin.bottom,
-            wlr_layer_surface.current.margin.left,
-            wlr_layer_surface.current.exclusive_zone,
-        },
-    );
-
-    if (!server.layer_shell.supported()) {
-        log.info("window manager did not bind river_layer_shell_v1, closing layer surface", .{});
-        wlr_layer_surface.destroy();
-        return;
-    }
-
-    if (wlr_layer_surface.output == null) {
-        var it = server.om.outputs.iterator(.forward);
-        while (it.next()) |output| {
-            if (output.layer_shell.requested.default) {
-                wlr_layer_surface.output = output.wlr_output;
-                break;
-            }
-        } else {
-            if (server.om.outputs.first()) |output| {
-                log.info("window manager did not set default layer surface output, choosing arbitrary output", .{});
-                wlr_layer_surface.output = output.wlr_output;
-            } else {
-                log.err("no output available for layer surface '{s}'", .{wlr_layer_surface.namespace});
-                wlr_layer_surface.destroy();
-                return;
-            }
-        }
-    }
-
-    LayerSurface.create(wlr_layer_surface) catch {
-        wlr_layer_surface.resource.postNoMemory();
-        return;
-    };
-}
-
-pub fn checkExclusiveFocus(_: *LayerShell) void {
-    // Find the topmost layer surface (if any) in the top or overlay layers which
-    // requests exclusive keyboard interactivity.
-    const to_focus = blk: {
-        for ([_]zwlr.LayerShellV1.Layer{ .overlay, .top }) |layer| {
-            const tree = server.scene.layerSurfaceTree(layer);
-            // Iterate in reverse to match rendering order.
-            var it = tree.children.iterator(.reverse);
-            while (it.next()) |node| {
-                assert(node.type == .tree);
-                const node_data: *SceneNodeData = @ptrCast(@alignCast(node.data orelse continue));
-                const layer_surface = node_data.data.layer_surface;
-                const wlr_layer_surface = layer_surface.wlr_layer_surface;
-                if (wlr_layer_surface.surface.mapped and
-                    wlr_layer_surface.current.keyboard_interactive == .exclusive)
-                {
-                    break :blk layer_surface;
-                }
-            }
-        }
-        break :blk null;
-    };
-
-    var it = server.input_manager.seats.iterator(.forward);
-    while (it.next()) |seat| {
-        if (to_focus) |layer_surface| {
-            seat.layer_shell.scheduled.focus = .{ .exclusive = layer_surface.ref };
-        } else if (seat.layer_shell.scheduled.focus == .exclusive) {
-            seat.layer_shell.scheduled.focus = .none;
-        }
-    }
-}
diff --git a/river/LayerShellOutput.zig b/river/LayerShellOutput.zig
deleted file mode 100644
index 590a221..0000000
--- a/river/LayerShellOutput.zig
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LayerShellOutput = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wayland = @import("wayland");
-const wl = wayland.server.wl;
-const river = wayland.server.river;
-const zwlr = wayland.server.zwlr;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Output = @import("Output.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-
-const log = std.log.scoped(.wm);
-
-object: ?*river.LayerShellOutputV1 = null,
-
-scheduled: struct {
-    non_exclusive_area: wlr.Box = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
-} = .{},
-sent: struct {
-    non_exclusive_area: ?wlr.Box = null,
-} = .{},
-requested: struct {
-    default: bool = false,
-} = .{},
-
-pub fn createObject(
-    shell_output: *LayerShellOutput,
-    client: *wl.Client,
-    version: u32,
-    id: u32,
-) void {
-    assert(shell_output.object == null);
-    shell_output.object = river.LayerShellOutputV1.create(client, version, id) catch {
-        client.postNoMemory();
-        log.err("out of memory", .{});
-        return;
-    };
-    shell_output.object.?.setHandler(*LayerShellOutput, handleRequest, handleDestroy, shell_output);
-    server.wm.dirtyWindowing();
-}
-
-pub fn makeInert(shell_output: *LayerShellOutput) void {
-    if (shell_output.object) |object| {
-        object.setHandler(?*anyopaque, handleRequestInert, null, null);
-        handleDestroy(object, shell_output);
-    }
-}
-
-fn handleRequestInert(
-    object: *river.LayerShellOutputV1,
-    request: river.LayerShellOutputV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) object.destroy();
-}
-
-fn handleDestroy(_: *river.LayerShellOutputV1, shell_output: *LayerShellOutput) void {
-    shell_output.object = null;
-    shell_output.sent = .{};
-    shell_output.requested = .{};
-}
-
-fn handleRequest(
-    layer_shell_output_v1: *river.LayerShellOutputV1,
-    request: river.LayerShellOutputV1.Request,
-    shell_output: *LayerShellOutput,
-) void {
-    assert(shell_output.object == layer_shell_output_v1);
-    switch (request) {
-        .destroy => layer_shell_output_v1.destroy(),
-        .set_default => {
-            var it = server.om.outputs.iterator(.forward);
-            while (it.next()) |output| {
-                output.layer_shell.requested.default = false;
-            }
-            shell_output.requested.default = true;
-        },
-    }
-}
-
-pub fn arrange(shell_output: *LayerShellOutput) void {
-    const output: *Output = @fieldParentPtr("layer_shell", shell_output);
-    shell_output.scheduled.non_exclusive_area = output.scheduled.box();
-    sendConfigures(output, .exclusive);
-    sendConfigures(output, .non_exclusive);
-    if (!std.meta.eql(
-        shell_output.sent.non_exclusive_area,
-        shell_output.scheduled.non_exclusive_area,
-    )) {
-        server.wm.dirtyWindowing();
-    }
-}
-
-fn sendConfigures(
-    output: *Output,
-    mode: enum { exclusive, non_exclusive },
-) void {
-    const output_width, const output_height = output.scheduled.dimensions();
-    const output_box = output.scheduled.box();
-    for ([_]zwlr.LayerShellV1.Layer{ .background, .bottom, .top, .overlay }) |layer| {
-        const tree = server.scene.layerSurfaceTree(layer);
-        var it = tree.children.safeIterator(.forward);
-        while (it.next()) |node| {
-            assert(node.type == .tree);
-            const node_data: *SceneNodeData = @ptrCast(@alignCast(node.data orelse continue));
-            const layer_surface = node_data.data.layer_surface;
-            if (!layer_surface.wlr_layer_surface.surface.mapped and
-                !layer_surface.wlr_layer_surface.initial_commit)
-            {
-                continue;
-            }
-            if (layer_surface.wlr_layer_surface.output != output.wlr_output) {
-                continue;
-            }
-            const current = layer_surface.wlr_layer_surface.current;
-            const exclusive = current.exclusive_zone > 0;
-            if (exclusive != (mode == .exclusive)) {
-                continue;
-            }
-            {
-                var new_area = output.layer_shell.scheduled.non_exclusive_area;
-                layer_surface.scene_layer_surface.configure(&output_box, &new_area);
-                // Clients can request bogus exclusive zones larger than the output
-                // dimensions and river must handle this gracefully. It seems reasonable
-                // to close layer shell clients that would cause the usable area of the
-                // output to become less than half the width/height of its full dimensions.
-                if (new_area.width < output_width / 2 or new_area.height < output_height / 2) {
-                    layer_surface.wlr_layer_surface.destroy();
-                    continue;
-                }
-                output.layer_shell.scheduled.non_exclusive_area = new_area;
-            }
-            const x = layer_surface.scene_layer_surface.tree.node.x;
-            const y = layer_surface.scene_layer_surface.tree.node.y;
-            layer_surface.popup_tree.node.setPosition(x, y);
-            layer_surface.scene_layer_surface.tree.node.subsurfaceTreeSetClip(&.{
-                .x = -(x - output.scheduled.x),
-                .y = -(y - output.scheduled.y),
-                .width = output_width,
-                .height = output_height,
-            });
-        }
-    }
-}
-
-pub fn manageStart(shell_output: *LayerShellOutput) void {
-    const output: *Output = @fieldParentPtr("layer_shell", shell_output);
-    assert(output.scheduled.state == .enabled or output.scheduled.state == .disabled_soft);
-
-    const scheduled_box = output.scheduled.box();
-    const sent_box = output.sent.box();
-
-    if (!std.meta.eql(scheduled_box, sent_box)) {
-        shell_output.scheduled.non_exclusive_area = scheduled_box;
-        sendConfigures(output, .exclusive);
-        sendConfigures(output, .non_exclusive);
-    }
-    if (!std.meta.eql(
-        shell_output.sent.non_exclusive_area,
-        shell_output.scheduled.non_exclusive_area,
-    )) {
-        if (shell_output.object) |layer_shell_output_v1| {
-            layer_shell_output_v1.sendNonExclusiveArea(
-                shell_output.scheduled.non_exclusive_area.x,
-                shell_output.scheduled.non_exclusive_area.y,
-                shell_output.scheduled.non_exclusive_area.width,
-                shell_output.scheduled.non_exclusive_area.height,
-            );
-            shell_output.sent.non_exclusive_area = shell_output.scheduled.non_exclusive_area;
-        }
-    }
-}
diff --git a/river/LayerShellSeat.zig b/river/LayerShellSeat.zig
deleted file mode 100644
index 767f759..0000000
--- a/river/LayerShellSeat.zig
+++ /dev/null
@@ -1,93 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LayerShellSeat = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wayland = @import("wayland");
-const wl = wayland.server.wl;
-const river = wayland.server.river;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const LayerSurface = @import("LayerSurface.zig");
-const Seat = @import("Seat.zig");
-
-const log = std.log.scoped(.wm);
-
-const Focus = union(enum) {
-    exclusive: LayerSurface.Ref,
-    non_exclusive: LayerSurface.Ref,
-    none,
-};
-
-object: ?*river.LayerShellSeatV1 = null,
-
-scheduled: struct {
-    focus: Focus = .none,
-} = .{},
-sent: struct {
-    focus: Focus = .none,
-} = .{},
-requested: struct {} = .{},
-
-pub fn createObject(
-    shell_seat: *LayerShellSeat,
-    client: *wl.Client,
-    version: u32,
-    id: u32,
-) void {
-    assert(shell_seat.object == null);
-    shell_seat.object = river.LayerShellSeatV1.create(client, version, id) catch {
-        client.postNoMemory();
-        return;
-    };
-    shell_seat.object.?.setHandler(*LayerShellSeat, handleRequest, handleDestroy, shell_seat);
-    server.wm.dirtyWindowing();
-}
-
-pub fn makeInert(shell_seat: *LayerShellSeat) void {
-    if (shell_seat.object) |object| {
-        object.setHandler(?*anyopaque, handleRequestInert, null, null);
-        shell_seat.object = null;
-    }
-}
-
-fn handleRequestInert(
-    object: *river.LayerShellSeatV1,
-    request: river.LayerShellSeatV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) object.destroy();
-}
-
-fn handleDestroy(_: *river.LayerShellSeatV1, shell_seat: *LayerShellSeat) void {
-    shell_seat.object = null;
-}
-
-fn handleRequest(
-    object: *river.LayerShellSeatV1,
-    request: river.LayerShellSeatV1.Request,
-    shell_seat: *LayerShellSeat,
-) void {
-    assert(shell_seat.object == object);
-    switch (request) {
-        .destroy => object.destroy(),
-    }
-}
-
-pub fn manageStart(shell_seat: *LayerShellSeat) void {
-    if (@as(std.meta.Tag(Focus), shell_seat.scheduled.focus) != shell_seat.sent.focus) {
-        if (shell_seat.object) |shell_seat_v1| {
-            switch (shell_seat.scheduled.focus) {
-                .exclusive => shell_seat_v1.sendFocusExclusive(),
-                .non_exclusive => shell_seat_v1.sendFocusNonExclusive(),
-                .none => shell_seat_v1.sendFocusNone(),
-            }
-        }
-    }
-    shell_seat.sent.focus = shell_seat.scheduled.focus;
-}
diff --git a/river/LayerSurface.zig b/river/LayerSurface.zig
deleted file mode 100644
index 9394c0e..0000000
--- a/river/LayerSurface.zig
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LayerSurface = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const zwlr = @import("wayland").server.zwlr;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Output = @import("Output.zig");
-const SceneNodeData = @import("SceneNodeData.zig");
-const SlotMap = @import("slotmap").SlotMap;
-const XdgPopup = @import("XdgPopup.zig");
-
-const log = std.log.scoped(.wm);
-
-/// Only packed in order to make == work.
-pub const Ref = packed struct {
-    key: SlotMap(*LayerSurface).Key,
-
-    pub fn get(ref: Ref) ?*LayerSurface {
-        return server.layer_shell.surfaces.get(ref.key);
-    }
-};
-
-ref: Ref,
-
-wlr_layer_surface: *wlr.LayerSurfaceV1,
-scene_layer_surface: *wlr.SceneLayerSurfaceV1,
-popup_tree: *wlr.SceneTree,
-
-destroy: wl.Listener(*wlr.LayerSurfaceV1) = wl.Listener(*wlr.LayerSurfaceV1).init(handleDestroy),
-map: wl.Listener(void) = wl.Listener(void).init(handleMap),
-unmap: wl.Listener(void) = wl.Listener(void).init(handleUnmap),
-commit: wl.Listener(*wlr.Surface) = wl.Listener(*wlr.Surface).init(handleCommit),
-new_popup: wl.Listener(*wlr.XdgPopup) = wl.Listener(*wlr.XdgPopup).init(handleNewPopup),
-
-pub fn create(wlr_layer_surface: *wlr.LayerSurfaceV1) error{OutOfMemory}!void {
-    const layer_surface = try util.gpa.create(LayerSurface);
-    errdefer util.gpa.destroy(layer_surface);
-
-    const key = try server.layer_shell.surfaces.put(util.gpa, layer_surface);
-    errdefer server.layer_shell.surfaces.remove(key);
-
-    const layer_tree = server.scene.layerSurfaceTree(wlr_layer_surface.current.layer);
-
-    layer_surface.* = .{
-        .ref = .{ .key = key },
-        .wlr_layer_surface = wlr_layer_surface,
-        .scene_layer_surface = try layer_tree.createSceneLayerSurfaceV1(wlr_layer_surface),
-        .popup_tree = try server.scene.layers.popups.createSceneTree(),
-    };
-
-    try SceneNodeData.attach(&layer_surface.scene_layer_surface.tree.node, .{ .layer_surface = layer_surface });
-    try SceneNodeData.attach(&layer_surface.popup_tree.node, .{ .layer_surface = layer_surface });
-
-    wlr_layer_surface.surface.data = &layer_surface.scene_layer_surface.tree.node;
-
-    wlr_layer_surface.events.destroy.add(&layer_surface.destroy);
-    wlr_layer_surface.surface.events.map.add(&layer_surface.map);
-    wlr_layer_surface.surface.events.unmap.add(&layer_surface.unmap);
-    wlr_layer_surface.surface.events.commit.add(&layer_surface.commit);
-    wlr_layer_surface.events.new_popup.add(&layer_surface.new_popup);
-}
-
-pub fn destroyPopups(layer_surface: *LayerSurface) void {
-    var it = layer_surface.wlr_layer_surface.popups.safeIterator(.forward);
-    while (it.next()) |wlr_xdg_popup| wlr_xdg_popup.destroy();
-}
-
-fn handleDestroy(listener: *wl.Listener(*wlr.LayerSurfaceV1), _: *wlr.LayerSurfaceV1) void {
-    const layer_surface: *LayerSurface = @fieldParentPtr("destroy", listener);
-
-    log.debug("layer surface '{s}' destroyed", .{layer_surface.wlr_layer_surface.namespace});
-
-    layer_surface.destroy.link.remove();
-    layer_surface.map.link.remove();
-    layer_surface.unmap.link.remove();
-    layer_surface.commit.link.remove();
-    layer_surface.new_popup.link.remove();
-
-    layer_surface.destroyPopups();
-
-    layer_surface.popup_tree.node.destroy();
-
-    // The wlr_surface may outlive the wlr_layer_surface so we must clean up the user data.
-    layer_surface.wlr_layer_surface.surface.data = null;
-
-    server.layer_shell.surfaces.remove(layer_surface.ref.key);
-    util.gpa.destroy(layer_surface);
-}
-
-fn handleMap(listener: *wl.Listener(void)) void {
-    const layer_surface: *LayerSurface = @fieldParentPtr("map", listener);
-    const wlr_layer_surface = layer_surface.wlr_layer_surface;
-
-    log.debug("layer surface '{s}' mapped", .{wlr_layer_surface.namespace});
-
-    if (wlr_layer_surface.current.keyboard_interactive == .on_demand) {
-        var it = server.input_manager.seats.iterator(.forward);
-        while (it.next()) |seat| {
-            if (seat.layer_shell.scheduled.focus != .exclusive) {
-                seat.layer_shell.scheduled.focus = .{ .non_exclusive = layer_surface.ref };
-            }
-        }
-    }
-
-    // Beware: it is possible for arrange() to destroy this LayerSurface!
-    const output: *Output = @ptrCast(@alignCast(layer_surface.wlr_layer_surface.output.?.data));
-    output.layer_shell.arrange();
-    server.layer_shell.checkExclusiveFocus();
-    server.wm.dirtyWindowing();
-}
-
-fn handleUnmap(listener: *wl.Listener(void)) void {
-    const layer_surface: *LayerSurface = @fieldParentPtr("unmap", listener);
-
-    log.debug("layer surface '{s}' unmapped", .{layer_surface.wlr_layer_surface.namespace});
-
-    {
-        var it = server.input_manager.seats.iterator(.forward);
-        while (it.next()) |seat| {
-            if (seat.focused == .layer_surface and seat.focused.layer_surface == layer_surface) {
-                seat.focus(.none);
-            }
-            if (seat.layer_shell.scheduled.focus == .non_exclusive and
-                seat.layer_shell.scheduled.focus.non_exclusive == layer_surface.ref)
-            {
-                seat.layer_shell.scheduled.focus = .none;
-            }
-        }
-    }
-
-    // Beware: it is possible for arrange() to destroy this LayerSurface!
-    const output: *Output = @ptrCast(@alignCast(layer_surface.wlr_layer_surface.output.?.data));
-    output.layer_shell.arrange();
-    server.layer_shell.checkExclusiveFocus();
-    server.wm.dirtyWindowing();
-}
-
-fn handleCommit(listener: *wl.Listener(*wlr.Surface), _: *wlr.Surface) void {
-    const layer_surface: *LayerSurface = @fieldParentPtr("commit", listener);
-    const wlr_layer_surface = layer_surface.wlr_layer_surface;
-
-    assert(wlr_layer_surface.output != null);
-
-    // If the layer was changed, move the LayerSurface to the proper tree.
-    if (wlr_layer_surface.current.committed.layer) {
-        const tree = server.scene.layerSurfaceTree(wlr_layer_surface.current.layer);
-        layer_surface.scene_layer_surface.tree.node.reparent(tree);
-    }
-
-    if (wlr_layer_surface.initial_commit or
-        @as(u32, @bitCast(wlr_layer_surface.current.committed)) != 0)
-    {
-        // Beware: it is possible for arrange() to destroy this LayerSurface!
-        const output: *Output = @ptrCast(@alignCast(layer_surface.wlr_layer_surface.output.?.data));
-        output.layer_shell.arrange();
-        server.layer_shell.checkExclusiveFocus();
-        server.wm.dirtyWindowing();
-    }
-}
-
-fn handleNewPopup(listener: *wl.Listener(*wlr.XdgPopup), wlr_xdg_popup: *wlr.XdgPopup) void {
-    const layer_surface: *LayerSurface = @fieldParentPtr("new_popup", listener);
-
-    XdgPopup.create(
-        wlr_xdg_popup,
-        layer_surface.popup_tree,
-        null,
-    ) catch {
-        wlr_xdg_popup.resource.postNoMemory();
-        return;
-    };
-}
diff --git a/river/LibinputAccelConfig.zig b/river/LibinputAccelConfig.zig
deleted file mode 100644
index 2553572..0000000
--- a/river/LibinputAccelConfig.zig
+++ /dev/null
@@ -1,111 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LibinputAccelConfig = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wayland = @import("wayland");
-const wl = wayland.server.wl;
-const river = wayland.server.river;
-
-const c = @import("c");
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const Keyboard = @import("Keyboard.zig");
-const Seat = @import("Seat.zig");
-
-const log = std.log.scoped(.input);
-
-object: *river.LibinputAccelConfigV1,
-libinput: ?*c.libinput_config_accel,
-
-pub fn create(
-    client: *wl.Client,
-    version: u32,
-    id: u32,
-    profile: c.enum_libinput_config_accel_profile,
-) !void {
-    const accel_config = try util.gpa.create(LibinputAccelConfig);
-    errdefer util.gpa.destroy(accel_config);
-    const object = try river.LibinputAccelConfigV1.create(client, version, id);
-    errdefer comptime unreachable;
-    accel_config.* = .{
-        .object = object,
-        .libinput = c.libinput_config_accel_create(profile),
-    };
-    object.setHandler(*LibinputAccelConfig, handleRequest, handleDestroy, accel_config);
-}
-
-fn handleDestroy(_: *river.LibinputAccelConfigV1, accel_config: *LibinputAccelConfig) void {
-    if (accel_config.libinput) |libinput| {
-        c.libinput_config_accel_destroy(libinput);
-    }
-    util.gpa.destroy(accel_config);
-}
-
-fn handleRequest(
-    object: *river.LibinputAccelConfigV1,
-    request: river.LibinputAccelConfigV1.Request,
-    accel_config: *LibinputAccelConfig,
-) void {
-    assert(accel_config.object == object);
-    switch (request) {
-        .destroy => object.destroy(),
-        .set_points => |args| {
-            const accel_type: c.enum_libinput_config_accel_type = switch (args.type) {
-                .fallback => c.LIBINPUT_ACCEL_TYPE_FALLBACK,
-                .motion => c.LIBINPUT_ACCEL_TYPE_MOTION,
-                .scroll => c.LIBINPUT_ACCEL_TYPE_SCROLL,
-                _ => {
-                    object.postError(.invalid_arg, "invalid accel_type enum value");
-                    return;
-                },
-            };
-            if (args.step.size != @sizeOf(f64)) {
-                object.postError(.invalid_arg, "invalid step argument");
-                return;
-            }
-            const step: f64 = args.step.slice(f64)[0];
-            if (args.points.size % @sizeOf(f64) != 0) {
-                object.postError(.invalid_arg, "invalid points argument");
-                return;
-            }
-            const points = util.gpa.alloc(f64, @divExact(args.points.size, @sizeOf(f64))) catch {
-                log.err("out of memory", .{});
-                object.postNoMemory();
-                return;
-            };
-            defer util.gpa.free(points);
-            @memcpy(
-                @as([]u8, @ptrCast(points)),
-                @as([*]u8, @ptrCast(args.points.data))[0..args.points.size],
-            );
-            const result = river.LibinputResultV1.create(
-                object.getClient(),
-                object.getVersion(),
-                args.result,
-            ) catch {
-                log.err("out of memory", .{});
-                object.postNoMemory();
-                return;
-            };
-            const libinput = accel_config.libinput orelse {
-                result.destroySendInvalid();
-                return;
-            };
-            switch (c.libinput_config_accel_set_points(
-                libinput,
-                accel_type,
-                step,
-                points.len,
-                points.ptr,
-            )) {
-                c.LIBINPUT_CONFIG_STATUS_SUCCESS => result.destroySendSuccess(),
-                c.LIBINPUT_CONFIG_STATUS_UNSUPPORTED => result.destroySendUnsupported(),
-                else => result.destroySendInvalid(),
-            }
-        },
-    }
-}
diff --git a/river/LibinputConfig.zig b/river/LibinputConfig.zig
deleted file mode 100644
index 4759fc8..0000000
--- a/river/LibinputConfig.zig
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LibinputConfig = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const wl = @import("wayland").server.wl;
-const river = @import("wayland").server.river;
-
-const c = @import("c");
-const server = &@import("main.zig").server;
-
-const LibinputAccelConfig = @import("LibinputAccelConfig.zig");
-const LibinputDevice = @import("LibinputDevice.zig");
-
-const log = std.log.scoped(.input);
-
-global: *wl.Global,
-objects: wl.list.Head(river.LibinputConfigV1, null),
-devices: wl.list.Head(LibinputDevice, .link),
-
-server_destroy: wl.Listener(*wl.Server) = .init(handleServerDestroy),
-
-pub fn init(config: *LibinputConfig) !void {
-    config.* = .{
-        .global = try wl.Global.create(server.wl_server, river.LibinputConfigV1, 2, *LibinputConfig, config, bind),
-        .objects = undefined,
-        .devices = undefined,
-    };
-    config.objects.init();
-    config.devices.init();
-    server.wl_server.addDestroyListener(&config.server_destroy);
-}
-
-fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
-    const config: *LibinputConfig = @fieldParentPtr("server_destroy", listener);
-
-    config.global.destroy();
-}
-
-fn bind(client: *wl.Client, config: *LibinputConfig, version: u32, id: u32) void {
-    const object = river.LibinputConfigV1.create(client, version, id) catch {
-        client.postNoMemory();
-        log.err("out of memory", .{});
-        return;
-    };
-    object.setHandler(*LibinputConfig, handleRequest, handleDestroy, config);
-    config.objects.append(object);
-    {
-        var it = config.devices.iterator(.forward);
-        while (it.next()) |device| device.createObject(object);
-    }
-}
-
-fn handleRequestInert(
-    object: *river.LibinputConfigV1,
-    request: river.LibinputConfigV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) object.destroy();
-}
-
-fn handleDestroy(object: *river.LibinputConfigV1, _: *LibinputConfig) void {
-    object.getLink().remove();
-}
-
-fn handleRequest(
-    object: *river.LibinputConfigV1,
-    request: river.LibinputConfigV1.Request,
-    _: *LibinputConfig,
-) void {
-    switch (request) {
-        .stop => {
-            object.getLink().remove();
-            object.sendFinished();
-            object.setHandler(?*anyopaque, handleRequestInert, null, null);
-        },
-        .destroy => {
-            object.postError(.invalid_destroy, "destroy before finished event sent");
-        },
-        .create_accel_config => |args| {
-            const profile: c.enum_libinput_config_accel_profile = switch (args.profile) {
-                .none => c.LIBINPUT_CONFIG_ACCEL_PROFILE_NONE,
-                .flat => c.LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
-                .adaptive => c.LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
-                .custom => c.LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM,
-                _ => {
-                    object.postError(.invalid_arg, "invalid accel_profile enum value");
-                    return;
-                },
-            };
-            LibinputAccelConfig.create(
-                object.getClient(),
-                object.getVersion(),
-                args.id,
-                profile,
-            ) catch {
-                log.err("out of memory", .{});
-                object.postNoMemory();
-                return;
-            };
-        },
-    }
-}
diff --git a/river/LibinputDevice.zig b/river/LibinputDevice.zig
deleted file mode 100644
index 507368f..0000000
--- a/river/LibinputDevice.zig
+++ /dev/null
@@ -1,583 +0,0 @@
-// SPDX-FileCopyrightText: © 2025 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LibinputDevice = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-const mem = std.mem;
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-const river = @import("wayland").server.river;
-
-const c = @import("c");
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const InputDevice = @import("InputDevice.zig");
-const LibinputAccelConfig = @import("LibinputAccelConfig.zig");
-
-const log = std.log.scoped(.input);
-
-libinput: *c.libinput_device,
-objects: wl.list.Head(river.LibinputDeviceV1, null),
-
-/// LibinputConfig.devices
-link: wl.list.Link,
-
-pub fn init(device: *LibinputDevice, handle: *c.libinput_device) void {
-    device.* = .{
-        .libinput = handle,
-        .objects = undefined,
-        .link = undefined,
-    };
-    device.objects.init();
-    server.libinput_config.devices.append(device);
-    {
-        var it = server.libinput_config.objects.iterator(.forward);
-        while (it.next()) |config_v1| device.createObject(config_v1);
-    }
-}
-
-pub fn createObject(device: *LibinputDevice, config_v1: *river.LibinputConfigV1) void {
-    const object = river.LibinputDeviceV1.create(config_v1.getClient(), config_v1.getVersion(), 0) catch {
-        log.err("out of memory", .{});
-        config_v1.postNoMemory();
-        return;
-    };
-    device.objects.append(object);
-    object.setHandler(*LibinputDevice, handleRequest, handleDestroy, device);
-    config_v1.sendLibinputDevice(object);
-    {
-        const base: *InputDevice = @fieldParentPtr("libinput", device);
-        assert(!base.virtual);
-        var it = base.objects.iterator(.forward);
-        while (it.next()) |input_device_v1| {
-            if (object.getClient() == input_device_v1.getClient()) {
-                object.sendInputDevice(input_device_v1);
-            }
-        }
-    }
-    object.sendSendEventsSupport(@bitCast(c.libinput_device_config_send_events_get_modes(device.libinput)));
-    object.sendSendEventsDefault(@bitCast(c.libinput_device_config_send_events_get_default_mode(device.libinput)));
-    object.sendSendEventsCurrent(@bitCast(c.libinput_device_config_send_events_get_mode(device.libinput)));
-    const tap_finger_count = c.libinput_device_config_tap_get_finger_count(device.libinput);
-    object.sendTapSupport(tap_finger_count);
-    if (tap_finger_count > 0) {
-        object.sendTapDefault(@enumFromInt(c.libinput_device_config_tap_get_default_enabled(device.libinput)));
-        object.sendTapCurrent(@enumFromInt(c.libinput_device_config_tap_get_enabled(device.libinput)));
-        object.sendTapButtonMapDefault(@enumFromInt(c.libinput_device_config_tap_get_default_button_map(device.libinput)));
-        object.sendTapButtonMapCurrent(@enumFromInt(c.libinput_device_config_tap_get_button_map(device.libinput)));
-        object.sendDragDefault(@enumFromInt(c.libinput_device_config_tap_get_default_drag_enabled(device.libinput)));
-        object.sendDragCurrent(@enumFromInt(c.libinput_device_config_tap_get_drag_enabled(device.libinput)));
-        object.sendDragLockDefault(@enumFromInt(c.libinput_device_config_tap_get_default_drag_lock_enabled(device.libinput)));
-        object.sendDragLockCurrent(@enumFromInt(c.libinput_device_config_tap_get_drag_lock_enabled(device.libinput)));
-    }
-    const three_finger_drag_finger_count = c.libinput_device_config_3fg_drag_get_finger_count(device.libinput);
-    object.sendThreeFingerDragSupport(three_finger_drag_finger_count);
-    if (three_finger_drag_finger_count >= 3) {
-        object.sendThreeFingerDragDefault(@enumFromInt(c.libinput_device_config_3fg_drag_get_default_enabled(device.libinput)));
-        object.sendThreeFingerDragCurrent(@enumFromInt(c.libinput_device_config_3fg_drag_get_enabled(device.libinput)));
-    }
-    const has_matrix = c.libinput_device_config_calibration_has_matrix(device.libinput);
-    object.sendCalibrationMatrixSupport(has_matrix);
-    if (has_matrix != 0) {
-        var matrix: [6]f32 = undefined;
-        const bytes: []u8 = @ptrCast(&matrix);
-        var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
-        _ = c.libinput_device_config_calibration_get_default_matrix(device.libinput, &matrix);
-        object.sendCalibrationMatrixDefault(&array);
-        _ = c.libinput_device_config_calibration_get_matrix(device.libinput, &matrix);
-        object.sendCalibrationMatrixCurrent(&array);
-    }
-    const profiles = c.libinput_device_config_accel_get_profiles(device.libinput);
-    object.sendAccelProfilesSupport(@bitCast(profiles));
-    if (profiles != 0) {
-        object.sendAccelProfileDefault(@enumFromInt(c.libinput_device_config_accel_get_default_profile(device.libinput)));
-        object.sendAccelProfileCurrent(@enumFromInt(c.libinput_device_config_accel_get_profile(device.libinput)));
-        var speed: [1]f64 = .{c.libinput_device_config_accel_get_default_speed(device.libinput)};
-        const bytes: []u8 = @ptrCast(&speed);
-        var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
-        object.sendAccelSpeedDefault(&array);
-        speed = .{c.libinput_device_config_accel_get_speed(device.libinput)};
-        object.sendAccelSpeedCurrent(&array);
-    }
-    const natural_scroll = c.libinput_device_config_scroll_has_natural_scroll(device.libinput);
-    object.sendNaturalScrollSupport(natural_scroll);
-    if (natural_scroll != 0) {
-        const default = c.libinput_device_config_scroll_get_default_natural_scroll_enabled(device.libinput);
-        object.sendNaturalScrollDefault(if (default != 0) .enabled else .disabled);
-        const current = c.libinput_device_config_scroll_get_natural_scroll_enabled(device.libinput);
-        object.sendNaturalScrollCurrent(if (current != 0) .enabled else .disabled);
-    }
-    const left_handed = c.libinput_device_config_left_handed_is_available(device.libinput);
-    object.sendLeftHandedSupport(left_handed);
-    if (left_handed != 0) {
-        const default = c.libinput_device_config_left_handed_get_default(device.libinput);
-        object.sendLeftHandedDefault(if (default != 0) .enabled else .disabled);
-        const current = c.libinput_device_config_left_handed_get(device.libinput);
-        object.sendLeftHandedCurrent(if (current != 0) .enabled else .disabled);
-    }
-    const click_methods = c.libinput_device_config_click_get_methods(device.libinput);
-    object.sendClickMethodSupport(@bitCast(click_methods));
-    if (click_methods != 0) {
-        object.sendClickMethodDefault(@enumFromInt(c.libinput_device_config_click_get_default_method(device.libinput)));
-        object.sendClickMethodCurrent(@enumFromInt(c.libinput_device_config_click_get_method(device.libinput)));
-        if (click_methods & c.LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER != 0) {
-            object.sendClickfingerButtonMapDefault(@enumFromInt(c.libinput_device_config_click_get_default_clickfinger_button_map(device.libinput)));
-            object.sendClickfingerButtonMapCurrent(@enumFromInt(c.libinput_device_config_click_get_clickfinger_button_map(device.libinput)));
-        }
-    }
-    const middle_emulation = c.libinput_device_config_middle_emulation_is_available(device.libinput);
-    object.sendMiddleEmulationSupport(middle_emulation);
-    if (middle_emulation != 0) {
-        object.sendMiddleEmulationDefault(@enumFromInt(c.libinput_device_config_middle_emulation_get_default_enabled(device.libinput)));
-        object.sendMiddleEmulationCurrent(@enumFromInt(c.libinput_device_config_middle_emulation_get_enabled(device.libinput)));
-    }
-    const scroll_methods = c.libinput_device_config_scroll_get_methods(device.libinput);
-    object.sendScrollMethodSupport(@bitCast(scroll_methods));
-    if (scroll_methods != 0) {
-        object.sendScrollMethodDefault(@enumFromInt(c.libinput_device_config_scroll_get_default_method(device.libinput)));
-        object.sendScrollMethodCurrent(@enumFromInt(c.libinput_device_config_scroll_get_method(device.libinput)));
-        if (scroll_methods & c.LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN != 0) {
-            object.sendScrollButtonDefault(c.libinput_device_config_scroll_get_default_button(device.libinput));
-            object.sendScrollButtonCurrent(c.libinput_device_config_scroll_get_button(device.libinput));
-            object.sendScrollButtonLockDefault(@enumFromInt(c.libinput_device_config_scroll_get_default_button_lock(device.libinput)));
-            object.sendScrollButtonLockCurrent(@enumFromInt(c.libinput_device_config_scroll_get_button_lock(device.libinput)));
-        }
-    }
-    const dwt = c.libinput_device_config_dwt_is_available(device.libinput);
-    object.sendDwtSupport(dwt);
-    if (dwt != 0) {
-        const default = c.libinput_device_config_dwt_get_default_enabled(device.libinput);
-        object.sendDwtDefault(if (default != 0) .enabled else .disabled);
-        const current = c.libinput_device_config_dwt_get_enabled(device.libinput);
-        object.sendDwtCurrent(if (current != 0) .enabled else .disabled);
-    }
-    const dwtp = c.libinput_device_config_dwtp_is_available(device.libinput);
-    object.sendDwtpSupport(dwtp);
-    if (dwtp != 0) {
-        const default = c.libinput_device_config_dwtp_get_default_enabled(device.libinput);
-        object.sendDwtpDefault(if (default != 0) .enabled else .disabled);
-        const current = c.libinput_device_config_dwtp_get_enabled(device.libinput);
-        object.sendDwtpCurrent(if (current != 0) .enabled else .disabled);
-    }
-    const rotation = c.libinput_device_config_rotation_is_available(device.libinput);
-    object.sendRotationSupport(rotation);
-    if (rotation != 0) {
-        object.sendRotationDefault(c.libinput_device_config_rotation_get_default_angle(device.libinput));
-        object.sendRotationCurrent(c.libinput_device_config_rotation_get_angle(device.libinput));
-    }
-    if (object.getVersion() >= 2) {
-        object.sendDone();
-    }
-}
-
-pub fn deinit(device: *LibinputDevice) void {
-    {
-        var it = device.objects.iterator(.forward);
-        while (it.next()) |object| {
-            object.getLink().remove();
-            object.sendRemoved();
-            object.setHandler(?*anyopaque, handleRequestInert, null, null);
-        }
-    }
-    assert(device.objects.empty());
-    device.link.remove();
-}
-
-fn handleRequestInert(
-    object: *river.LibinputDeviceV1,
-    request: river.LibinputDeviceV1.Request,
-    _: ?*anyopaque,
-) void {
-    if (request == .destroy) object.destroy();
-}
-
-fn handleDestroy(object: *river.LibinputDeviceV1, _: *LibinputDevice) void {
-    object.getLink().remove();
-}
-
-fn handleRequest(
-    object: *river.LibinputDeviceV1,
-    request: river.LibinputDeviceV1.Request,
-    device: *LibinputDevice,
-) void {
-    var send_done = false;
-    switch (request) {
-        .destroy => object.destroy(),
-        .set_send_events => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_send_events_set_mode(device.libinput, @bitCast(args.mode));
-            if (result.send(status)) {
-                const current = c.libinput_device_config_send_events_get_mode(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendSendEventsCurrent(@bitCast(current));
-                send_done = true;
-            }
-        },
-        .set_tap => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_tap_set_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_TAP_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_TAP_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid tap_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_tap_get_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendTapCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_tap_button_map => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_tap_set_button_map(device.libinput, switch (args.button_map) {
-                .lrm => c.LIBINPUT_CONFIG_TAP_MAP_LRM,
-                .lmr => c.LIBINPUT_CONFIG_TAP_MAP_LMR,
-                _ => {
-                    object.postError(.invalid_arg, "invalid tap_button_map enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_tap_get_button_map(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendTapButtonMapCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_drag => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_tap_set_drag_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_DRAG_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_DRAG_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid drag_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_tap_get_drag_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendDragCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_drag_lock => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_tap_set_drag_lock_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_DRAG_LOCK_DISABLED,
-                .enabled_timeout => c.LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_TIMEOUT,
-                .enabled_sticky => c.LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY,
-                _ => {
-                    object.postError(.invalid_arg, "invalid drag_lock_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_tap_get_drag_lock_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendDragLockCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_three_finger_drag => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_3fg_drag_set_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_3FG_DRAG_DISABLED,
-                .enabled_3fg => c.LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG,
-                .enabled_4fg => c.LIBINPUT_CONFIG_3FG_DRAG_ENABLED_4FG,
-                _ => {
-                    object.postError(.invalid_arg, "invalid three_finger_drag_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_3fg_drag_get_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendThreeFingerDragCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_calibration_matrix => |args| {
-            if (args.matrix.size != @sizeOf([6]f32)) {
-                object.postError(.invalid_arg, "invalid calibration matrix");
-                return;
-            }
-            const matrix = args.matrix.slice(f32)[0..6];
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_calibration_set_matrix(device.libinput, matrix);
-            if (result.send(status)) {
-                var current: [6]f32 = undefined;
-                const bytes: []u8 = @ptrCast(&current);
-                var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
-                _ = c.libinput_device_config_calibration_get_matrix(device.libinput, &current);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendCalibrationMatrixCurrent(&array);
-                send_done = true;
-            }
-        },
-        .set_accel_profile => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_accel_set_profile(device.libinput, switch (args.profile) {
-                .none => c.LIBINPUT_CONFIG_ACCEL_PROFILE_NONE,
-                .flat => c.LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT,
-                .adaptive => c.LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE,
-                .custom => c.LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM,
-                _ => {
-                    object.postError(.invalid_arg, "invalid accel_profile enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_accel_get_profile(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_accel_speed => |args| {
-            if (args.speed.size != @sizeOf(f64)) {
-                object.postError(.invalid_arg, "invalid accel speed");
-                return;
-            }
-            const speed = args.speed.slice(f64)[0];
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_accel_set_speed(device.libinput, speed);
-            if (result.send(status)) {
-                var current: [1]f64 = .{c.libinput_device_config_accel_get_speed(device.libinput)};
-                const bytes: []u8 = @ptrCast(&current);
-                var array: wl.Array = .{ .size = bytes.len, .alloc = bytes.len, .data = bytes.ptr };
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendAccelSpeedCurrent(&array);
-                send_done = true;
-            }
-        },
-        .apply_accel_config => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const accel_config: *LibinputAccelConfig = @ptrCast(@alignCast(args.config.getUserData()));
-            const config = accel_config.libinput orelse {
-                _ = result.send(c.LIBINPUT_CONFIG_STATUS_INVALID);
-                return;
-            };
-            const status = c.libinput_device_config_accel_apply(device.libinput, config);
-            if (result.send(status)) {
-                const current = c.libinput_device_config_accel_get_profile(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendAccelProfileCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_natural_scroll => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_scroll_set_natural_scroll_enabled(device.libinput, switch (args.state) {
-                .disabled => 0,
-                .enabled => 1,
-                _ => {
-                    object.postError(.invalid_arg, "invalid natural_scroll_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_scroll_get_natural_scroll_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendNaturalScrollCurrent(if (current != 0) .enabled else .disabled);
-                send_done = true;
-            }
-        },
-        .set_left_handed => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_left_handed_set(device.libinput, switch (args.state) {
-                .disabled => 0,
-                .enabled => 1,
-                _ => {
-                    object.postError(.invalid_arg, "invalid left_handed_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_left_handed_get(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendLeftHandedCurrent(if (current != 0) .enabled else .disabled);
-                send_done = true;
-            }
-        },
-        .set_click_method => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_click_set_method(device.libinput, switch (args.method) {
-                .none => c.LIBINPUT_CONFIG_CLICK_METHOD_NONE,
-                .button_areas => c.LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS,
-                .clickfinger => c.LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER,
-                _ => {
-                    object.postError(.invalid_arg, "invalid click_method enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_click_get_method(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendClickMethodCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_clickfinger_button_map => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_click_set_clickfinger_button_map(device.libinput, switch (args.button_map) {
-                .lrm => c.LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM,
-                .lmr => c.LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR,
-                _ => {
-                    object.postError(.invalid_arg, "invalid clickfinger_button_map enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_click_get_clickfinger_button_map(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendClickfingerButtonMapCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_middle_emulation => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_middle_emulation_set_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid middle_emulation_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_middle_emulation_get_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendMiddleEmulationCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_scroll_method => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_scroll_set_method(device.libinput, switch (args.method) {
-                .no_scroll => c.LIBINPUT_CONFIG_SCROLL_NO_SCROLL,
-                .two_finger => c.LIBINPUT_CONFIG_SCROLL_2FG,
-                .edge => c.LIBINPUT_CONFIG_SCROLL_EDGE,
-                .on_button_down => c.LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN,
-                _ => {
-                    object.postError(.invalid_arg, "invalid scroll_method enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_scroll_get_method(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendScrollMethodCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_scroll_button => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_scroll_set_button(device.libinput, args.button);
-            if (result.send(status)) {
-                const current = c.libinput_device_config_scroll_get_button(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendScrollButtonCurrent(current);
-                send_done = true;
-            }
-        },
-        .set_scroll_button_lock => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_scroll_set_button_lock(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid scroll_button_lock_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_scroll_get_button_lock(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendScrollButtonLockCurrent(@enumFromInt(current));
-                send_done = true;
-            }
-        },
-        .set_dwt => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_dwt_set_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_DWT_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_DWT_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid dwt_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_dwt_get_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendDwtCurrent(if (current != 0) .enabled else .disabled);
-                send_done = true;
-            }
-        },
-        .set_dwtp => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_dwtp_set_enabled(device.libinput, switch (args.state) {
-                .disabled => c.LIBINPUT_CONFIG_DWTP_DISABLED,
-                .enabled => c.LIBINPUT_CONFIG_DWTP_ENABLED,
-                _ => {
-                    object.postError(.invalid_arg, "invalid dwtp_state enum value");
-                    return;
-                },
-            });
-            if (result.send(status)) {
-                const current = c.libinput_device_config_dwtp_get_enabled(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendDwtpCurrent(if (current != 0) .enabled else .disabled);
-                send_done = true;
-            }
-        },
-        .set_rotation => |args| {
-            const result = Result.create(object, args.result) orelse return;
-            const status = c.libinput_device_config_rotation_set_angle(device.libinput, args.angle);
-            if (result.send(status)) {
-                const current = c.libinput_device_config_rotation_get_angle(device.libinput);
-                var it = device.objects.iterator(.forward);
-                while (it.next()) |o| o.sendRotationCurrent(current);
-                send_done = true;
-            }
-        },
-    }
-
-    if (send_done) {
-        var it = device.objects.iterator(.forward);
-        while (it.next()) |o| {
-            if (o.getVersion() >= 2) {
-                o.sendDone();
-            }
-        }
-    }
-}
-
-const Result = struct {
-    object: *river.LibinputResultV1,
-
-    pub fn create(object: *river.LibinputDeviceV1, id: u32) ?Result {
-        const result = river.LibinputResultV1.create(object.getClient(), object.getVersion(), id) catch {
-            log.err("out of memory", .{});
-            object.postNoMemory();
-            return null;
-        };
-        return .{ .object = result };
-    }
-
-    pub fn send(result: *const Result, status: u32) bool {
-        switch (status) {
-            c.LIBINPUT_CONFIG_STATUS_SUCCESS => result.object.destroySendSuccess(),
-            c.LIBINPUT_CONFIG_STATUS_UNSUPPORTED => result.object.destroySendUnsupported(),
-            else => result.object.destroySendInvalid(),
-        }
-        return status == c.LIBINPUT_CONFIG_STATUS_SUCCESS;
-    }
-};
diff --git a/river/LockManager.zig b/river/LockManager.zig
deleted file mode 100644
index 75c74d9..0000000
--- a/river/LockManager.zig
+++ /dev/null
@@ -1,245 +0,0 @@
-// SPDX-FileCopyrightText: © 2021 The River Developers
-// SPDX-License-Identifier: GPL-3.0-only
-
-const LockManager = @This();
-
-const std = @import("std");
-const assert = std.debug.assert;
-
-const build_options = @import("build_options");
-
-const wlr = @import("wlroots");
-const wl = @import("wayland").server.wl;
-
-const server = &@import("main.zig").server;
-const util = @import("util.zig");
-
-const LockSurface = @import("LockSurface.zig");
-const Output = @import("Output.zig");
-
-const log = std.log.scoped(.lock);
-
-wlr_manager: *wlr.SessionLockManagerV1,
-
-state: enum {
-    /// No lock request has been made and the session is unlocked.
-    unlocked,
-    /// A lock request has been made and river is waiting for all outputs to have
-    /// rendered a lock surface before sending the locked event.
-    waiting_for_lock_surfaces,
-    /// A lock request has been made but waiting for a lock surface to be rendered
-    /// on all outputs timed out. Now river is waiting only for all outputs to at
-    /// least be blanked before sending the locked event.
-    waiting_for_blank,
-    /// All outputs are either blanked or have a lock surface rendered and the
-    /// locked event has been sent.
-    locked,
-} = .unlocked,
-lock: ?*wlr.SessionLockV1 = null,
-
-/// Limit on how long the locked event will be delayed to wait for
-/// lock surfaces to be created and rendered. If this times out, then
-/// the locked event will be sent immediately after all outputs have
-/// been blanked.
-lock_surfaces_timer: *wl.EventSource,
-
-new_lock: wl.Listener(*wlr.SessionLockV1) = .init(handleLock),
-unlock: wl.Listener(void) = .init(handleUnlock),
-destroy: wl.Listener(void) = .init(handleDestroy),
-new_surface: wl.Listener(*wlr.SessionLockSurfaceV1) = .init(handleSurface),
-
-pub fn init(manager: *LockManager) !void {
-    const event_loop = server.wl_server.getEventLoop();
-    const timer = try event_loop.addTimer(*LockManager, handleLockSurfacesTimeout, manager);
-    errdefer timer.remove();
-
-    manager.* = .{
-        .wlr_manager = try wlr.SessionLockManagerV1.create(server.wl_server),
-        .lock_surfaces_timer = timer,
-    };
-
-    manager.wlr_manager.events.new_lock.add(&manager.new_lock);
-}
-
-pub fn deinit(manager: *LockManager) void {
-    // deinit() should only be called after wl.Server.destroyClients()
-    assert(manager.lock == null);
-
-    manager.lock_surfaces_timer.remove();
-
-    manager.new_lock.link.remove();
-}
-
-fn handleLock(listener: *wl.Listener(*wlr.SessionLockV1), lock: *wlr.SessionLockV1) void {
-    const manager: *LockManager = @fieldParentPtr("new_lock", listener);
-
-    log.debug("session lock client made lock request", .{});
-
-    if (manager.lock != null) {
-        log.info("denying new session lock client, an active one already exists", .{});
-        lock.destroy();
-        return;
-    }
-
-    manager.lock = lock;
-
-    if (manager.state == .unlocked) {
-        manager.state = .waiting_for_lock_surfaces;
-
-        assert(!server.scene.locked_tree.node.enabled);
-        server.scene.locked_tree.node.setEnabled(true);
-
-        manager.lock_surfaces_timer.timerUpdate(200) catch {
-            log.err("error setting lock surfaces timer, imperfect frames may be shown", .{});
-            manager.state = .waiting_for_blank;
-            // This call is necessary in the case that all outputs in the layout are disabled.
-            manager.maybeLock();
-        };
-
-        {
-            var it = server.input_manager.seats.iterator(.forward);
-            while (it.next()) |seat| {
-                seat.focus(.none);
-            }
-        }
-    } else {
-        if (manager.state == .locked) {
-            lock.sendLocked();
-        }
-
-        log.info("n
diff truncated