aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/ri/river-mk-keymap/src
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-06-29 10:32:13 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-06-29 10:32:13 +0200
commit3d507acb42554b2551024ee3ca8490c203a1a9f8 (patch)
treececa79f3696cf9eab522be55c07c32e38de5edaf /pkgs/by-name/ri/river-mk-keymap/src
parentflake.lock: Update (diff)
downloadnixos-config-3d507acb42554b2551024ee3ca8490c203a1a9f8.zip
pkgs/river-mk-keymap: Improve with key-chord support and which-key interface
Diffstat (limited to 'pkgs/by-name/ri/river-mk-keymap/src')
-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.rs367
-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.rs52
-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, 2608 insertions, 148 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![];
+ /// # 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 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!(),
- };
+ 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),
+ )
+ }
- value
- .modes
- .iter()
- .map(|mode| {
- let mut riverctl = Command::new("riverctl");
- riverctl.args([value.map_mode.as_command(), mode, &mods, &key_value]);
+ 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),
+ )
+ }
- riverctl.args(value.command.iter().map(String::as_str));
- riverctl
- })
- .collect::<Vec<_>>()
+ 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()))?;
+ let keymap_path = &args.keymap.canonicalize().with_context(|| {
+ format!(
+ "Failed to canonicalize kepmay path: '{}'",
+ args.keymap.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:?}'"))?;
+ let config = {
+ let keymap_file = fs::read_to_string(keymap_path).with_context(|| {
+ format!(
+ "Failed to open keymap file at: '{}'.",
+ keymap_path.display()
+ )
+ })?;
- if !status.success() {
- eprintln!("Command ('{command:?}') returned with non zero exit code: {status}");
+ 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();
+ }
+ }
+}