diff options
Diffstat (limited to '')
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(¤t_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(); + } + } +} |