about summary refs log tree commit diff stats
path: root/pkgs/by-name/ri/river-mk-keymap/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/cli.rs14
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs373
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs105
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/main.rs58
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs173
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs214
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs272
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs57
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs129
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs1
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs28
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs21
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs437
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs290
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs596
15 files changed, 2614 insertions, 154 deletions
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
index e3c49310..61646cfd 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/cli.rs
@@ -16,6 +16,16 @@ use clap::Parser;
 #[command(author, version, about, long_about = None)]
 /// A tool to manage your key mappings for the river window manager
 pub(super) struct Args {
-    /// Path to mappings JSON file
-    pub path: PathBuf,
+    #[command(subcommand)]
+    pub command: SubCommand,
+
+    #[arg(long, short)]
+    /// Path to mapping config JSON file
+    pub keymap: PathBuf,
+}
+
+#[derive(clap::Subcommand, Clone, Debug)]
+pub(super) enum SubCommand {
+    Init {},
+    ShowHelp {},
 }
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
index e948ccfe..07c41918 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
@@ -8,112 +8,289 @@
 // You should have received a copy of the License along with this program.
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
-use std::process::Command;
+use std::{env::current_exe, path::Path, process::Command};
 
-use keymaps::key_repr::{KeyValue, MediaKeyCode, MouseKeyValue};
+use anyhow::{bail, Result};
+use keymaps::key_repr::{Key, KeyValue, Keys, MediaKeyCode, ModifierKeyCode, MouseKeyValue};
+use rustix::path::Arg;
 
-use super::{KeyMap, MapMode};
+use super::KeyMap;
 
 impl KeyMap {
-    #[must_use]
-    pub fn to_commands(self) -> Vec<Command> {
-        self.0
-            .iter()
-            .flat_map(|(key, value)| {
-                let key = key.last().expect("Will exist");
-                let mods = {
-                    let modifiers = key.modifiers();
-                    let mut output = vec![];
-
-                    if modifiers.alt() {
-                        output.push("Alt");
-                    }
-                    if modifiers.ctrl() {
-                        output.push("Control");
-                    }
-                    if modifiers.meta() {
-                        output.push("Super");
-                    }
-                    if modifiers.shift() {
-                        output.push("Shift");
-                    }
-                    if output.is_empty() {
-                        "None".to_owned()
-                    } else {
-                        output.join("+")
-                    }
-                };
-                let key_value = match key.value() {
-                    KeyValue::Backspace => "BackSpace".to_owned(),
-                    KeyValue::Enter => "Enter".to_owned(),
-                    KeyValue::Left => "Left".to_owned(),
-                    KeyValue::Right => "Right".to_owned(),
-                    KeyValue::Up => "Up".to_owned(),
-                    KeyValue::Down => "Down".to_owned(),
-                    KeyValue::Home => "Home".to_owned(),
-                    KeyValue::End => "End".to_owned(),
-                    KeyValue::PageUp => "Page_Up".to_owned(),
-                    KeyValue::PageDown => "Page_Down".to_owned(),
-                    KeyValue::Tab => "Tab".to_owned(),
-                    KeyValue::BackTab => "BackTab".to_owned(),
-                    KeyValue::Delete => "Delete".to_owned(),
-                    KeyValue::Insert => "Insert".to_owned(),
-                    KeyValue::F(num) => format!("F{num}"),
-                    KeyValue::Char(a) => a.to_string(),
-                    KeyValue::Null => "Null".to_owned(),
-                    KeyValue::Esc => "Esc".to_owned(),
-                    KeyValue::CapsLock => "CapsLock".to_owned(),
-                    KeyValue::ScrollLock => "ScrollLock".to_owned(),
-                    KeyValue::NumLock => "NumLock".to_owned(),
-                    KeyValue::PrintScreen => "Print".to_owned(),
-                    KeyValue::Pause => "Pause".to_owned(),
-                    KeyValue::Menu => "Menu".to_owned(),
-                    KeyValue::KeypadBegin => "KeypadBegin".to_owned(),
-                    KeyValue::Media(media_key_code) => match media_key_code {
-                        MediaKeyCode::Play => "XF86AudioPlay".to_owned(),
-                        MediaKeyCode::Pause => "XF86AudioPause".to_owned(),
-                        MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(),
-                        MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(),
-                        MediaKeyCode::Stop => "XF86AudioStop".to_owned(),
-                        MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(),
-                        MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(),
-                        MediaKeyCode::TrackNext => "XF86AudioTrackNext".to_owned(),
-                        MediaKeyCode::TrackPrevious => "XF86AudioTrackPrevious".to_owned(),
-                        MediaKeyCode::Record => "XF86AudioRecord".to_owned(),
-                        MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(),
-                        MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(),
-                        MediaKeyCode::MuteVolume => "XF86AudioMuteVolume".to_owned(),
-                    },
-                    KeyValue::MouseKey(mouse_key_value) => match mouse_key_value {
-                        MouseKeyValue::Left => "BTN_LEFT".to_owned(),
-                        MouseKeyValue::Right => "BTN_RIGHT".to_owned(),
-                        MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(),
-                    },
-                    _ => todo!(),
-                };
-
-                value
-                    .modes
-                    .iter()
-                    .map(|mode| {
-                        let mut riverctl = Command::new("riverctl");
-                        riverctl.args([value.map_mode.as_command(), mode, &mods, &key_value]);
-
-                        riverctl.args(value.command.iter().map(String::as_str));
-                        riverctl
-                    })
-                    .collect::<Vec<_>>()
+    /// # Errors
+    /// If impossible requests are made.
+    ///
+    /// # Panics
+    /// If internal assertions fail.
+    #[allow(clippy::too_many_lines)]
+    pub fn to_commands(self, keymap_path: &Path) -> Result<Vec<Command>> {
+        self.0.iter().try_for_each(|(keys, value)| {
+            let (prefix, last) = keys.split_at(keys.len() - 1);
+            let prefix = prefix.to_owned();
+
+            if value.allow_locked && !prefix.is_empty() {
+                bail!(
+                    "Only single key mappings can be used \
+                        in locked mode, but '{}' contains multiple ('{}').",
+                    Keys::from(keys),
+                    Keys::from(prefix),
+                )
+            }
+
+            if !prefix.is_empty()
+                && [
+                    "<ESC>".parse().expect("hardcoded"),
+                    "<BACKSPACE>".parse().expect("hardcoded"),
+                ]
+                .contains(&last[0])
+            {
+                bail!(
+                    "You cannot use <ESC> or <BACKSPACE> as the final part of a \
+                        prefixed mapping, as that is used to return \
+                        to 'normal' or the upper mode; found in '{}'",
+                    Keys::from(keys),
+                )
+            }
+
+            Ok(())
+        })?;
+
+        let output = self
+            .0
+            .into_iter()
+            .flat_map(|(keys, value)| {
+                let (prefix, mapping) = keys.split_at(keys.len() - 1);
+
+                let (final_mode, mut base): (Option<String>, _) =
+                    prefix
+                        .iter()
+                        .fold((None, vec![]), |(acc_mode, mut acc_vec), key| {
+                            // Declare intermediate modes for each key.
+                            let mode_name: String = {
+                                let base = key.to_string_repr();
+
+                                if let Some(result) = &acc_mode {
+                                    result.to_owned() + base.as_str()
+                                } else {
+                                    base
+                                }
+                            };
+
+                            let mut riverctl = Command::new("riverctl");
+                            riverctl.args(["declare-mode", mode_name.as_str()]);
+
+                            let mut output = vec![riverctl];
+
+                            // Provide keymaps for entering and leaving the mode
+                            if let Some(acc_mode) = acc_mode.clone() {
+                                output.extend(key_to_command(
+                                    key.to_owned(),
+                                    &["enter-mode".to_owned(), mode_name.clone()],
+                                    &acc_mode,
+                                    false,
+                                ));
+                            } else {
+                                // Also spawn the help display if we start from the “normal” mode.
+                                output.extend(key_to_command(
+                                    key.to_owned(),
+                                    &[
+                                        "spawn".to_owned(),
+                                        format!(
+                                            "{} && sleep 1 && {}",
+                                            shlex::try_join([
+                                                "riverctl",
+                                                "enter-mode",
+                                                mode_name.as_str()
+                                            ])
+                                            .expect("Should work"),
+                                            shlex::try_join([
+                                                current_exe()
+                                                    .expect("Should have a current exe")
+                                                    .as_os_str()
+                                                    .as_str()
+                                                    .expect("Should be valid utf8"),
+                                                "--keymap",
+                                                keymap_path.to_str().expect("Should be valid utf8"),
+                                                "show-help",
+                                            ])
+                                            .expect("Should work"),
+                                        ),
+                                    ],
+                                    "normal",
+                                    false,
+                                ));
+                            }
+
+                            // Provide a mapping for going up a mode
+                            output.extend(key_to_command(
+                                "<BACKSPACE>".parse().expect("Hardcoded"),
+                                &[
+                                    "enter-mode".to_owned(),
+                                    acc_mode.unwrap_or("normal".to_owned()),
+                                ],
+                                &mode_name,
+                                false,
+                            ));
+
+                            // Another one for going back to normal.
+                            output.extend(key_to_command(
+                                "<ESC>".parse().expect("Hardcoded"),
+                                &["enter-mode".to_owned(), "normal".to_owned()],
+                                &mode_name,
+                                false,
+                            ));
+
+                            acc_vec.extend(output);
+
+                            (Some(mode_name), acc_vec)
+                        });
+
+                base.extend(key_to_command(
+                    mapping[0],
+                    &value.command,
+                    final_mode.as_ref().map_or("normal", |v| v.as_str()),
+                    value.allow_locked,
+                ));
+
+                base
             })
-            .collect()
+            .collect();
+
+        Ok(output)
     }
 }
 
-impl MapMode {
-    pub(crate) fn as_command(self) -> &'static str {
-        match self {
-            MapMode::Map => "map",
-            MapMode::MapMouse => "map-pointer",
-            MapMode::Unmap => "unmap",
+fn key_value_to_xkb_common_name(value: KeyValue) -> (String, Vec<&'static str>) {
+    let mut extra_modifiers = vec![];
+
+    let output = match value {
+        KeyValue::Backspace => "BackSpace".to_owned(),
+        KeyValue::Enter => "Return".to_owned(),
+        KeyValue::Left => "Left".to_owned(),
+        KeyValue::Right => "Right".to_owned(),
+        KeyValue::Up => "Up".to_owned(),
+        KeyValue::Down => "Down".to_owned(),
+        KeyValue::Home => "Home".to_owned(),
+        KeyValue::End => "End".to_owned(),
+        KeyValue::PageUp => "Page_Up".to_owned(),
+        KeyValue::PageDown => "Page_Down".to_owned(),
+        KeyValue::Tab => "Tab".to_owned(),
+        KeyValue::BackTab => "BackTab".to_owned(),
+        KeyValue::Delete => "Delete".to_owned(),
+        KeyValue::Insert => "Insert".to_owned(),
+        KeyValue::F(num) => format!("F{num}"),
+        KeyValue::Char(a) => {
+            // River does not differentiate between 'a' and 'A',
+            // so we need to do it beforehand.
+            if a.is_ascii_uppercase() {
+                extra_modifiers.push("Shift");
+            }
+
+            if a == ' ' {
+                "Space".to_string()
+            } else {
+                a.to_string()
+            }
+        }
+        KeyValue::Null => "Null".to_owned(),
+        KeyValue::Esc => "Escape".to_owned(),
+        KeyValue::CapsLock => "CapsLock".to_owned(),
+        KeyValue::ScrollLock => "ScrollLock".to_owned(),
+        KeyValue::NumLock => "NumLock".to_owned(),
+        KeyValue::PrintScreen => "Print".to_owned(),
+        KeyValue::Pause => "Pause".to_owned(),
+        KeyValue::Menu => "Menu".to_owned(),
+        KeyValue::KeypadBegin => "KeypadBegin".to_owned(),
+        KeyValue::Media(media_key_code) => match media_key_code {
+            MediaKeyCode::Play => "XF86AudioPlay".to_owned(),
+            MediaKeyCode::Pause => "XF86AudioPause".to_owned(),
+            MediaKeyCode::PlayPause => "XF86AudioPlayPause".to_owned(),
+            MediaKeyCode::Reverse => "XF86AudioReverse".to_owned(),
+            MediaKeyCode::Stop => "XF86AudioStop".to_owned(),
+            MediaKeyCode::FastForward => "XF86AudioFastForward".to_owned(),
+            MediaKeyCode::Rewind => "XF86AudioRewind".to_owned(),
+            MediaKeyCode::TrackNext => "XF86AudioTrackNext".to_owned(),
+            MediaKeyCode::TrackPrevious => "XF86AudioTrackPrevious".to_owned(),
+            MediaKeyCode::Record => "XF86AudioRecord".to_owned(),
+            MediaKeyCode::LowerVolume => "XF86AudioLowerVolume".to_owned(),
+            MediaKeyCode::RaiseVolume => "XF86AudioRaiseVolume".to_owned(),
+            MediaKeyCode::MuteVolume => "XF86AudioMute".to_owned(),
+        },
+        KeyValue::MouseKey(mouse_key_value) => match mouse_key_value {
+            MouseKeyValue::Left => "BTN_LEFT".to_owned(),
+            MouseKeyValue::Right => "BTN_RIGHT".to_owned(),
+            MouseKeyValue::Middle => "BTN_MIDDLE".to_owned(),
+        },
+        KeyValue::ModifierKey(modifier_key_code) => match modifier_key_code {
+            ModifierKeyCode::LeftAlt => "ALT_L".to_owned(),
+            ModifierKeyCode::RightAlt => "ALT_R".to_owned(),
+            ModifierKeyCode::LeftCtrl => "CTRL_L".to_owned(),
+            ModifierKeyCode::RightCtrl => "CTRL_R".to_owned(),
+            ModifierKeyCode::LeftMeta => "SUPER_L".to_owned(),
+            ModifierKeyCode::RightMeta => "SUPER_R".to_owned(),
+            ModifierKeyCode::LeftShift => "SHIFT_L".to_owned(),
+            ModifierKeyCode::RightShift => "SHIFT_R".to_owned(),
+        },
+        other => todo!("Key value: {other} not known."),
+    };
+
+    (output, extra_modifiers)
+}
+
+fn key_to_command(key: Key, command: &[String], mode: &str, allow_locked: bool) -> Vec<Command> {
+    let mut modifiers = {
+        let modifiers = key.modifiers();
+        let mut output = vec![];
+
+        if modifiers.alt() {
+            output.push("Alt");
         }
+        if modifiers.ctrl() {
+            output.push("Control");
+        }
+        if modifiers.meta() {
+            output.push("Super");
+        }
+        if modifiers.shift() {
+            output.push("Shift");
+        }
+        output
+    };
+
+    let (key_value, extra_modifiers) = key_value_to_xkb_common_name(key.value());
+    modifiers.extend(extra_modifiers);
+
+    let map_mode = if let KeyValue::MouseKey(_) = key.value() {
+        "map-pointer"
+    } else {
+        "map"
+    };
+
+    let modifiers = if modifiers.is_empty() {
+        "None".to_owned()
+    } else {
+        modifiers.join("+")
+    };
+
+    let mut output = vec![{
+        let mut riverctl = Command::new("riverctl");
+        riverctl.args([map_mode, mode, &modifiers, &key_value]);
+
+        riverctl.args(command.iter().map(String::as_str));
+
+        riverctl
+    }];
+
+    if allow_locked {
+        output.push({
+            let mut riverctl = Command::new("riverctl");
+            riverctl.args([map_mode, "locked", &modifiers, &key_value]);
+
+            riverctl.args(command.iter().map(String::as_str));
+
+            riverctl
+        });
     }
+
+    output
 }
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
index 2c82ee05..60ed41b8 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
@@ -8,40 +8,91 @@
 // You should have received a copy of the License along with this program.
 // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
 
-use std::{collections::HashMap, fmt::Display, ops::Deref, str::FromStr};
+use std::{fmt::Display, ops::Deref, str::FromStr};
 
-use anyhow::Context;
-use keymaps::{key_repr::Key, map_tree::MapTrie};
+use anyhow::{anyhow, bail, Context, Result};
+use keymaps::{
+    key_repr::{Key, Keys},
+    map_tree::MapTrie,
+};
 use serde::{Deserialize, Serialize};
+use serde_json::{Map, Value};
 
 pub mod commands;
 
-#[derive(Deserialize, Serialize, Debug)]
-#[allow(clippy::module_name_repetitions)]
-pub struct RawKeyMap(HashMap<Key, KeyConfig>);
-
 #[derive(Clone, Deserialize, Serialize, Debug, PartialEq, PartialOrd)]
-/// What values to use for: `riverctl <map_mode> <mode> <mods> <key> <command..>`
+/// What values to use for: `riverctl <command..>`
+#[serde(deny_unknown_fields)]
 pub struct KeyConfig {
     command: Vec<String>,
 
-    #[serde(default = "default_mode")]
-    modes: Vec<String>,
-
-    #[serde(default = "MapMode::default")]
-    map_mode: MapMode,
+    /// Whether to allow this key mapping in the “locked” mode.
+    #[serde(default)]
+    allow_locked: bool,
 }
 
 impl FromStr for KeyMap {
     type Err = anyhow::Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let raw: RawKeyMap =
-            serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?;
+        fn decode_value(
+            output: &mut MapTrie<KeyConfig>,
+            current_key: Vec<Key>,
+            value: &Value,
+        ) -> Result<()> {
+            let key_config = if let Some(value) = value.as_array() {
+                KeyConfig {
+                    command: value
+                        .iter()
+                        .map(|v| v.as_str().map(ToOwned::to_owned))
+                        .collect::<Option<_>>()
+                        .ok_or(anyhow!("A array contained a non-string value: {value:#?}"))?,
+                    allow_locked: false,
+                }
+            } else if let Some(object) = value.as_object() {
+                if object.contains_key("command") {
+                    serde_json::from_value(value.to_owned())
+                        .with_context(|| format!("Failed to parse key config: {value:#?}"))?
+                } else {
+                    for (key, value) in object {
+                        let mut local_current_key = current_key.clone();
+                        local_current_key.push(
+                            Key::from_str(key)
+                                .with_context(|| format!("Failed to parse key '{key}'"))?,
+                        );
+
+                        decode_value(output, local_current_key, value)?;
+                    }
+                    return Ok(());
+                }
+            } else {
+                bail!("Value ({}) is invalid (not array or object).", value)
+            };
+
+            output
+                .insert(&current_key, key_config.clone())
+                .with_context(|| {
+                    format!(
+                        "Failed to insert mapping {} -> {key_config}",
+                        Keys::from(current_key)
+                    )
+                })?;
+
+            Ok(())
+        }
+
         let mut out = MapTrie::<KeyConfig>::new();
-        for (key, value) in raw.0 {
-            out.insert(&[key], value.clone())
-                .with_context(|| format!("Failed to insert mapping {key} -> {value}"))?;
+
+        let raw: Map<String, Value> =
+            serde_json::from_str(s).context("Failed to parse the keymap config file as json.")?;
+
+        for (key, value) in raw {
+            decode_value(
+                &mut out,
+                vec![Key::from_str(&key)
+                    .with_context(|| format!("Failed to parse key ('{key}')"))?],
+                &value,
+            )?;
         }
 
         Ok(Self(out))
@@ -53,24 +104,6 @@ impl Display for KeyConfig {
     }
 }
 
-fn default_mode() -> Vec<String> {
-    vec!["normal".to_owned()]
-}
-
-#[derive(Copy, Deserialize, Serialize, Debug, Clone, Default, PartialEq, PartialOrd)]
-enum MapMode {
-    #[default]
-    Map,
-    MapMouse,
-    Unmap,
-}
-
-impl Display for MapMode {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        <Self as std::fmt::Debug>::fmt(self, f)
-    }
-}
-
 #[derive(Debug)]
 pub struct KeyMap(MapTrie<KeyConfig>);
 
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/main.rs b/pkgs/by-name/ri/river-mk-keymap/src/main.rs
index 63955f7f..7d7736b9 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/main.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/main.rs
@@ -13,31 +13,53 @@ use std::fs;
 use anyhow::Context;
 use clap::Parser;
 
-mod cli;
+pub mod cli;
 pub mod key_map;
+pub mod wayland;
 
 use crate::{cli::Args, key_map::KeyMap};
 
 fn main() -> Result<(), anyhow::Error> {
     let args = Args::parse();
-    let keymap_file = fs::read_to_string(&args.path)
-        .with_context(|| format!("Failed to open keymap file at: '{}'.", args.path.display()))?;
-
-    let keymap: KeyMap = keymap_file
-        .parse()
-        .with_context(|| format!("Failed to parse keymap file at: {}", args.path.display()))?;
-
-    // println!("{keymap}");
-    // println!("Commands:");
-    for mut command in keymap.to_commands() {
-        // println!("Executing {command:?}");
-        let status = command
-            .status()
-            .with_context(|| format!("Failed to run command: '{command:?}'"))?;
-
-        if !status.success() {
-            eprintln!("Command ('{command:?}') returned with non zero exit code: {status}");
+
+    let keymap_path = &args.keymap.canonicalize().with_context(|| {
+        format!(
+            "Failed to canonicalize kepmay path: '{}'",
+            args.keymap.display()
+        )
+    })?;
+
+    let config = {
+        let keymap_file = fs::read_to_string(keymap_path).with_context(|| {
+            format!(
+                "Failed to open keymap file at: '{}'.",
+                keymap_path.display()
+            )
+        })?;
+
+        let keymap: KeyMap = keymap_file.parse().with_context(|| {
+            format!("Failed to parse keymap file at: {}", keymap_path.display())
+        })?;
+
+        keymap
+    };
+
+    match args.command {
+        cli::SubCommand::Init {} => {
+            println!("{config}");
+            // println!("Commands:");
+            for mut command in config.to_commands(keymap_path)? {
+                // println!("{command:?}");
+                let status = command
+                    .status()
+                    .with_context(|| format!("Failed to run command: '{command:?}'"))?;
+
+                if !status.success() {
+                    eprintln!("Command ('{command:?}') returned with non zero exit code: {status}");
+                }
+            }
         }
+        cli::SubCommand::ShowHelp {} => wayland::main(config)?,
     }
 
     Ok(())
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs
new file mode 100644
index 00000000..0517ecf2
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/ansi/mod.rs
@@ -0,0 +1,173 @@
+use std::mem;
+
+use vte::{Params, Parser, Perform};
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum Color {
+    Black,
+    Red,
+    Green,
+    Yellow,
+    Blue,
+    Purple,
+    Cyan,
+    White,
+}
+
+#[derive(Debug)]
+struct Cleaner {
+    current_color: Option<Color>,
+    styles: StyledString,
+    current: String,
+}
+
+#[derive(Debug)]
+struct StyledStringInner {
+    val: String,
+    color: Option<Color>,
+}
+
+pub(crate) struct StyledChar {
+    ch: char,
+    color: Option<Color>,
+}
+
+impl StyledChar {
+    pub(crate) fn as_char(&self) -> char {
+        self.ch
+    }
+
+    pub(crate) fn is_bold(&self) -> bool {
+        self.color.is_some()
+    }
+
+    pub(crate) fn color(&self) -> Option<Color> {
+        self.color
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct StyledString {
+    inner: Vec<StyledStringInner>,
+}
+
+impl StyledString {
+    fn push(&mut self, val: StyledStringInner) {
+        self.inner.push(val);
+    }
+
+    pub(crate) fn chars(&self) -> impl Iterator<Item = StyledChar> + use<'_> {
+        self.inner.iter().flat_map(|inner| {
+            inner.val.chars().map(|ch| StyledChar {
+                ch,
+                color: inner.color,
+            })
+        })
+    }
+}
+
+impl Cleaner {
+    fn reset_color(&mut self) {
+        self.styles.push(StyledStringInner {
+            val: mem::take(&mut self.current),
+            color: mem::take(&mut self.current_color),
+        });
+    }
+
+    fn set_color(&mut self, color: Color) {
+        self.current_color = Some(color);
+    }
+
+    fn add_char(&mut self, c: char) {
+        self.current.push(c);
+    }
+}
+
+impl Perform for Cleaner {
+    fn print(&mut self, c: char) {
+        self.add_char(c);
+    }
+
+    fn execute(&mut self, byte: u8) {
+        if byte == b'\n' {
+            self.reset_color();
+            self.add_char('\n');
+            self.reset_color();
+        } else {
+            eprintln!("Unknown [execute]: {byte:02x}");
+        }
+    }
+
+    fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
+        eprintln!(
+            "Unknown [hook] params={params:?}, intermediates={intermediates:?}, ignore={ignore:?}, char={c:?}"
+        );
+    }
+
+    fn put(&mut self, byte: u8) {
+        eprintln!("Unknonw [put] {byte:02x}");
+    }
+
+    fn unhook(&mut self) {
+        eprintln!("Unknown [unhook]");
+    }
+
+    fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
+        eprintln!("Unkown [osc_dispatch] params={params:?} bell_terminated={bell_terminated}");
+    }
+
+    fn csi_dispatch(&mut self, params: &Params, _: &[u8], _: bool, c: char) {
+        let params: Vec<u16> = params.iter().flatten().copied().collect();
+
+        if c != 'm' {
+            return;
+        }
+
+        // See: https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
+        match params[..] {
+            [0] => self.reset_color(),
+            // [0, regular] if matches!(regular, 30..=37) => {}
+            [1, bold] if matches!(bold, 30..=37) => match bold {
+                30 => self.set_color(Color::Black),
+                31 => self.set_color(Color::Red),
+                32 => self.set_color(Color::Green),
+                36 => self.set_color(Color::Yellow),
+                34 => self.set_color(Color::Blue),
+                35 => self.set_color(Color::Purple),
+                33 => self.set_color(Color::Cyan),
+                37 => self.set_color(Color::White),
+                _ => unreachable!("Was filtered out"),
+            },
+            // [4, underline] if matches!(underline, 30..=37) => {}
+            // [background] if matches!(background, 40..=47) => {}
+            _ => todo!(),
+        }
+
+        // println!(
+        //     "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}",
+        //     params, intermediates, ignore, c
+        // );
+    }
+
+    fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
+        eprintln!(
+            "Unkown [esc_dispatch] intermediates={intermediates:?}, ignore={ignore:?}, byte={byte:02x}"
+        );
+    }
+}
+
+pub(crate) fn parse(input: &str) -> StyledString {
+    let mut statemachine = Parser::new();
+    let mut performer = Cleaner {
+        current_color: None,
+        styles: StyledString { inner: vec![] },
+        current: String::new(),
+    };
+
+    let buf: Vec<_> = input.bytes().collect();
+
+    statemachine.advance(&mut performer, &buf[..]);
+
+    assert!(performer.current.is_empty() && performer.current_color.is_none());
+    performer.styles
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
new file mode 100644
index 00000000..c6e04fdf
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
@@ -0,0 +1,214 @@
+use std::num::NonZero;
+
+use keymaps::key_repr::Key;
+use wayland_client::{
+    globals::GlobalListContents,
+    protocol::{
+        wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_registry, wl_seat::WlSeat,
+        wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
+    },
+    Connection, Dispatch, QueueHandle,
+};
+
+use wayland_protocols_wlr::layer_shell::v1::client::{
+    zwlr_layer_shell_v1::ZwlrLayerShellV1,
+    zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
+};
+
+use crate::wayland::{
+    ansi, render,
+    river::protocols::river_protocols::{
+        zriver_seat_status_v1::{self, ZriverSeatStatusV1},
+        zriver_status_manager_v1::ZriverStatusManagerV1,
+    },
+    AppData,
+};
+
+impl Dispatch<ZriverSeatStatusV1, ()> for AppData {
+    fn event(
+        state: &mut Self,
+        _: &ZriverSeatStatusV1,
+        event: <ZriverSeatStatusV1 as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+        if let zriver_seat_status_v1::Event::Mode { name } = event {
+            let new_text = {
+                if name == "normal" {
+                    // We are back at the normal mode.
+                    // There is no need to display the mappings anymore, exit.
+                    state.should_exit = true;
+                    return;
+                } else if let Ok(keys) = Key::parse_multiple(&name) {
+                    if let Some(val) = state.config.get(&keys) {
+                        ansi::parse(val.to_string().as_str())
+                    } else {
+                        // Mode name not know, do nothing.
+                        return;
+                    }
+                } else {
+                    // Mode name not valid, do nothing.
+                    return;
+                }
+            };
+
+            let px_height;
+            (state.pixel_data, (state.max_px_width, px_height)) =
+                render::text(&new_text).expect("Works?");
+
+            // We add the `5` here, so that our letters don't stop exactly at the border.
+            state
+                .window
+                .0
+                .set_size(state.max_px_width + 5, px_height + 5);
+            state.window.1.commit();
+
+            if state.configured {
+                state.draw();
+            }
+        }
+    }
+}
+
+impl Dispatch<ZwlrLayerSurfaceV1, ()> for AppData {
+    fn event(
+        state: &mut Self,
+        proxy: &ZwlrLayerSurfaceV1,
+        event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+        match event {
+            zwlr_layer_surface_v1::Event::Configure {
+                serial,
+                width,
+                height,
+            } => {
+                state.buffer = None;
+
+                proxy.ack_configure(serial);
+
+                state.width = NonZero::new(width).map_or_else(|| state.width, NonZero::get);
+                state.height = NonZero::new(height).map_or_else(|| state.height, NonZero::get);
+
+                state.draw();
+
+                state.configured = true;
+            }
+            zwlr_layer_surface_v1::Event::Closed => {
+                state.should_exit = true;
+            }
+            _ => (),
+        }
+    }
+}
+
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for AppData {
+    fn event(
+        _: &mut AppData,
+        _: &wl_registry::WlRegistry,
+        _: wl_registry::Event,
+        _: &GlobalListContents,
+        _: &Connection,
+        _: &QueueHandle<AppData>,
+    ) {
+    }
+}
+
+impl Dispatch<WlShmPool, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlShmPool,
+        _: <WlShmPool as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<WlShm, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlShm,
+        _: <WlShm as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<WlSurface, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlSurface,
+        _: <WlSurface as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<WlCompositor, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlCompositor,
+        _: <WlCompositor as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<WlSeat, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlSeat,
+        _: <WlSeat as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<WlBuffer, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &WlBuffer,
+        _: <WlBuffer as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<ZriverStatusManagerV1, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &ZriverStatusManagerV1,
+        _: <ZriverStatusManagerV1 as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
+
+impl Dispatch<ZwlrLayerShellV1, ()> for AppData {
+    fn event(
+        _: &mut Self,
+        _: &ZwlrLayerShellV1,
+        _: <ZwlrLayerShellV1 as wayland_client::Proxy>::Event,
+        (): &(),
+        _: &Connection,
+        _: &QueueHandle<Self>,
+    ) {
+    }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
new file mode 100644
index 00000000..44c010d5
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
@@ -0,0 +1,272 @@
+#![allow(
+    clippy::cast_sign_loss,
+    clippy::cast_possible_wrap,
+    clippy::cast_precision_loss,
+    clippy::cast_possible_truncation
+)]
+
+use anyhow::Result;
+use wayland_client::{
+    globals::registry_queue_init,
+    protocol::{
+        wl_compositor::WlCompositor,
+        wl_seat::WlSeat,
+        wl_shm::{self, WlShm},
+        wl_surface::WlSurface,
+    },
+    Connection,
+};
+use wayland_protocols_wlr::layer_shell::v1::client::{
+    zwlr_layer_shell_v1::{self, ZwlrLayerShellV1},
+    zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
+};
+
+use crate::{
+    key_map::KeyMap,
+    wayland::{
+        ansi::Color,
+        river::protocols::river_protocols::zriver_status_manager_v1::ZriverStatusManagerV1,
+        shm::slot::{Buffer, SlotPool},
+    },
+};
+
+mod ansi;
+mod render;
+mod river;
+mod shm;
+
+mod dispatches;
+
+struct AppData {
+    pool: SlotPool,
+    window: (ZwlrLayerSurfaceV1, WlSurface),
+
+    configured: bool,
+    buffer: Option<Buffer>,
+
+    width: u32,
+    height: u32,
+
+    max_px_width: u32,
+    pixel_data: (Vec<f32>, Vec<Option<Color>>),
+
+    config: KeyMap,
+    should_exit: bool,
+}
+
+impl AppData {
+    #[allow(clippy::too_many_lines)]
+    fn draw(&mut self) {
+        let width = self.width;
+        let height = self.height;
+        let stride = self.width as i32 * 4;
+
+        let buffer = self.buffer.get_or_insert_with(|| {
+            self.pool
+                .create_buffer(
+                    width as i32,
+                    height as i32,
+                    stride,
+                    wl_shm::Format::Argb8888,
+                )
+                .expect("Works?")
+                .0
+        });
+
+        let canvas = if let Some(canvas) = self.pool.canvas(buffer) {
+            canvas
+        } else {
+            // This should be rare, but if the compositor has not released the previous
+            // buffer, we need double-buffering.
+            let (second_buffer, canvas) = self
+                .pool
+                .create_buffer(
+                    self.width as i32,
+                    self.height as i32,
+                    stride,
+                    wl_shm::Format::Argb8888,
+                )
+                .expect("create buffer");
+            *buffer = second_buffer;
+            canvas
+        };
+
+        // Draw to the window.
+        {
+            canvas
+                .chunks_exact_mut(stride as usize)
+                .enumerate()
+                .for_each(|(row_index, row)| {
+                    // let row_slice = row_slice(self.height, row_index as u32, 0.97);
+                    // let allowed_columns = (f64::from(self.width) * row_slice).ceil() as usize;
+
+                    row.chunks_exact_mut(4)
+                        .enumerate()
+                        .for_each(|(column_index, chunk)| {
+                            // const BACKGROUND_COLOR: u32 = 0xee58_5b70;
+                            const BACKGROUND_COLOR: u32 = 0xee00_0000;
+
+                            assert!(column_index as u32 <= self.width);
+
+                            // if column_index > allowed_columns
+                            //     || column_index < (self.width as usize - allowed_columns)
+                            // {
+                            //     let array: &mut [u8; 4] = chunk.try_into().unwrap();
+                            //     *array = 0u32.to_le_bytes();
+                            //     return;
+                            // }
+
+                            if column_index >= (self.max_px_width as usize) {
+                                let array: &mut [u8; 4] = chunk.try_into().unwrap();
+                                *array = BACKGROUND_COLOR.to_le_bytes();
+                            } else {
+                                assert!(column_index < self.max_px_width as usize);
+
+                                let position =
+                                    column_index + row_index * self.max_px_width as usize;
+
+                                if let Some(coverage) = &self.pixel_data.0.get(position) {
+                                    let a = (BACKGROUND_COLOR & (0xff << (6 * 4))) >> 24;
+
+                                    let (r, g, b) = if let Some(color) = self
+                                        .pixel_data
+                                        .1
+                                        .get(position)
+                                        .expect("If the pixel is set, the color will too")
+                                    {
+                                        let (r, g, b) = match color {
+                                            Color::Black => (0, 0, 0),
+                                            Color::Red => (0xff, 0, 0),
+                                            Color::Green => (0, 0xff, 0),
+                                            Color::Yellow => (0xff, 0xff, 0),
+                                            Color::Blue => (0, 0, 0xff),
+                                            Color::Purple => (0x80, 0, 0x80),
+                                            Color::Cyan => (0, 0xff, 0xff),
+                                            Color::White => (0xff, 0xff, 0xff),
+                                        };
+
+                                        let r = (r as f32 * **coverage).ceil() as u32;
+                                        let g = (g as f32 * **coverage).ceil() as u32;
+                                        let b = (b as f32 * **coverage).ceil() as u32;
+
+                                        (r, g, b)
+                                    } else {
+                                        let r = (255.0 * **coverage).ceil() as u32;
+                                        let g = (255.0 * **coverage).ceil() as u32;
+                                        let b = (255.0 * **coverage).ceil() as u32;
+
+                                        (r, g, b)
+                                    };
+
+                                    let color: u32 = (a << 24) + (r << 16) + (g << 8) + b;
+
+                                    let array: &mut [u8; 4] = chunk.try_into().unwrap();
+                                    *array = color.to_le_bytes();
+                                } else {
+                                    let array: &mut [u8; 4] = chunk.try_into().unwrap();
+                                    *array = BACKGROUND_COLOR.to_le_bytes();
+                                }
+                            }
+                        });
+                });
+        }
+
+        self.window
+            .1
+            .damage_buffer(0, 0, self.width as i32, self.height as i32);
+
+        buffer.attach_to(&self.window.1).expect("works");
+        self.window.1.commit();
+    }
+}
+
+/// # Errors
+/// If a protocol error arises.
+pub fn main(config: KeyMap) -> Result<()> {
+    let conn = Connection::connect_to_env()?;
+    let (globals, mut queue) = registry_queue_init::<AppData>(&conn)?;
+    let qh = queue.handle();
+
+    let seat: WlSeat = globals.bind(&qh, 9..=9, ())?;
+    let status_manager: ZriverStatusManagerV1 = globals.bind(&qh, 4..=4, ())?;
+    let _seat_status = status_manager.get_river_seat_status(&seat, &qh, ());
+
+    let compositor: WlCompositor = globals.bind(&qh, 6..=6, ())?;
+    let shm: WlShm = globals.bind(&qh, 1..=1, ())?;
+    // let xdg_wm: XdgWmBase = globals.bind(&qh, 5..=5, ())?;
+
+    let surface = compositor.create_surface(&qh, ());
+    let pool = SlotPool::new(1024 * 1024, &shm)?;
+
+    let zwlr_layer_shell: ZwlrLayerShellV1 = globals.bind(&qh, 4..=4, ())?;
+    let layer_surface = zwlr_layer_shell.get_layer_surface(
+        &surface,
+        None,
+        zwlr_layer_shell_v1::Layer::Overlay,
+        "river-mk-keymap which-key".to_owned(),
+        &qh,
+        (),
+    );
+
+    layer_surface.set_size(256, 256);
+    layer_surface
+        .set_anchor(zwlr_layer_surface_v1::Anchor::Left | zwlr_layer_surface_v1::Anchor::Top);
+
+    surface.commit();
+
+    let mut me = AppData {
+        config,
+        should_exit: false,
+
+        configured: false,
+        buffer: None,
+
+        width: 256,
+        height: 256,
+
+        max_px_width: 0,
+        pixel_data: (vec![], vec![]),
+
+        window: (layer_surface, surface),
+
+        pool,
+    };
+
+    loop {
+        queue.blocking_dispatch(&mut me)?;
+
+        if me.should_exit {
+            break;
+        }
+    }
+
+    Ok(())
+}
+
+// /// Calculate which amount of the current row (`i`) should be painted, if we want a corner
+// /// rounding of percent `p` and have an total of `n` rows.
+// fn row_slice(n_u32: u32, i_u32: u32, p: f64) -> f64 {
+//     fn within_tolerance(a: f64, b: f64) -> bool {
+//         const ALLOWED_ERROR: f64 = 0.000_000_1;
+//
+//         (a - b).abs() < ALLOWED_ERROR
+//     }
+//
+//     let i = f64::from(i_u32);
+//     let n = f64::from(n_u32);
+//
+//     let out = p + (1.0 - p) * (PI * i / n).sin();
+//
+//     assert!(out >= 0.0);
+//     assert!(out <= 1.0);
+//
+//     if i_u32 == 0 || i_u32 == n_u32 {
+//         assert!(within_tolerance(out, p));
+//     }
+//
+//     if i_u32 < n_u32 / 2 {
+//         assert!(within_tolerance(out, row_slice(n_u32, n_u32 - i_u32, p)));
+//     }
+//
+//     out
+// }
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
new file mode 100644
index 00000000..7f0aaec9
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
@@ -0,0 +1,57 @@
+use ab_glyph::{point, Font, Glyph, Point, ScaleFont};
+
+use crate::wayland::ansi::{StyledChar, StyledString};
+
+/// Simple paragraph layout for glyphs into `target`.
+/// Starts at position `(0, ascent)`.
+///
+/// This is for testing and examples.
+pub(super) fn layout_paragraph<F, SF, BF, BSF>(
+    font: SF,
+    bold_font: BSF,
+    position: Point,
+    max_width: f32,
+    text: &StyledString,
+    target: &mut Vec<(Glyph, StyledChar)>,
+) where
+    F: Font,
+    SF: ScaleFont<F>,
+    BF: Font,
+    BSF: ScaleFont<BF>,
+{
+    let v_advance = font.height() + font.line_gap();
+    let mut caret = position + point(0.0, font.ascent());
+    let mut last_glyph: Option<Glyph> = None;
+
+    for c in text.chars() {
+        if c.as_char().is_control() {
+            if c.as_char() == '\n' {
+                caret = point(position.x, caret.y + v_advance);
+                last_glyph = None;
+            }
+            continue;
+        }
+
+        let mut glyph = if c.is_bold() {
+            bold_font.scaled_glyph(c.as_char())
+        } else {
+            font.scaled_glyph(c.as_char())
+        };
+
+        if let Some(previous) = last_glyph.take() {
+            caret.x += font.kern(previous.id, glyph.id);
+        }
+        glyph.position = caret;
+
+        last_glyph = Some(glyph.clone());
+        caret.x += font.h_advance(glyph.id);
+
+        if !c.as_char().is_whitespace() && caret.x > position.x + max_width {
+            caret = point(position.x, caret.y + v_advance);
+            glyph.position = caret;
+            last_glyph = None;
+        }
+
+        target.push((glyph, c));
+    }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
new file mode 100644
index 00000000..e92def3c
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
@@ -0,0 +1,129 @@
+use std::{fs::File, io::Read};
+
+use ab_glyph::{point, Font, FontVec, PxScale, ScaleFont};
+use anyhow::{Context, Result};
+use font_kit::{
+    family_name::FamilyName, handle::Handle, properties::Properties, source::SystemSource,
+};
+
+use crate::wayland::ansi::{Color, StyledString};
+
+mod layout;
+
+fn get_font(weight: f32) -> Result<impl Font> {
+    let handle = SystemSource::new()
+        .select_best_match(
+            &[FamilyName::Monospace],
+            Properties::new().weight(font_kit::properties::Weight(weight)),
+        )
+        .context("Failed to find a monospace font")?;
+
+    match handle {
+        Handle::Path { path, font_index } => {
+            let data = {
+                let mut buffer = vec![];
+
+                let mut file = File::open(&path)?;
+                file.read_to_end(&mut buffer)?;
+                buffer
+            };
+
+            FontVec::try_from_vec_and_index(data, font_index).with_context(|| {
+                format!(
+                    "Failed to load font at '{}' with index {}",
+                    path.display(),
+                    font_index
+                )
+            })
+        }
+        Handle::Memory { .. } => unimplemented!(),
+    }
+}
+
+pub(super) type ColorVec = (Vec<f32>, Vec<Option<Color>>);
+pub(super) fn text(input: &StyledString) -> Result<(ColorVec, (u32, u32))> {
+    let normal_font = get_font(400.0)?;
+    let bold_font = get_font(600.0)?;
+
+    let height: f32 = 15.0;
+    let px_height = height.ceil() as usize;
+
+    let scale = PxScale {
+        x: height,
+        y: height,
+    };
+
+    let scaled_font = normal_font.into_scaled(scale);
+    let bold_scaled_font = bold_font.into_scaled(scale);
+
+    let mut glyphs = Vec::new();
+    layout::layout_paragraph(
+        &scaled_font,
+        &bold_scaled_font,
+        point(0.0, 0.0),
+        9999.0,
+        input,
+        &mut glyphs,
+    );
+
+    let px_width = glyphs
+        .iter()
+        .fold(0.0, |acc, (g, c)| {
+            let next = g.position.x
+                + if c.is_bold() {
+                    bold_scaled_font.h_advance(g.id)
+                } else {
+                    scaled_font.h_advance(g.id)
+                };
+
+            if next > acc {
+                next
+            } else {
+                acc
+            }
+        })
+        .ceil() as usize;
+
+    // Rasterise to a f32 alpha vec
+    let mut pixel_data = vec![0.0; px_width * px_height];
+    let mut color_data = vec![None; px_width * px_height];
+    for (g, c) in glyphs {
+        let maybe_glyph = if c.is_bold() {
+            bold_scaled_font.outline_glyph(g)
+        } else {
+            scaled_font.outline_glyph(g)
+        };
+
+        if let Some(og) = maybe_glyph {
+            let bounds = og.px_bounds();
+            og.draw(|x, y, v| {
+                let x = x as f32 + bounds.min.x;
+                let y = y as f32 + bounds.min.y;
+                let next_idx = x as usize + y as usize * px_width;
+
+                assure_idx(&mut pixel_data, next_idx, 0.0);
+                assure_idx(&mut color_data, next_idx, None);
+
+                // save the coverage alpha
+                pixel_data[next_idx] += v;
+                color_data[next_idx] = c.color();
+            });
+        }
+    }
+
+    let len = pixel_data.len();
+    Ok((
+        (pixel_data, color_data),
+        (px_width as u32, (len / px_width) as u32),
+    ))
+}
+
+fn assure_idx<T: Copy + Clone>(pixel_data: &mut Vec<T>, next_idx: usize, fill: T) {
+    let last = pixel_data.len() - 1;
+
+    if next_idx > last {
+        let needed = next_idx - last;
+
+        pixel_data.extend(vec![fill; needed]);
+    }
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs
new file mode 100644
index 00000000..f17c7ac8
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod protocols;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
new file mode 100644
index 00000000..e54b65e1
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
@@ -0,0 +1,28 @@
+pub(crate) mod river_protocols {
+    use wayland_client;
+    // import objects from the core protocol if needed
+    use wayland_client::protocol::{wl_output, wl_seat};
+
+    // This module hosts a low-level representation of the protocol objects
+    // you will not need to interact with it yourself, but the code generated
+    // by the generate_client_code! macro will use it
+    // import the interfaces from the core protocol if needed
+
+    #[allow(non_upper_case_globals)]
+    pub(crate) mod __status {
+        use wayland_client::backend as wayland_backend;
+        use wayland_client::protocol::__interfaces::{
+            wl_output_interface, wl_seat_interface, WL_OUTPUT_INTERFACE, WL_SEAT_INTERFACE,
+        };
+        wayland_scanner::generate_interfaces!("./resources/river-status-unstable-v1.xml");
+    }
+
+    use self::__status::{
+        ZRIVER_OUTPUT_STATUS_V1_INTERFACE, ZRIVER_SEAT_STATUS_V1_INTERFACE,
+        ZRIVER_STATUS_MANAGER_V1_INTERFACE,
+    };
+
+    // This macro generates the actual types that represent the wayland objects of
+    // your custom protocol
+    wayland_scanner::generate_client_code!("./resources/river-status-unstable-v1.xml");
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs
new file mode 100644
index 00000000..65d3c590
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/mod.rs
@@ -0,0 +1,21 @@
+#![allow(dead_code)]
+
+pub(crate) mod multi;
+pub(crate) mod raw;
+pub(crate) mod slot;
+
+use std::io;
+
+use wayland_client::globals::GlobalError;
+
+/// An error that may occur when creating a pool.
+#[derive(Debug, thiserror::Error)]
+pub enum CreatePoolError {
+    /// The [`wl_shm`] global is not bound.
+    #[error(transparent)]
+    Global(#[from] GlobalError),
+
+    /// Error while allocating the shared memory.
+    #[error(transparent)]
+    Create(#[from] io::Error),
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
new file mode 100644
index 00000000..0b1fdc1b
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
@@ -0,0 +1,437 @@
+//! A pool implementation which automatically manage buffers.
+//!
+//! This pool is built on the [`RawPool`].
+//!
+//! The [`MultiPool`] takes a key which is used to identify buffers and tries to return the buffer associated to the key
+//! if possible. If no buffer in the pool is associated to the key, it will create a new one.
+//!
+//! # Example
+//!
+//! ```rust
+//! use smithay_client_toolkit::reexports::client::{
+//!     QueueHandle,
+//!     protocol::wl_surface::WlSurface,
+//!     protocol::wl_shm::Format,
+//! };
+//! use smithay_client_toolkit::shm::multi::MultiPool;
+//!
+//! struct WlFoo {
+//!     // The surface we'll draw on and the index of buffer associated to it
+//!     surface: (WlSurface, usize),
+//!     pool: MultiPool<(WlSurface, usize)>
+//! }
+//!
+//! impl WlFoo {
+//!     fn draw(&mut self, qh: &QueueHandle<WlFoo>) {
+//!         let surface = &self.surface.0;
+//!         // We'll increment "i" until the pool can create a new buffer
+//!         // if there's no buffer associated with our surface and "i" or if
+//!         // a buffer with the obuffer associated with our surface and "i" is free for use.
+//!         //
+//!         // There's no limit to the amount of buffers we can allocate to our surface but since
+//!         // shm buffers are released fairly fast, it's unlikely we'll need more than double buffering.
+//!         for i in 0..2 {
+//!             self.surface.1 = i;
+//!             if let Ok((offset, buffer, slice)) = self.pool.create_buffer(
+//!                 100,
+//!                 100 * 4,
+//!                 100,
+//!                 &self.surface,
+//!                 Format::Argb8888,
+//!             ) {
+//!                 /*
+//!                     insert drawing code here
+//!                 */
+//!                 surface.attach(Some(buffer), 0, 0);
+//!                 surface.commit();
+//!                 // We exit the function after the draw.
+//!                 return;
+//!             }
+//!         }
+//!         /*
+//!             If there's no buffer available we can for example request a frame callback
+//!             and trigger a redraw when it fires.
+//!             (not shown in this example)
+//!         */
+//!     }
+//! }
+//!
+//! fn draw(slice: &mut [u8]) {
+//!     todo!()
+//! }
+//!
+//! ```
+//!
+
+use std::borrow::Borrow;
+use std::io;
+use std::os::unix::io::OwnedFd;
+
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc,
+};
+use wayland_client::backend::protocol::Message;
+use wayland_client::backend::{Backend, ObjectData, ObjectId};
+use wayland_client::{
+    protocol::{wl_buffer, wl_shm},
+    Proxy,
+};
+
+use crate::wayland::shm::CreatePoolError;
+
+use super::raw::RawPool;
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum PoolError {
+    #[error("buffer is currently used")]
+    InUse,
+    #[error("buffer is overlapping another")]
+    Overlap,
+    #[error("buffer could not be found")]
+    NotFound,
+}
+
+/// This pool manages buffers associated with keys.
+/// Only one buffer can be attributed to a given key.
+#[derive(Debug)]
+pub(crate) struct MultiPool<K> {
+    buffer_list: Vec<BufferSlot<K>>,
+    pub(crate) inner: RawPool,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) struct BufferSlot<K> {
+    free: Arc<AtomicBool>,
+    size: usize,
+    used: usize,
+    offset: usize,
+    buffer: Option<wl_buffer::WlBuffer>,
+    key: K,
+}
+
+impl<K> Drop for BufferSlot<K> {
+    fn drop(&mut self) {
+        self.destroy().ok();
+    }
+}
+
+impl<K> BufferSlot<K> {
+    pub(crate) fn destroy(&self) -> Result<(), PoolError> {
+        self.buffer
+            .as_ref()
+            .ok_or(PoolError::NotFound)
+            .and_then(|buffer| {
+                self.free
+                    .load(Ordering::Relaxed)
+                    .then(|| buffer.destroy())
+                    .ok_or(PoolError::InUse)
+            })
+    }
+}
+
+impl<K> MultiPool<K> {
+    pub(crate) fn new(shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> {
+        Ok(Self {
+            inner: RawPool::new(4096, shm)?,
+            buffer_list: Vec::new(),
+        })
+    }
+
+    /// Resizes the memory pool, notifying the server the pool has changed in size.
+    ///
+    /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the
+    /// current size of the pool, this function will do nothing.
+    pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> {
+        self.inner.resize(size)
+    }
+
+    /// Removes the buffer with the given key from the pool and rearranges the others.
+    pub(crate) fn remove<Q>(&mut self, key: &Q) -> Option<BufferSlot<K>>
+    where
+        Q: PartialEq,
+        K: Borrow<Q>,
+    {
+        self.buffer_list
+            .iter()
+            .enumerate()
+            .find(|(_, slot)| slot.key.borrow().eq(key))
+            .map(|(i, _)| i)
+            .map(|i| self.buffer_list.remove(i))
+    }
+
+    /// Insert a buffer into the pool.
+    ///
+    /// The parameters are:
+    ///
+    /// - `width`: the width of this buffer (in pixels)
+    /// - `height`: the height of this buffer (in pixels)
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+    /// - `key`: a borrowed form of the stored key type
+    /// - `format`: the encoding format of the pixels.
+    pub(crate) fn insert<Q>(
+        &mut self,
+        width: i32,
+        stride: i32,
+        height: i32,
+        key: &Q,
+        format: wl_shm::Format,
+    ) -> Result<usize, PoolError>
+    where
+        K: Borrow<Q>,
+        Q: PartialEq + ToOwned<Owned = K>,
+    {
+        let mut offset = 0;
+        let mut found_key = false;
+        let size = (stride * height) as usize;
+        let mut index = Err(PoolError::NotFound);
+
+        for (i, buf_slot) in self.buffer_list.iter_mut().enumerate() {
+            if buf_slot.key.borrow().eq(key) {
+                found_key = true;
+                if buf_slot.free.load(Ordering::Relaxed) {
+                    // Destroys the buffer if it's resized
+                    if size != buf_slot.used {
+                        if let Some(buffer) = buf_slot.buffer.take() {
+                            buffer.destroy();
+                        }
+                    }
+                    // Increases the size of the Buffer if it's too small and add 5% padding.
+                    // It is possible this buffer overlaps the following but the else if
+                    // statement prevents this buffer from being returned if that's the case.
+                    buf_slot.size = buf_slot.size.max(size + size / 20);
+                    index = Ok(i);
+                } else {
+                    index = Err(PoolError::InUse);
+                }
+            // If a buffer is resized, it is likely that the followings might overlap
+            } else if offset > buf_slot.offset {
+                // When the buffer is free, it's safe to shift it because we know the compositor won't try to read it.
+                if buf_slot.free.load(Ordering::Relaxed) {
+                    if offset != buf_slot.offset {
+                        if let Some(buffer) = buf_slot.buffer.take() {
+                            buffer.destroy();
+                        }
+                    }
+                    buf_slot.offset = offset;
+                } else {
+                    // If one of the overlapping buffers is busy, then no buffer can be returned because it could result in a data race.
+                    index = Err(PoolError::InUse);
+                }
+            } else if found_key {
+                break;
+            }
+            let size = (buf_slot.size + 63) & !63;
+            offset += size;
+        }
+
+        if !found_key {
+            if let Err(err) = index {
+                return self
+                    .dyn_resize(offset, width, stride, height, key.to_owned(), format)
+                    .map(|()| self.buffer_list.len() - 1)
+                    .ok_or(err);
+            }
+        }
+
+        index
+    }
+
+    /// Retreives the buffer associated with the given key.
+    ///
+    /// The parameters are:
+    ///
+    /// - `width`: the width of this buffer (in pixels)
+    /// - `height`: the height of this buffer (in pixels)
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+    /// - `key`: a borrowed form of the stored key type
+    /// - `format`: the encoding format of the pixels.
+    pub(crate) fn get<Q>(
+        &mut self,
+        width: i32,
+        stride: i32,
+        height: i32,
+        key: &Q,
+        format: wl_shm::Format,
+    ) -> Option<(usize, &wl_buffer::WlBuffer, &mut [u8])>
+    where
+        Q: PartialEq,
+        K: Borrow<Q>,
+    {
+        let len = self.inner.len();
+        let size = (stride * height) as usize;
+        let buf_slot = self
+            .buffer_list
+            .iter_mut()
+            .find(|buf_slot| buf_slot.key.borrow().eq(key))?;
+
+        if buf_slot.size >= size {
+            return None;
+        }
+
+        buf_slot.used = size;
+        let offset = buf_slot.offset;
+        if buf_slot.buffer.is_none() {
+            if offset + size > len {
+                self.inner.resize(offset + size + size / 20).ok()?;
+            }
+            let free = Arc::new(AtomicBool::new(true));
+            let data = BufferObjectData { free: free.clone() };
+            let buffer = self.inner.create_buffer_raw(
+                offset as i32,
+                width,
+                height,
+                stride,
+                format,
+                Arc::new(data),
+            );
+            buf_slot.free = free;
+            buf_slot.buffer = Some(buffer);
+        }
+        let buf = buf_slot.buffer.as_ref()?;
+        buf_slot.free.store(false, Ordering::Relaxed);
+        Some((offset, buf, &mut self.inner.mmap()[offset..][..size]))
+    }
+
+    /// Returns the buffer associated with the given key and its offset (usize) in the mempool.
+    ///
+    /// The parameters are:
+    ///
+    /// - `width`: the width of this buffer (in pixels)
+    /// - `height`: the height of this buffer (in pixels)
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+    /// - `key`: a borrowed form of the stored key type
+    /// - `format`: the encoding format of the pixels.
+    ///
+    /// The offset can be used to determine whether or not a buffer was moved in the mempool
+    /// and by consequence if it should be damaged partially or fully.
+    pub(crate) fn create_buffer<Q>(
+        &mut self,
+        width: i32,
+        stride: i32,
+        height: i32,
+        key: &Q,
+        format: wl_shm::Format,
+    ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError>
+    where
+        K: Borrow<Q>,
+        Q: PartialEq + ToOwned<Owned = K>,
+    {
+        let index = self.insert(width, stride, height, key, format)?;
+        self.get_at(index, width, stride, height, format)
+    }
+
+    /// Retreives the buffer at the given index.
+    fn get_at(
+        &mut self,
+        index: usize,
+        width: i32,
+        stride: i32,
+        height: i32,
+        format: wl_shm::Format,
+    ) -> Result<(usize, &wl_buffer::WlBuffer, &mut [u8]), PoolError> {
+        let len = self.inner.len();
+        let size = (stride * height) as usize;
+        let buf_slot = self.buffer_list.get_mut(index).ok_or(PoolError::NotFound)?;
+
+        if size > buf_slot.size {
+            return Err(PoolError::Overlap);
+        }
+
+        buf_slot.used = size;
+        let offset = buf_slot.offset;
+        if buf_slot.buffer.is_none() {
+            if offset + size > len {
+                self.inner
+                    .resize(offset + size + size / 20)
+                    .map_err(|_| PoolError::Overlap)?;
+            }
+            let free = Arc::new(AtomicBool::new(true));
+            let data = BufferObjectData { free: free.clone() };
+            let buffer = self.inner.create_buffer_raw(
+                offset as i32,
+                width,
+                height,
+                stride,
+                format,
+                Arc::new(data),
+            );
+            buf_slot.free = free;
+            buf_slot.buffer = Some(buffer);
+        }
+        buf_slot.free.store(false, Ordering::Relaxed);
+        let buf = buf_slot.buffer.as_ref().unwrap();
+        Ok((offset, buf, &mut self.inner.mmap()[offset..][..size]))
+    }
+
+    /// Calcule the offet and size of a buffer based on its stride.
+    fn offset(mut offset: i32, stride: i32, height: i32) -> (usize, usize) {
+        // bytes per pixel
+        let size = stride * height;
+        // 5% padding.
+        offset += offset / 20;
+        offset = (offset + 63) & !63;
+        (offset as usize, size as usize)
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    /// Resizes the pool and appends a new buffer.
+    fn dyn_resize(
+        &mut self,
+        offset: usize,
+        width: i32,
+        stride: i32,
+        height: i32,
+        key: K,
+        format: wl_shm::Format,
+    ) -> Option<()> {
+        let (offset, size) = Self::offset(offset as i32, stride, height);
+        if self.inner.len() < offset + size {
+            self.resize(offset + size + size / 20).ok()?;
+        }
+        let free = Arc::new(AtomicBool::new(true));
+        let data = BufferObjectData { free: free.clone() };
+        let buffer = self.inner.create_buffer_raw(
+            offset as i32,
+            width,
+            height,
+            stride,
+            format,
+            Arc::new(data),
+        );
+        self.buffer_list.push(BufferSlot {
+            offset,
+            used: 0,
+            free,
+            buffer: Some(buffer),
+            size,
+            key,
+        });
+        Some(())
+    }
+}
+
+struct BufferObjectData {
+    free: Arc<AtomicBool>,
+}
+
+impl ObjectData for BufferObjectData {
+    fn event(
+        self: Arc<Self>,
+        _backend: &Backend,
+        msg: Message<ObjectId, OwnedFd>,
+    ) -> Option<Arc<dyn ObjectData>> {
+        debug_assert!(wayland_client::backend::protocol::same_interface(
+            msg.sender_id.interface(),
+            wl_buffer::WlBuffer::interface()
+        ));
+        debug_assert!(msg.opcode == 0);
+
+        // wl_buffer only has a single event: wl_buffer.release
+        self.free.store(true, Ordering::Relaxed);
+
+        None
+    }
+
+    fn destroyed(&self, _: ObjectId) {}
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
new file mode 100644
index 00000000..a12afaa0
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
@@ -0,0 +1,290 @@
+//! A raw shared memory pool handler.
+//!
+//! This is intended as a safe building block for higher level shared memory pool abstractions and is not
+//! encouraged for most library users.
+
+use rustix::{
+    io::Errno,
+    shm::{Mode, OFlags},
+};
+use std::{
+    fs::File,
+    io,
+    ops::Deref,
+    os::unix::prelude::{AsFd, BorrowedFd, OwnedFd},
+    sync::Arc,
+    time::{SystemTime, UNIX_EPOCH},
+};
+
+use memmap2::MmapMut;
+use wayland_client::{
+    backend::ObjectData,
+    protocol::{wl_buffer, wl_shm, wl_shm_pool},
+    Dispatch, Proxy, QueueHandle, WEnum,
+};
+
+use super::CreatePoolError;
+
+/// A raw handler for file backed shared memory pools.
+///
+/// This type of pool will create the SHM memory pool and provide a way to resize the pool.
+///
+/// This pool does not release buffers. If you need this, use one of the higher level pools.
+#[derive(Debug)]
+pub struct RawPool {
+    pool: DestroyOnDropPool,
+    len: usize,
+    mem_file: File,
+    mmap: MmapMut,
+}
+
+impl RawPool {
+    pub fn new(len: usize, shm: &wl_shm::WlShm) -> Result<RawPool, CreatePoolError> {
+        let shm_fd = RawPool::create_shm_fd()?;
+        let mem_file = File::from(shm_fd);
+        mem_file.set_len(len as u64)?;
+
+        let pool = shm
+            .send_constructor(
+                wl_shm::Request::CreatePool {
+                    fd: mem_file.as_fd(),
+                    size: len as i32,
+                },
+                Arc::new(ShmPoolData),
+            )
+            .unwrap_or_else(|_| Proxy::inert(shm.backend().clone()));
+        let mmap = unsafe { MmapMut::map_mut(&mem_file)? };
+
+        Ok(RawPool {
+            pool: DestroyOnDropPool(pool),
+            len,
+            mem_file,
+            mmap,
+        })
+    }
+
+    /// Resizes the memory pool, notifying the server the pool has changed in size.
+    ///
+    /// The [`wl_shm`] protocol only allows the pool to be made bigger. If the new size is smaller than the
+    /// current size of the pool, this function will do nothing.
+    pub fn resize(&mut self, size: usize) -> io::Result<()> {
+        if size > self.len {
+            self.len = size;
+            self.mem_file.set_len(size as u64)?;
+            self.pool.resize(size as i32);
+            self.mmap = unsafe { MmapMut::map_mut(&self.mem_file) }?;
+        }
+
+        Ok(())
+    }
+
+    /// Returns a reference to the underlying shared memory file using the memmap2 crate.
+    pub fn mmap(&mut self) -> &mut MmapMut {
+        &mut self.mmap
+    }
+
+    /// Returns the size of the mempool
+    #[allow(clippy::len_without_is_empty)]
+    pub fn len(&self) -> usize {
+        self.len
+    }
+
+    /// Create a new buffer to this pool.
+    ///
+    /// ## Parameters
+    /// - `offset`: the offset (in bytes) from the beginning of the pool at which this buffer starts.
+    /// - `width` and `height`: the width and height of the buffer in pixels.
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one.
+    /// - `format`: the encoding format of the pixels.
+    ///
+    /// The encoding format of the pixels must be supported by the compositor or else a protocol error is
+    /// risen. You can ensure the format is supported by listening to [`Shm::formats`](crate::shm::Shm::formats).
+    ///
+    /// Note this function only creates the [`wl_buffer`] object, you will need to write to the pixels using the
+    /// [`io::Write`] implementation or [`RawPool::mmap`].
+    #[allow(clippy::too_many_arguments)]
+    pub fn create_buffer<D, U>(
+        &mut self,
+        offset: i32,
+        width: i32,
+        height: i32,
+        stride: i32,
+        format: wl_shm::Format,
+        udata: U,
+        qh: &QueueHandle<D>,
+    ) -> wl_buffer::WlBuffer
+    where
+        D: Dispatch<wl_buffer::WlBuffer, U> + 'static,
+        U: Send + Sync + 'static,
+    {
+        self.pool
+            .create_buffer(offset, width, height, stride, format, qh, udata)
+    }
+
+    /// Create a new buffer to this pool.
+    ///
+    /// This is identical to [`Self::create_buffer`], but allows using a custom [`ObjectData`]
+    /// implementation instead of relying on the [Dispatch] interface.
+    #[allow(clippy::too_many_arguments)]
+    pub fn create_buffer_raw(
+        &mut self,
+        offset: i32,
+        width: i32,
+        height: i32,
+        stride: i32,
+        format: wl_shm::Format,
+        data: Arc<dyn ObjectData + 'static>,
+    ) -> wl_buffer::WlBuffer {
+        self.pool
+            .send_constructor(
+                wl_shm_pool::Request::CreateBuffer {
+                    offset,
+                    width,
+                    height,
+                    stride,
+                    format: WEnum::Value(format),
+                },
+                data,
+            )
+            .unwrap_or_else(|_| Proxy::inert(self.pool.backend().clone()))
+    }
+
+    /// Returns the pool object used to communicate with the server.
+    pub fn pool(&self) -> &wl_shm_pool::WlShmPool {
+        &self.pool
+    }
+}
+
+impl AsFd for RawPool {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.mem_file.as_fd()
+    }
+}
+
+impl From<RawPool> for OwnedFd {
+    fn from(pool: RawPool) -> Self {
+        pool.mem_file.into()
+    }
+}
+
+impl io::Write for RawPool {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        io::Write::write(&mut self.mem_file, buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        io::Write::flush(&mut self.mem_file)
+    }
+}
+
+impl io::Seek for RawPool {
+    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+        io::Seek::seek(&mut self.mem_file, pos)
+    }
+}
+
+impl RawPool {
+    fn create_shm_fd() -> io::Result<OwnedFd> {
+        #[cfg(target_os = "linux")]
+        {
+            match RawPool::create_memfd() {
+                Ok(fd) => return Ok(fd),
+
+                // Not supported, use fallback.
+                Err(Errno::NOSYS) => (),
+
+                Err(err) => return Err(Into::<io::Error>::into(err)),
+            }
+        }
+
+        let time = SystemTime::now();
+        let mut mem_file_handle = format!(
+            "/smithay-client-toolkit-{}",
+            time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
+        );
+
+        loop {
+            let flags = OFlags::CREATE | OFlags::EXCL | OFlags::RDWR;
+
+            let mode = Mode::RUSR | Mode::WUSR;
+
+            match rustix::shm::open(mem_file_handle.as_str(), flags, mode) {
+                Ok(fd) => match rustix::shm::unlink(mem_file_handle.as_str()) {
+                    Ok(()) => return Ok(fd),
+
+                    Err(errno) => {
+                        return Err(errno.into());
+                    }
+                },
+
+                Err(Errno::EXIST) => {
+                    // Change the handle if we happen to be duplicate.
+                    let time = SystemTime::now();
+
+                    mem_file_handle = format!(
+                        "/smithay-client-toolkit-{}",
+                        time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
+                    );
+                }
+
+                Err(Errno::INTR) => (),
+
+                Err(err) => return Err(err.into()),
+            }
+        }
+    }
+
+    #[cfg(target_os = "linux")]
+    fn create_memfd() -> rustix::io::Result<OwnedFd> {
+        use rustix::fs::{MemfdFlags, SealFlags};
+
+        loop {
+            let name = c"smithay-client-toolkit";
+            let flags = MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC;
+
+            match rustix::fs::memfd_create(name, flags) {
+                Ok(fd) => {
+                    // We only need to seal for the purposes of optimization, ignore the errors.
+                    let _ = rustix::fs::fcntl_add_seals(&fd, SealFlags::SHRINK | SealFlags::SEAL);
+                    return Ok(fd);
+                }
+
+                Err(Errno::INTR) => (),
+
+                Err(err) => return Err(err),
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+struct DestroyOnDropPool(wl_shm_pool::WlShmPool);
+
+impl Deref for DestroyOnDropPool {
+    type Target = wl_shm_pool::WlShmPool;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl Drop for DestroyOnDropPool {
+    fn drop(&mut self) {
+        self.0.destroy();
+    }
+}
+
+#[derive(Debug)]
+struct ShmPoolData;
+
+impl ObjectData for ShmPoolData {
+    fn event(
+        self: Arc<Self>,
+        _: &wayland_client::backend::Backend,
+        _: wayland_client::backend::protocol::Message<wayland_client::backend::ObjectId, OwnedFd>,
+    ) -> Option<Arc<(dyn ObjectData + 'static)>> {
+        unreachable!("wl_shm_pool has no events")
+    }
+
+    fn destroyed(&self, _: wayland_client::backend::ObjectId) {}
+}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
new file mode 100644
index 00000000..ab52c5f6
--- /dev/null
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
@@ -0,0 +1,596 @@
+//! A pool implementation based on buffer slots
+
+use std::io;
+use std::{
+    os::unix::io::{AsRawFd, OwnedFd},
+    sync::{
+        atomic::{AtomicU8, AtomicUsize, Ordering},
+        Arc, Mutex, Weak,
+    },
+};
+
+use wayland_client::backend::protocol::Message;
+use wayland_client::backend::{ObjectData, ObjectId};
+use wayland_client::{
+    protocol::{wl_buffer, wl_shm, wl_surface},
+    Proxy,
+};
+
+use crate::wayland::shm::raw::RawPool;
+use crate::wayland::shm::CreatePoolError;
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum CreateBufferError {
+    /// Slot creation error.
+    #[error(transparent)]
+    Io(#[from] io::Error),
+
+    /// Pool mismatch.
+    #[error("Incorrect pool for slot")]
+    PoolMismatch,
+
+    /// Slot size mismatch
+    #[error("Requested buffer size is too large for slot")]
+    SlotTooSmall,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub(crate) enum ActivateSlotError {
+    /// Buffer was already active
+    #[error("Buffer was already active")]
+    AlreadyActive,
+}
+
+#[derive(Debug)]
+pub(crate) struct SlotPool {
+    pub(crate) inner: RawPool,
+    free_list: Arc<Mutex<Vec<FreelistEntry>>>,
+}
+
+#[derive(Debug)]
+struct FreelistEntry {
+    offset: usize,
+    len: usize,
+}
+
+/// A chunk of memory allocated from a [`SlotPool`]
+///
+/// Retaining this object is only required if you wish to resize or change the buffer's format
+/// without changing the contents of the backing memory.
+#[derive(Debug)]
+pub(crate) struct Slot {
+    inner: Arc<SlotInner>,
+}
+
+#[derive(Debug)]
+struct SlotInner {
+    free_list: Weak<Mutex<Vec<FreelistEntry>>>,
+    offset: usize,
+    len: usize,
+    active_buffers: AtomicUsize,
+    /// Count of all "real" references to this slot.  This includes all Slot objects and any
+    /// [`BufferData`] object that is not in the DEAD state.  When this reaches zero, the memory for
+    /// this slot will return to the [`free_list`].  It is not possible for it to reach zero and have a
+    /// Slot or Buffer referring to it.
+    all_refs: AtomicUsize,
+}
+
+/// A wrapper around a [`wl_buffer::WlBuffer`] which has been allocated via a [`SlotPool`].
+///
+/// When this object is dropped, the buffer will be destroyed immediately if it is not active, or
+/// upon the server's release if it is.
+#[derive(Debug)]
+pub(crate) struct Buffer {
+    inner: wl_buffer::WlBuffer,
+    height: i32,
+    stride: i32,
+    slot: Slot,
+}
+
+/// [`ObjectData`] for the [`WlBuffer`]
+#[derive(Debug)]
+struct BufferData {
+    inner: Arc<SlotInner>,
+    state: AtomicU8,
+}
+
+// These constants define the value of BufferData::state, since AtomicEnum does not exist.
+impl BufferData {
+    /// Buffer is counted in [`active_buffers`] list; will return to INACTIVE on Release.
+    const ACTIVE: u8 = 0;
+
+    /// Buffer is not counted in [`active_buffers`] list, but also has not been destroyed.
+    const INACTIVE: u8 = 1;
+
+    /// Buffer is counted in [`active_buffers`] list; will move to DEAD on Release
+    const DESTROY_ON_RELEASE: u8 = 2;
+
+    /// Buffer has been destroyed
+    const DEAD: u8 = 3;
+
+    /// Value that is [`ORed`] on buffer release to transition to the next state
+    const RELEASE_SET: u8 = 1;
+
+    /// Value that is [`ORed`] on buffer destroy to transition to the next state
+    const DESTROY_SET: u8 = 2;
+
+    /// Call after successfully transitioning the state to DEAD
+    fn record_death(&self) {
+        drop(Slot {
+            inner: self.inner.clone(),
+        });
+    }
+}
+
+impl SlotPool {
+    pub(crate) fn new(len: usize, shm: &wl_shm::WlShm) -> Result<Self, CreatePoolError> {
+        let inner = RawPool::new(len, shm)?;
+        let free_list = Arc::new(Mutex::new(vec![FreelistEntry {
+            offset: 0,
+            len: inner.len(),
+        }]));
+        Ok(SlotPool { inner, free_list })
+    }
+
+    /// Create a new buffer in a new slot.
+    ///
+    /// This returns the buffer and the canvas.  The parameters are:
+    ///
+    /// - `width`: the width of this buffer (in pixels)
+    /// - `height`: the height of this buffer (in pixels)
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+    /// - `format`: the encoding format of the pixels. Using a format that was not
+    ///   advertised to the `wl_shm` global by the server is a protocol error and will
+    ///   terminate your connection.
+    ///
+    /// The [Slot] for this buffer will have exactly the size required for the data.  It can be
+    /// accessed via [`Buffer::slot`] to create additional buffers that point to the same data.  This
+    /// is required if you wish to change formats, buffer dimensions, or attach a canvas to
+    /// multiple surfaces.
+    ///
+    /// For more control over sizing, use [`Self::new_slot`] and [`Self::create_buffer_in`].
+    pub(crate) fn create_buffer(
+        &mut self,
+        width: i32,
+        height: i32,
+        stride: i32,
+        format: wl_shm::Format,
+    ) -> Result<(Buffer, &mut [u8]), CreateBufferError> {
+        let len = (height as usize) * (stride as usize);
+        let slot = self.new_slot(len)?;
+        let buffer = self.create_buffer_in(&slot, width, height, stride, format)?;
+        let canvas = self.raw_data_mut(&slot);
+        Ok((buffer, canvas))
+    }
+
+    /// Get the bytes corresponding to a given slot or buffer if drawing to the slot is permitted.
+    ///
+    /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
+    /// to this pool.
+    pub(crate) fn canvas(&mut self, key: &impl CanvasKey) -> Option<&mut [u8]> {
+        key.canvas(self)
+    }
+
+    /// Returns the size, in bytes, of this pool.
+    #[allow(clippy::len_without_is_empty)]
+    pub(crate) fn len(&self) -> usize {
+        self.inner.len()
+    }
+
+    /// Resizes the memory pool, notifying the server the pool has changed in size.
+    ///
+    /// This is an optimization; the pool automatically resizes when you allocate new slots.
+    pub(crate) fn resize(&mut self, size: usize) -> io::Result<()> {
+        let old_len = self.inner.len();
+        self.inner.resize(size)?;
+        let new_len = self.inner.len();
+        if old_len == new_len {
+            return Ok(());
+        }
+        // add the new memory to the freelist
+        let mut free = self.free_list.lock().unwrap();
+        if let Some(FreelistEntry { offset, len }) = free.last_mut() {
+            if *offset + *len == old_len {
+                *len += new_len - old_len;
+                return Ok(());
+            }
+        }
+        free.push(FreelistEntry {
+            offset: old_len,
+            len: new_len - old_len,
+        });
+        Ok(())
+    }
+
+    fn alloc(&mut self, size: usize) -> io::Result<usize> {
+        let mut free = self.free_list.lock().unwrap();
+        for FreelistEntry { offset, len } in free.iter_mut() {
+            if *len >= size {
+                let rv = *offset;
+                *len -= size;
+                *offset += size;
+                return Ok(rv);
+            }
+        }
+        let mut rv = self.inner.len();
+        let mut pop_tail = false;
+        if let Some(FreelistEntry { offset, len }) = free.last() {
+            if offset + len == self.inner.len() {
+                rv -= len;
+                pop_tail = true;
+            }
+        }
+        // resize like Vec::reserve, always at least doubling
+        let target = std::cmp::max(rv + size, self.inner.len() * 2);
+        self.inner.resize(target)?;
+        // adjust the end of the freelist here
+        if pop_tail {
+            free.pop();
+        }
+        if target > rv + size {
+            free.push(FreelistEntry {
+                offset: rv + size,
+                len: target - rv - size,
+            });
+        }
+        Ok(rv)
+    }
+
+    fn free(free_list: &Mutex<Vec<FreelistEntry>>, mut offset: usize, mut len: usize) {
+        let mut free = free_list.lock().unwrap();
+        let mut nf = Vec::with_capacity(free.len() + 1);
+        for &FreelistEntry {
+            offset: ioff,
+            len: ilen,
+        } in free.iter()
+        {
+            if ioff + ilen == offset {
+                offset = ioff;
+                len += ilen;
+                continue;
+            }
+            if ioff == offset + len {
+                len += ilen;
+                continue;
+            }
+            if ioff > offset + len && len != 0 {
+                nf.push(FreelistEntry { offset, len });
+                len = 0;
+            }
+            if ilen != 0 {
+                nf.push(FreelistEntry {
+                    offset: ioff,
+                    len: ilen,
+                });
+            }
+        }
+        if len != 0 {
+            nf.push(FreelistEntry { offset, len });
+        }
+        *free = nf;
+    }
+
+    /// Create a new slot with the given size in bytes.
+    pub(crate) fn new_slot(&mut self, mut len: usize) -> io::Result<Slot> {
+        len = (len + 63) & !63;
+        let offset = self.alloc(len)?;
+
+        Ok(Slot {
+            inner: Arc::new(SlotInner {
+                free_list: Arc::downgrade(&self.free_list),
+                offset,
+                len,
+                active_buffers: AtomicUsize::new(0),
+                all_refs: AtomicUsize::new(1),
+            }),
+        })
+    }
+
+    /// Get the bytes corresponding to a given slot.
+    ///
+    /// Note: prefer using [`Self::canvas`], which will prevent drawing to a buffer that has not been
+    /// released by the server.
+    ///
+    /// Returns an empty buffer if the slot does not belong to this pool.
+    pub(crate) fn raw_data_mut(&mut self, slot: &Slot) -> &mut [u8] {
+        if slot.inner.free_list.as_ptr() == Arc::as_ptr(&self.free_list) {
+            &mut self.inner.mmap()[slot.inner.offset..][..slot.inner.len]
+        } else {
+            &mut []
+        }
+    }
+
+    /// Create a new buffer corresponding to a slot.
+    ///
+    /// The parameters are:
+    ///
+    /// - `width`: the width of this buffer (in pixels)
+    /// - `height`: the height of this buffer (in pixels)
+    /// - `stride`: distance (in bytes) between the beginning of a row and the next one
+    /// - `format`: the encoding format of the pixels. Using a format that was not
+    ///   advertised to the `wl_shm` global by the server is a protocol error and will
+    ///   terminate your connection
+    pub(crate) fn create_buffer_in(
+        &mut self,
+        slot: &Slot,
+        width: i32,
+        height: i32,
+        stride: i32,
+        format: wl_shm::Format,
+    ) -> Result<Buffer, CreateBufferError> {
+        let offset = slot.inner.offset as i32;
+        let len = (height as usize) * (stride as usize);
+        if len > slot.inner.len {
+            return Err(CreateBufferError::SlotTooSmall);
+        }
+
+        if slot.inner.free_list.as_ptr() != Arc::as_ptr(&self.free_list) {
+            return Err(CreateBufferError::PoolMismatch);
+        }
+
+        let slot = slot.clone();
+        // take a ref for the BufferData, which will be destroyed by BufferData::record_death
+        slot.inner.all_refs.fetch_add(1, Ordering::Relaxed);
+        let data = Arc::new(BufferData {
+            inner: slot.inner.clone(),
+            state: AtomicU8::new(BufferData::INACTIVE),
+        });
+        let buffer = self
+            .inner
+            .create_buffer_raw(offset, width, height, stride, format, data);
+        Ok(Buffer {
+            inner: buffer,
+            height,
+            stride,
+            slot,
+        })
+    }
+}
+
+impl Clone for Slot {
+    fn clone(&self) -> Self {
+        let inner = self.inner.clone();
+        inner.all_refs.fetch_add(1, Ordering::Relaxed);
+        Slot { inner }
+    }
+}
+
+impl Drop for Slot {
+    fn drop(&mut self) {
+        if self.inner.all_refs.fetch_sub(1, Ordering::Relaxed) == 1 {
+            if let Some(free_list) = self.inner.free_list.upgrade() {
+                SlotPool::free(&free_list, self.inner.offset, self.inner.len);
+            }
+        }
+    }
+}
+
+impl Drop for SlotInner {
+    fn drop(&mut self) {
+        debug_assert_eq!(*self.all_refs.get_mut(), 0);
+    }
+}
+
+/// A helper trait for [`SlotPool::canvas`].
+pub(crate) trait CanvasKey {
+    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]>;
+}
+
+impl Slot {
+    /// Return true if there are buffers referencing this slot whose contents are being accessed
+    /// by the server.
+    pub(crate) fn has_active_buffers(&self) -> bool {
+        self.inner.active_buffers.load(Ordering::Relaxed) != 0
+    }
+
+    /// Returns the size, in bytes, of this slot.
+    #[allow(clippy::len_without_is_empty)]
+    pub(crate) fn len(&self) -> usize {
+        self.inner.len
+    }
+
+    /// Get the bytes corresponding to a given slot if drawing to the slot is permitted.
+    ///
+    /// Returns `None` if there are active buffers in the slot or if the slot does not correspond
+    /// to this pool.
+    pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+        if self.has_active_buffers() {
+            return None;
+        }
+        if self.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
+            Some(&mut pool.inner.mmap()[self.inner.offset..][..self.inner.len])
+        } else {
+            None
+        }
+    }
+}
+
+impl CanvasKey for Slot {
+    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+        self.canvas(pool)
+    }
+}
+
+impl Buffer {
+    /// Attach a buffer to a surface.
+    ///
+    /// This marks the slot as active until the server releases the buffer, which will happen
+    /// automatically assuming the surface is committed without attaching a different buffer.
+    ///
+    /// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that
+    /// could be attached to a surface in a multi-threaded client, make this call while you have
+    /// exclusive access to the corresponding [`SlotPool`].
+    pub(crate) fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> {
+        self.activate()?;
+        surface.attach(Some(&self.inner), 0, 0);
+        Ok(())
+    }
+
+    /// Get the inner buffer.
+    pub(crate) fn wl_buffer(&self) -> &wl_buffer::WlBuffer {
+        &self.inner
+    }
+
+    pub(crate) fn height(&self) -> i32 {
+        self.height
+    }
+
+    pub(crate) fn stride(&self) -> i32 {
+        self.stride
+    }
+
+    fn data(&self) -> Option<&BufferData> {
+        self.inner.object_data()?.downcast_ref()
+    }
+
+    /// Get the bytes corresponding to this buffer if drawing is permitted.
+    ///
+    /// This may be smaller than the canvas associated with the slot.
+    pub(crate) fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+        let len = (self.height as usize) * (self.stride as usize);
+        if self.slot.inner.active_buffers.load(Ordering::Relaxed) != 0 {
+            return None;
+        }
+        if self.slot.inner.free_list.as_ptr() == Arc::as_ptr(&pool.free_list) {
+            Some(&mut pool.inner.mmap()[self.slot.inner.offset..][..len])
+        } else {
+            None
+        }
+    }
+
+    /// Get the slot corresponding to this buffer.
+    pub(crate) fn slot(&self) -> Slot {
+        self.slot.clone()
+    }
+
+    /// Manually mark a buffer as active.
+    ///
+    /// An active buffer prevents drawing on its slot until a Release event is received or until
+    /// manually deactivated.
+    pub(crate) fn activate(&self) -> Result<(), ActivateSlotError> {
+        let data = self.data().expect("UserData type mismatch");
+
+        // This bitwise AND will transition INACTIVE -> ACTIVE, or do nothing if the buffer was
+        // already ACTIVE.  No other ordering is required, as the server will not send a Release
+        // until we send our attach after returning Ok.
+        match data
+            .state
+            .fetch_and(!BufferData::RELEASE_SET, Ordering::Relaxed)
+        {
+            BufferData::INACTIVE => {
+                data.inner.active_buffers.fetch_add(1, Ordering::Relaxed);
+                Ok(())
+            }
+            BufferData::ACTIVE => Err(ActivateSlotError::AlreadyActive),
+            _ => unreachable!("Invalid state in BufferData"),
+        }
+    }
+
+    /// Manually mark a buffer as inactive.
+    ///
+    /// This should be used when the buffer was manually marked as active or when a buffer was
+    /// attached to a surface but not committed.  Calling this function on a buffer that was
+    /// committed to a surface risks making the surface contents undefined.
+    pub(crate) fn deactivate(&self) -> Result<(), ActivateSlotError> {
+        let data = self.data().expect("UserData type mismatch");
+
+        // Same operation as the Release event, but we know the Buffer was not dropped.
+        match data
+            .state
+            .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed)
+        {
+            BufferData::ACTIVE => {
+                data.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+                Ok(())
+            }
+            BufferData::INACTIVE => Err(ActivateSlotError::AlreadyActive),
+            _ => unreachable!("Invalid state in BufferData"),
+        }
+    }
+}
+
+impl CanvasKey for Buffer {
+    fn canvas<'pool>(&self, pool: &'pool mut SlotPool) -> Option<&'pool mut [u8]> {
+        self.canvas(pool)
+    }
+}
+
+impl Drop for Buffer {
+    fn drop(&mut self) {
+        if let Some(data) = self.data() {
+            match data
+                .state
+                .fetch_or(BufferData::DESTROY_SET, Ordering::Relaxed)
+            {
+                BufferData::ACTIVE => {
+                    // server is using the buffer, let ObjectData handle the destroy
+                }
+                BufferData::INACTIVE => {
+                    data.record_death();
+                    self.inner.destroy();
+                }
+                _ => unreachable!("Invalid state in BufferData"),
+            }
+        }
+    }
+}
+
+impl ObjectData for BufferData {
+    fn event(
+        self: Arc<Self>,
+        handle: &wayland_client::backend::Backend,
+        msg: Message<ObjectId, OwnedFd>,
+    ) -> Option<Arc<dyn ObjectData>> {
+        debug_assert!(wayland_client::backend::protocol::same_interface(
+            msg.sender_id.interface(),
+            wl_buffer::WlBuffer::interface()
+        ));
+        debug_assert!(msg.opcode == 0);
+
+        match self
+            .state
+            .fetch_or(BufferData::RELEASE_SET, Ordering::Relaxed)
+        {
+            BufferData::ACTIVE => {
+                self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+            }
+            BufferData::INACTIVE => {
+                // possible spurious release, or someone called deactivate incorrectly
+                eprintln!("Unexpected WlBuffer::Release on an inactive buffer");
+            }
+            BufferData::DESTROY_ON_RELEASE => {
+                self.record_death();
+                self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+
+                // The Destroy message is identical to Release message (no args, same ID), so just reply
+                handle
+                    .send_request(msg.map_fd(|x| x.as_raw_fd()), None, None)
+                    .expect("Unexpected invalid ID");
+            }
+            BufferData::DEAD => {
+                // no-op, this object is already unusable
+            }
+            _ => unreachable!("Invalid state in BufferData"),
+        }
+
+        None
+    }
+
+    fn destroyed(&self, _: ObjectId) {}
+}
+
+impl Drop for BufferData {
+    fn drop(&mut self) {
+        let state = *self.state.get_mut();
+        if state == BufferData::ACTIVE || state == BufferData::DESTROY_ON_RELEASE {
+            // Release the active-buffer count
+            self.inner.active_buffers.fetch_sub(1, Ordering::Relaxed);
+        }
+
+        if state != BufferData::DEAD {
+            // nobody has ever transitioned state to DEAD, so we are responsible for freeing the
+            // extra reference
+            self.record_death();
+        }
+    }
+}