From 7d9cd198babbb32d29dd119c8b95c03e13b12858 Mon Sep 17 00:00:00 2001 From: kossLAN Date: Fri, 29 May 2026 12:09:20 -0400 Subject: [PATCH] keyboard: move keymap state into workspace crate --- Cargo.lock | 13 + Cargo.toml | 2 + keyboard/Cargo.toml | 16 + keyboard/src/lib.rs | 357 ++++++++++++++++++ src/backend.rs | 2 +- src/kbvm.rs | 216 +---------- src/keyboard.rs | 118 +----- src/utils.rs | 14 +- src/utils/linkedlist.rs | 1 + utils/src/clonecell.rs | 2 + {src/utils => utils/src}/event_listener.rs | 17 +- utils/src/lib.rs | 2 + utils/src/linkedlist.rs | 414 +++++++++++++++++++++ 13 files changed, 832 insertions(+), 342 deletions(-) create mode 100644 keyboard/Cargo.toml create mode 100644 keyboard/src/lib.rs rename {src/utils => utils/src}/event_listener.rs (86%) create mode 100644 utils/src/linkedlist.rs diff --git a/Cargo.lock b/Cargo.lock index ad7509e9..0dce200a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,7 @@ dependencies = [ "jay-gfx-types", "jay-input-types", "jay-io-uring", + "jay-keyboard", "jay-layout-animation", "jay-libinput", "jay-logger", @@ -930,6 +931,18 @@ dependencies = [ "uapi", ] +[[package]] +name = "jay-keyboard" +version = "0.1.0" +dependencies = [ + "blake3", + "jay-input-types", + "jay-utils", + "kbvm", + "thiserror", + "uapi", +] + [[package]] name = "jay-layout-animation" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ff0339e9..b93ad9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ members = [ "logger", "video-types", "input-types", + "keyboard", "gfx-types", "theme", "clientmem", @@ -99,6 +100,7 @@ jay-bugs = { version = "0.1.0", path = "bugs" } jay-logger = { version = "0.1.0", path = "logger" } jay-video-types = { version = "0.1.0", path = "video-types" } jay-input-types = { version = "0.1.0", path = "input-types" } +jay-keyboard = { version = "0.1.0", path = "keyboard" } jay-gfx-types = { version = "0.1.0", path = "gfx-types" } jay-theme = { version = "0.1.0", path = "theme" } jay-clientmem = { version = "0.1.0", path = "clientmem" } diff --git a/keyboard/Cargo.toml b/keyboard/Cargo.toml new file mode 100644 index 00000000..99d62753 --- /dev/null +++ b/keyboard/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jay-keyboard" +version = "0.1.0" +edition = "2024" +license = "GPL-3.0-only" +description = "Keyboard state and keymap helpers for the Jay compositor" +repository = "https://github.com/mahkoh/jay" + +[dependencies] +jay-input-types = { version = "0.1.0", path = "../input-types" } +jay-utils = { version = "0.1.0", path = "../utils" } + +blake3 = "1.8.2" +kbvm = { version = "0.1.6", features = ["compose"] } +thiserror = "2.0.11" +uapi = "0.2.13" diff --git a/keyboard/src/lib.rs b/keyboard/src/lib.rs new file mode 100644 index 00000000..f584787d --- /dev/null +++ b/keyboard/src/lib.rs @@ -0,0 +1,357 @@ +use { + jay_input_types::{ + LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds, + }, + jay_utils::{ + event_listener::EventSource, + numcell::NumCell, + oserror::{OsError, OsErrorExt, OsErrorExt2}, + syncqueue::SyncQueue, + vecset::VecSet, + }, + kbvm::{ + Components, + lookup::LookupTable, + state_machine::{self, Event, StateMachine}, + xkb::{ + self, Keymap, + diagnostic::{Diagnostic, WriteToLog}, + keymap::{Indicator, IndicatorMatcher}, + rmlvo::Group, + }, + }, + std::{ + cell::{Ref, RefCell}, + io::Write, + rc::Rc, + }, + thiserror::Error, + uapi::{OwnedFd, c}, +}; + +#[derive(Debug, Error)] +pub enum KeyboardError { + #[error("Could not create a keymap memfd")] + KeymapMemfd(#[source] OsError), + #[error("Could not copy the keymap")] + KeymapCopy(#[source] OsError), +} + +#[derive(Debug)] +pub struct KeyboardStateIds { + next: NumCell, +} + +impl Default for KeyboardStateIds { + fn default() -> Self { + Self { + next: NumCell::new(1), + } + } +} + +impl KeyboardStateIds { + pub fn next(&self) -> KeyboardStateId { + KeyboardStateId(self.next.fetch_add(1)) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct KeyboardStateId(u64); + +impl KeyboardStateId { + pub fn raw(&self) -> u64 { + self.0 + } + + pub fn from_raw(id: u64) -> Self { + Self(id) + } +} + +impl std::fmt::Display for KeyboardStateId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +#[derive(Debug, Error)] +pub enum KbvmError { + #[error("could not parse the keymap")] + CouldNotParseKeymap(#[source] Diagnostic), + #[error("Could not create a keymap memfd")] + KeymapMemfd(#[source] OsError), +} + +pub struct KbvmContext { + pub ctx: xkb::Context, +} + +impl Default for KbvmContext { + fn default() -> Self { + let mut ctx = xkb::Context::builder(); + ctx.enable_environment(true); + Self { ctx: ctx.build() } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct KbvmMapId([u8; 32]); + +pub struct KbvmMap { + pub id: KbvmMapId, + pub state_machine: StateMachine, + pub lookup_table: LookupTable, + pub map: KeymapFd, + pub xwayland_map: KeymapFd, + pub has_indicators: bool, + pub num_lock: Option, + pub caps_lock: Option, + pub scroll_lock: Option, + pub compose: Option, + pub kana: Option, +} + +pub struct KbvmState { + pub map: Rc, + pub state: state_machine::State, + pub kb_state: KeyboardState, +} + +pub struct KeyboardState { + pub id: KeyboardStateId, + pub map: Rc, + pub pressed_keys: VecSet, + pub mods: Components, + pub leds: Leds, + pub leds_changed: EventSource, +} + +pub trait LedsListener { + fn leds(&self, leds: Leds); +} + +pub trait DynKeyboardState { + fn borrow(&self) -> Ref<'_, KeyboardState>; +} + +impl DynKeyboardState for RefCell { + fn borrow(&self) -> Ref<'_, KeyboardState> { + self.borrow() + } +} + +impl DynKeyboardState for RefCell { + fn borrow(&self) -> Ref<'_, KeyboardState> { + Ref::map(self.borrow(), |v| &v.kb_state) + } +} + +impl KeyboardState { + pub fn apply_event(&mut self, event: Event) -> bool { + let changed = self.mods.apply_event(event); + if changed && self.map.has_indicators { + self.update_leds(); + } + changed + } + + pub fn update_leds(&mut self) { + if !self.map.has_indicators { + return; + } + let mut new = Leds::none(); + macro_rules! map_led { + ($field:ident, $led:ident) => { + if let Some(m) = &self.map.$field + && m.matches(&self.mods) + { + new |= $led; + } + }; + } + map_led!(num_lock, LED_NUM_LOCK); + map_led!(caps_lock, LED_CAPS_LOCK); + map_led!(scroll_lock, LED_SCROLL_LOCK); + map_led!(compose, LED_COMPOSE); + map_led!(kana, LED_KANA); + if new != self.leds { + self.leds = new; + for listener in self.leds_changed.iter() { + listener.leds(new); + } + } + } +} + +#[derive(Clone)] +pub struct KeymapFd { + pub map: Rc, + pub len: usize, +} + +impl KeymapFd { + pub fn create_unprotected_fd(&self) -> Result { + let fd = uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) + .map_os_err(KeyboardError::KeymapMemfd)?; + let target = self.len as c::off_t; + let mut pos = 0; + while pos < target { + let rem = target - pos; + let res = uapi::sendfile(fd.raw(), self.map.raw(), Some(&mut pos), rem as usize) + .to_os_error(); + match res { + Ok(_) | Err(OsError(c::EINTR)) => {} + Err(e) => return Err(KeyboardError::KeymapCopy(e)), + } + } + Ok(Self { + map: Rc::new(fd), + len: self.len, + }) + } +} + +impl KbvmContext { + pub fn parse_keymap(&self, keymap: &[u8]) -> Result, KbvmError> { + let map = self + .ctx + .keymap_from_bytes(WriteToLog, None, keymap) + .map_err(KbvmError::CouldNotParseKeymap)?; + let id = KbvmMapId(*blake3::hash(keymap).as_bytes()); + self.create_keymap(id, map) + } + + pub fn keymap_from_rmlvo( + &self, + rules: Option<&str>, + model: Option<&str>, + layout: Option<&str>, + variant: Option<&str>, + options: Option<&str>, + ) -> Result, KbvmError> { + let mut groups = None::>; + if layout.is_some() || variant.is_some() { + groups = Some( + Group::from_layouts_and_variants( + layout.unwrap_or_default(), + variant.unwrap_or_default(), + ) + .collect(), + ); + } + let mut options_vec = None::>; + if let Some(options) = options { + options_vec = Some(options.split(",").collect()); + } + self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref()) + } + + pub fn keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option<&[Group<'_>]>, + options: Option<&[&str]>, + ) -> Result, KbvmError> { + let map = self + .ctx + .keymap_from_names(WriteToLog, rules, model, groups, options); + let id = KbvmMapId(*blake3::hash(map.format().to_string().as_bytes()).as_bytes()); + self.create_keymap(id, map) + } + + fn create_keymap(&self, id: KbvmMapId, map: Keymap) -> Result, KbvmError> { + let mut has_indicators = false; + let mut num_lock = None; + let mut caps_lock = None; + let mut scroll_lock = None; + let mut compose = None; + let mut kana = None; + for indicator in map.indicators() { + match indicator.name() { + Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()), + Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()), + Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()), + Indicator::COMPOSE => compose = Some(indicator.matcher()), + Indicator::KANA => kana = Some(indicator.matcher()), + _ => continue, + } + has_indicators = true; + } + let builder = map.to_builder(); + let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?; + let (_, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?; + Ok(Rc::new(KbvmMap { + id, + state_machine: builder.build_state_machine(), + map, + xwayland_map, + lookup_table: builder.build_lookup_table(), + has_indicators, + num_lock, + caps_lock, + scroll_lock, + compose, + kana, + })) + } +} + +fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> { + let mut format = map.format(); + if xwayland { + format = format.lookup_only(true).rename_long_keys(true); + } + let str = format!("{}\n", format); + let mut memfd = + uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).to_os_error()?; + memfd.write_all(str.as_bytes())?; + memfd.write_all(&[0])?; + uapi::lseek(memfd.raw(), 0, c::SEEK_SET).to_os_error()?; + uapi::fcntl_add_seals( + memfd.raw(), + c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, + ) + .to_os_error()?; + let fd = KeymapFd { + map: Rc::new(memfd), + len: str.len() + 1, + }; + Ok((str, fd)) +} + +impl KbvmMap { + pub fn state(self: &Rc, id: KeyboardStateId) -> KbvmState { + KbvmState { + map: self.clone(), + state: self.state_machine.create_state(), + kb_state: KeyboardState { + id, + map: self.clone(), + pressed_keys: Default::default(), + mods: Default::default(), + leds: Default::default(), + leds_changed: Default::default(), + }, + } + } +} + +impl KbvmState { + pub fn apply_events(&mut self, events: &SyncQueue) { + let state = &mut self.kb_state; + while let Some(event) = events.pop() { + state.apply_event(event); + match event { + Event::KeyDown(kc) => { + state.pressed_keys.insert(kc.to_evdev()); + } + Event::KeyUp(kc) => { + state.pressed_keys.remove(&kc.to_evdev()); + } + _ => {} + } + } + } +} diff --git a/src/backend.rs b/src/backend.rs index 4d1b0e54..34fc2bbc 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -44,7 +44,7 @@ pub mod transaction; pub use jay_input_types::{ AXIS_120, AxisSource, ButtonState, InputDeviceAccelProfile, InputDeviceClickMethod, KeyState, - LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds, ScrollAxis, + Leds, ScrollAxis, }; linear_ids!(ConnectorIds, ConnectorId); diff --git a/src/kbvm.rs b/src/kbvm.rs index 5d8b2921..cbb7c6d8 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -1,77 +1,21 @@ +pub use jay_keyboard::{KbvmContext, KbvmError, KbvmMap, KbvmMapId, KbvmState}; + use { crate::{ backend::KeyState, ifs::wl_seat::WlSeatGlobal, - keyboard::{DynKeyboardState, KeyboardState, KeyboardStateId, KeymapFd}, - utils::{ - oserror::{OsError, OsErrorExt}, - syncqueue::SyncQueue, - vecset::VecSet, - }, + utils::{syncqueue::SyncQueue, vecset::VecSet}, }, kbvm::{ Keycode, - lookup::LookupTable, - state_machine::{self, Direction, Event, StateMachine}, - xkb::{ - self, Keymap, - diagnostic::{Diagnostic, WriteToLog}, - keymap::{Indicator, IndicatorMatcher}, - rmlvo::Group, - }, + state_machine::{Direction, Event}, }, std::{ - cell::{Cell, Ref, RefCell}, - io::Write, + cell::{Cell, RefCell}, rc::Rc, }, - thiserror::Error, - uapi::c, }; -#[derive(Debug, Error)] -pub enum KbvmError { - #[error("could not parse the keymap")] - CouldNotParseKeymap(#[source] Diagnostic), - #[error("Could not create a keymap memfd")] - KeymapMemfd(#[source] OsError), -} - -pub struct KbvmContext { - pub ctx: xkb::Context, -} - -impl Default for KbvmContext { - fn default() -> Self { - let mut ctx = xkb::Context::builder(); - ctx.enable_environment(true); - Self { ctx: ctx.build() } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct KbvmMapId([u8; 32]); - -pub struct KbvmMap { - pub id: KbvmMapId, - pub state_machine: StateMachine, - pub lookup_table: LookupTable, - pub map: KeymapFd, - pub xwayland_map: KeymapFd, - pub has_indicators: bool, - pub num_lock: Option, - pub caps_lock: Option, - pub scroll_lock: Option, - pub compose: Option, - pub kana: Option, -} - -pub struct KbvmState { - pub map: Rc, - pub state: state_machine::State, - pub kb_state: KeyboardState, -} - pub struct PhysicalKeyboardState { state: Rc>, inner: RefCell, @@ -85,156 +29,6 @@ struct PkInner { event_stash: Vec, } -impl DynKeyboardState for RefCell { - fn borrow(&self) -> Ref<'_, KeyboardState> { - Ref::map(self.borrow(), |v| &v.kb_state) - } -} - -impl KbvmContext { - pub fn parse_keymap(&self, keymap: &[u8]) -> Result, KbvmError> { - let map = self - .ctx - .keymap_from_bytes(WriteToLog, None, keymap) - .map_err(KbvmError::CouldNotParseKeymap)?; - let id = KbvmMapId(*blake3::hash(keymap).as_bytes()); - self.create_keymap(id, map) - } - - pub fn keymap_from_rmlvo( - &self, - rules: Option<&str>, - model: Option<&str>, - layout: Option<&str>, - variant: Option<&str>, - options: Option<&str>, - ) -> Result, KbvmError> { - let mut groups = None::>; - if layout.is_some() || variant.is_some() { - groups = Some( - Group::from_layouts_and_variants( - layout.unwrap_or_default(), - variant.unwrap_or_default(), - ) - .collect(), - ); - } - let mut options_vec = None::>; - if let Some(options) = options { - options_vec = Some(options.split(",").collect()); - } - self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref()) - } - - pub fn keymap_from_names( - &self, - rules: Option<&str>, - model: Option<&str>, - groups: Option<&[Group<'_>]>, - options: Option<&[&str]>, - ) -> Result, KbvmError> { - let map = self - .ctx - .keymap_from_names(WriteToLog, rules, model, groups, options); - let id = KbvmMapId(*blake3::hash(map.format().to_string().as_bytes()).as_bytes()); - self.create_keymap(id, map) - } - - fn create_keymap(&self, id: KbvmMapId, map: Keymap) -> Result, KbvmError> { - let mut has_indicators = false; - let mut num_lock = None; - let mut caps_lock = None; - let mut scroll_lock = None; - let mut compose = None; - let mut kana = None; - for indicator in map.indicators() { - match indicator.name() { - Indicator::NUM_LOCK => num_lock = Some(indicator.matcher()), - Indicator::CAPS_LOCK => caps_lock = Some(indicator.matcher()), - Indicator::SCROLL_LOCK => scroll_lock = Some(indicator.matcher()), - Indicator::COMPOSE => compose = Some(indicator.matcher()), - Indicator::KANA => kana = Some(indicator.matcher()), - _ => continue, - } - has_indicators = true; - } - let builder = map.to_builder(); - let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?; - let (_, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?; - Ok(Rc::new(KbvmMap { - id, - state_machine: builder.build_state_machine(), - map, - xwayland_map, - lookup_table: builder.build_lookup_table(), - has_indicators, - num_lock, - caps_lock, - scroll_lock, - compose, - kana, - })) - } -} - -fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> { - let mut format = map.format(); - if xwayland { - format = format.lookup_only(true).rename_long_keys(true); - } - let str = format!("{}\n", format); - let mut memfd = - uapi::memfd_create("keymap", c::MFD_CLOEXEC | c::MFD_ALLOW_SEALING).to_os_error()?; - memfd.write_all(str.as_bytes())?; - memfd.write_all(&[0])?; - uapi::lseek(memfd.raw(), 0, c::SEEK_SET).to_os_error()?; - uapi::fcntl_add_seals( - memfd.raw(), - c::F_SEAL_SEAL | c::F_SEAL_GROW | c::F_SEAL_SHRINK | c::F_SEAL_WRITE, - ) - .to_os_error()?; - let fd = KeymapFd { - map: Rc::new(memfd), - len: str.len() + 1, - }; - Ok((str, fd)) -} - -impl KbvmMap { - pub fn state(self: &Rc, id: KeyboardStateId) -> KbvmState { - KbvmState { - map: self.clone(), - state: self.state_machine.create_state(), - kb_state: KeyboardState { - id, - map: self.clone(), - pressed_keys: Default::default(), - mods: Default::default(), - leds: Default::default(), - leds_changed: Default::default(), - }, - } - } -} - -impl KbvmState { - pub fn apply_events(&mut self, events: &SyncQueue) { - let state = &mut self.kb_state; - while let Some(event) = events.pop() { - state.apply_event(event); - match event { - Event::KeyDown(kc) => { - state.pressed_keys.insert(kc.to_evdev()); - } - Event::KeyUp(kc) => { - state.pressed_keys.remove(&kc.to_evdev()); - } - _ => {} - } - } - } -} - impl PhysicalKeyboardState { pub fn new(state: &Rc>) -> Self { Self { diff --git a/src/keyboard.rs b/src/keyboard.rs index 04d53d7e..90f59904 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,116 +1,4 @@ -use { - crate::{ - backend::{LED_CAPS_LOCK, LED_COMPOSE, LED_KANA, LED_NUM_LOCK, LED_SCROLL_LOCK, Leds}, - kbvm::KbvmMap, - utils::{ - event_listener::EventSource, - oserror::{OsError, OsErrorExt, OsErrorExt2}, - vecset::VecSet, - }, - }, - kbvm::{Components, state_machine::Event}, - std::{ - cell::{Ref, RefCell}, - rc::Rc, - }, - thiserror::Error, - uapi::{OwnedFd, c}, +pub use jay_keyboard::{ + DynKeyboardState, KeyboardError, KeyboardState, KeyboardStateId, KeyboardStateIds, KeymapFd, + LedsListener, }; - -#[derive(Debug, Error)] -pub enum KeyboardError { - #[error("Could not create a keymap memfd")] - KeymapMemfd(#[source] OsError), - #[error("Could not copy the keymap")] - KeymapCopy(#[source] OsError), -} - -linear_ids!(KeyboardStateIds, KeyboardStateId, u64); - -pub struct KeyboardState { - pub id: KeyboardStateId, - pub map: Rc, - pub pressed_keys: VecSet, - pub mods: Components, - pub leds: Leds, - pub leds_changed: EventSource, -} - -pub trait LedsListener { - fn leds(&self, leds: Leds); -} - -pub trait DynKeyboardState { - fn borrow(&self) -> Ref<'_, KeyboardState>; -} - -impl DynKeyboardState for RefCell { - fn borrow(&self) -> Ref<'_, KeyboardState> { - self.borrow() - } -} - -impl KeyboardState { - pub fn apply_event(&mut self, event: Event) -> bool { - let changed = self.mods.apply_event(event); - if changed && self.map.has_indicators { - self.update_leds(); - } - changed - } - - pub fn update_leds(&mut self) { - if !self.map.has_indicators { - return; - } - let mut new = Leds::none(); - macro_rules! map_led { - ($field:ident, $led:ident) => { - if let Some(m) = &self.map.$field - && m.matches(&self.mods) - { - new |= $led; - } - }; - } - map_led!(num_lock, LED_NUM_LOCK); - map_led!(caps_lock, LED_CAPS_LOCK); - map_led!(scroll_lock, LED_SCROLL_LOCK); - map_led!(compose, LED_COMPOSE); - map_led!(kana, LED_KANA); - if new != self.leds { - self.leds = new; - for listener in self.leds_changed.iter() { - listener.leds(new); - } - } - } -} - -#[derive(Clone)] -pub struct KeymapFd { - pub map: Rc, - pub len: usize, -} - -impl KeymapFd { - pub fn create_unprotected_fd(&self) -> Result { - let fd = uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) - .map_os_err(KeyboardError::KeymapMemfd)?; - let target = self.len as c::off_t; - let mut pos = 0; - while pos < target { - let rem = target - pos; - let res = uapi::sendfile(fd.raw(), self.map.raw(), Some(&mut pos), rem as usize) - .to_os_error(); - match res { - Ok(_) | Err(OsError(c::EINTR)) => {} - Err(e) => return Err(KeyboardError::KeymapCopy(e)), - } - } - Ok(Self { - map: Rc::new(fd), - len: self.len, - }) - } -} diff --git a/src/utils.rs b/src/utils.rs index bc8912cd..51e5b893 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -72,7 +72,19 @@ pub mod buffd; pub mod bufio; pub mod clone3; pub mod double_click_state; -pub mod event_listener; +pub mod event_listener { + pub use jay_utils::event_listener::*; + + use {crate::state::State, std::rc::Rc}; + + pub async fn handle_lazy_event_sources(state: Rc) { + handle_lazy_event_sources_of(&state.lazy_event_sources).await; + } + + pub async fn handle_post_layout_event_sources(state: Rc) { + handle_lazy_event_sources_of(&state.post_layout_event_sources).await; + } +} pub mod linkedlist; pub mod line_logger; pub mod object_drop_queue; diff --git a/src/utils/linkedlist.rs b/src/utils/linkedlist.rs index cd976314..817a26dc 100644 --- a/src/utils/linkedlist.rs +++ b/src/utils/linkedlist.rs @@ -340,6 +340,7 @@ impl LinkedNode { LinkedNode { data: node.into() } } + #[allow(dead_code)] pub fn detached(t: T) -> Self { Self::new(Some(t)) } diff --git a/utils/src/clonecell.rs b/utils/src/clonecell.rs index 2d6cd930..8f5a8f11 100644 --- a/utils/src/clonecell.rs +++ b/utils/src/clonecell.rs @@ -94,3 +94,5 @@ unsafe impl UnsafeCellCloneSafe unsafe impl UnsafeCellCloneSafe for Modifiers {} unsafe impl UnsafeCellCloneSafe for Window {} + +unsafe impl UnsafeCellCloneSafe for crate::linkedlist::NodeRef {} diff --git a/src/utils/event_listener.rs b/utils/src/event_listener.rs similarity index 86% rename from src/utils/event_listener.rs rename to utils/src/event_listener.rs index 95820b95..454f0dcd 100644 --- a/src/utils/event_listener.rs +++ b/utils/src/event_listener.rs @@ -1,10 +1,7 @@ use { crate::{ - state::State, - utils::{ - linkedlist::{LinkedList, LinkedListIter, LinkedNode}, - queue::AsyncQueue, - }, + linkedlist::{LinkedList, LinkedListIter, LinkedNode}, + queue::AsyncQueue, }, std::{ cell::Cell, @@ -13,15 +10,7 @@ use { }, }; -pub async fn handle_lazy_event_sources(state: Rc) { - handle_lazy_event_sources_of(&state.lazy_event_sources).await; -} - -pub async fn handle_post_layout_event_sources(state: Rc) { - handle_lazy_event_sources_of(&state.post_layout_event_sources).await; -} - -async fn handle_lazy_event_sources_of(sources: &LazyEventSources) { +pub async fn handle_lazy_event_sources_of(sources: &LazyEventSources) { loop { let source = sources.queue.pop().await; source.queued.set(false); diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 64b73864..d5937af8 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -12,10 +12,12 @@ pub mod compat; pub mod copyhashmap; pub mod double_buffered; pub mod errorfmt; +pub mod event_listener; pub mod fdcloser; pub mod free_list; pub mod geometric_decay; pub mod hash_map_ext; +pub mod linkedlist; pub mod log_on_drop; pub mod mmap; pub mod nice; diff --git a/utils/src/linkedlist.rs b/utils/src/linkedlist.rs new file mode 100644 index 00000000..e14b1f5c --- /dev/null +++ b/utils/src/linkedlist.rs @@ -0,0 +1,414 @@ +use { + crate::numcell::NumCell, + std::{ + cell::Cell, + fmt::{Debug, Formatter}, + mem, + ops::Deref, + ptr::NonNull, + }, +}; + +const LINKED_NODE_REF_COUNT: usize = !(!0 >> 1); + +pub struct LinkedList { + root: LinkedNode, +} + +impl Debug for LinkedList { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + +impl LinkedList { + pub fn new() -> Self { + Self { + root: LinkedNode::new(None), + } + } + + pub fn append_all(&self, other: &LinkedList) { + if other.is_empty() || self.root.data == other.root.data { + return; + } + unsafe { + let o_root = other.root.data; + let o_first = o_root.as_ref().next.get(); + let o_last = o_root.as_ref().prev.get(); + let s_first = self.root.data; + let s_last = s_first.as_ref().prev.get(); + o_first.as_ref().prev.set(s_last); + s_last.as_ref().next.set(o_first); + o_last.as_ref().next.set(s_first); + s_first.as_ref().prev.set(o_last); + o_root.as_ref().next.set(o_root); + o_root.as_ref().prev.set(o_root); + } + } + + fn endpoint(&self, ep: NonNull>) -> Option> { + unsafe { + if ep != self.root.data { + ep.as_ref().rc.fetch_add(1); + Some(NodeRef { data: ep }) + } else { + None + } + } + } + + pub fn is_empty(&self) -> bool { + self.last().is_none() + } + + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + + pub fn last(&self) -> Option> { + unsafe { self.endpoint(self.root.data.as_ref().prev.get()) } + } + + pub fn first(&self) -> Option> { + unsafe { self.endpoint(self.root.data.as_ref().next.get()) } + } + + pub fn add_last(&self, t: T) -> LinkedNode { + self.root.prepend(t) + } + + pub fn add_first(&self, t: T) -> LinkedNode { + self.root.append(t) + } + + pub fn add_last_existing(&self, t: &NodeRef) { + self.root.prepend_existing(t) + } + + pub fn add_first_existing(&self, t: &NodeRef) { + self.root.append_existing(t) + } + + pub fn rotate_last(&self, t: &NodeRef) { + unsafe { + let root = self.root.data.as_ref(); + root.prev.get().as_ref().next.set(root.next.get()); + root.next.get().as_ref().prev.set(root.prev.get()); + root.prev.set(t.data); + root.next.set(t.data.as_ref().next.get()); + t.data.as_ref().next.get().as_ref().prev.set(self.root.data); + t.data.as_ref().next.set(self.root.data); + } + } + + pub fn iter(&self) -> LinkedListIter { + unsafe { + let root = self.root.data.as_ref(); + root.rc.fetch_add(1); + root.next.get().as_ref().rc.fetch_add(1); + LinkedListIter { + root: self.root.data, + next: root.next.get(), + } + } + } + + pub fn rev_iter(&self) -> RevLinkedListIter { + unsafe { + let root = self.root.data.as_ref(); + root.rc.fetch_add(1); + root.prev.get().as_ref().rc.fetch_add(1); + RevLinkedListIter { + root: self.root.data, + next: root.prev.get(), + } + } + } +} + +pub struct LinkedListIter { + root: NonNull>, + next: NonNull>, +} + +impl Iterator for LinkedListIter { + type Item = NodeRef; + + fn next(&mut self) -> Option { + if self.root == self.next { + return None; + } + unsafe { + let old_next = self.next; + self.next = old_next.as_ref().next.get(); + self.next.as_ref().rc.fetch_add(1); + Some(NodeRef { data: old_next }) + } + } +} + +impl Drop for LinkedListIter { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.root, 1); + dec_ref_count(self.next, 1); + } + } +} + +pub struct RevLinkedListIter { + root: NonNull>, + next: NonNull>, +} + +impl Iterator for RevLinkedListIter { + type Item = NodeRef; + + fn next(&mut self) -> Option { + if self.root == self.next { + return None; + } + unsafe { + let old_next = self.next; + self.next = old_next.as_ref().prev.get(); + self.next.as_ref().rc.fetch_add(1); + Some(NodeRef { data: old_next }) + } + } +} + +impl Drop for RevLinkedListIter { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.root, 1); + dec_ref_count(self.next, 1); + } + } +} + +#[repr(transparent)] +#[must_use] +pub struct LinkedNode { + data: NonNull>, +} + +impl Debug for LinkedNode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked().fmt(f) } + } +} + +impl Deref for LinkedNode { + type Target = NodeRef; + + fn deref(&self) -> &Self::Target { + unsafe { mem::transmute(self) } + } +} + +#[repr(transparent)] +pub struct NodeRef { + data: NonNull>, +} + +impl Debug for NodeRef { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked().fmt(f) } + } +} + +impl Deref for NodeRef { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { self.data.as_ref().data.as_ref().unwrap_unchecked() } + } +} + +impl Drop for NodeRef { + fn drop(&mut self) { + unsafe { + dec_ref_count(self.data, 1); + } + } +} + +impl Clone for NodeRef { + fn clone(&self) -> Self { + unsafe { + self.data.as_ref().rc.fetch_add(1); + Self { data: self.data } + } + } +} + +impl NodeRef { + pub fn prepend(&self, t: T) -> LinkedNode { + unsafe { prepend(self.data, t) } + } + + pub fn append(&self, t: T) -> LinkedNode { + unsafe { append(self.data, t) } + } + + pub fn prepend_existing(&self, t: &NodeRef) { + unsafe { prepend_existing(self.data, t) } + } + + pub fn append_existing(&self, t: &NodeRef) { + unsafe { append_existing(self.data, t) } + } + + fn peer(&self, peer: F) -> Option> + where + F: FnOnce(&NodeData) -> &Cell>>, + { + unsafe { + let data = self.data.as_ref(); + let other = peer(data).get(); + if other.as_ref().data.is_some() { + other.as_ref().rc.fetch_add(1); + Some(NodeRef { data: other }) + } else { + None + } + } + } + + pub fn prev(&self) -> Option> { + self.peer(|d| &d.prev) + } + + pub fn next(&self) -> Option> { + self.peer(|d| &d.next) + } + + pub fn detach(&self) { + unsafe { + let data = self.data.as_ref(); + data.prev.get().as_ref().next.set(data.next.get()); + data.next.get().as_ref().prev.set(data.prev.get()); + data.prev.set(self.data); + data.next.set(self.data); + } + } +} + +struct NodeData { + rc: NumCell, + prev: Cell>>, + next: Cell>>, + data: Option, +} + +unsafe fn dec_ref_count(slf: NonNull>, n: usize) { + unsafe { + if slf.as_ref().rc.fetch_sub(n) == n { + drop(Box::from_raw(slf.as_ptr())); + } + } +} + +impl Drop for LinkedNode { + fn drop(&mut self) { + unsafe { + self.detach(); + dec_ref_count(self.data, LINKED_NODE_REF_COUNT); + } + } +} + +impl LinkedNode { + fn new(t: Option) -> Self { + let node = Box::leak(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(NonNull::dangling()), + next: Cell::new(NonNull::dangling()), + data: t, + })); + let ptr = NonNull::from(&mut *node); + node.prev.set(ptr); + node.next.set(ptr); + LinkedNode { data: node.into() } + } + + pub fn detached(t: T) -> Self { + Self::new(Some(t)) + } + + pub fn to_ref(&self) -> NodeRef { + unsafe { + self.data.as_ref().rc.fetch_add(1); + NodeRef { data: self.data } + } + } +} + +unsafe fn prepend_existing(data: NonNull>, t: &NodeRef) { + unsafe { + let dref = data.as_ref(); + let tref = t.data.as_ref(); + if tref.rc.get() < LINKED_NODE_REF_COUNT { + log::error!("Trying to prepend a node whose linked node has already been dropped"); + return; + } + t.detach(); + tref.prev.set(dref.prev.get()); + tref.next.set(data); + dref.prev.get().as_ref().next.set(t.data); + dref.prev.set(t.data); + } +} + +unsafe fn prepend(data: NonNull>, t: T) -> LinkedNode { + unsafe { + let dref = data.as_ref(); + let node = NonNull::new_unchecked(Box::into_raw(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(dref.prev.get()), + next: Cell::new(data), + data: Some(t), + }))); + dref.prev.get().as_ref().next.set(node); + dref.prev.set(node); + LinkedNode { data: node } + } +} + +unsafe fn append_existing(data: NonNull>, t: &NodeRef) { + unsafe { + let dref = data.as_ref(); + let tref = t.data.as_ref(); + if tref.rc.get() < LINKED_NODE_REF_COUNT { + log::error!("Trying to append a node whose linked node has already been dropped"); + return; + } + t.detach(); + tref.prev.set(data); + tref.next.set(dref.next.get()); + dref.next.get().as_ref().prev.set(t.data); + dref.next.set(t.data); + } +} + +unsafe fn append(data: NonNull>, t: T) -> LinkedNode { + unsafe { + let dref = data.as_ref(); + let node = NonNull::new_unchecked(Box::into_raw(Box::new(NodeData { + rc: NumCell::new(LINKED_NODE_REF_COUNT), + prev: Cell::new(data), + next: Cell::new(dref.next.get()), + data: Some(t), + }))); + dref.next.get().as_ref().prev.set(node); + dref.next.set(node); + LinkedNode { data: node } + } +}