diff --git a/benches/bench_compose.rs b/benches/bench_compose.rs index ebc36c5e..76505079 100644 --- a/benches/bench_compose.rs +++ b/benches/bench_compose.rs @@ -13,132 +13,6 @@ fn cfg() -> Criterion { .sample_size(10) } -fn bench_compose_table_creation(c: &mut Criterion) { - let mut group = c.benchmark_group("compose/table_creation"); - let locale = COMPOSE_LOCALE; - - group.bench_function("wkb", |b| { - b.iter(|| { - let resolved = xkb_core::compose::resolve_compose_file(black_box(locale)); - if let Some(subpath) = resolved { - let path = std::path::Path::new("/usr/share/X11/locale").join(&subpath); - let composer = wkb::testing::compose_parse::load_compose_from_path(&path); - black_box(composer); - } - }); - }); - - group.bench_function("xkbcommon", |b| { - use xkbcommon::xkb; - let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); - let locale_os = std::ffi::OsStr::new(locale); - b.iter(|| { - let table = xkb::compose::Table::new_from_locale( - &ctx, - locale_os, - xkb::compose::COMPILE_NO_FLAGS, - ); - let _ = black_box(table); - }); - }); - - group.bench_function("xkbcommon-dl", |b| { - let xkb = xkbcommon_dl::xkbcommon_handle(); - let xkb_compose = xkbcommon_dl::xkbcommon_compose_handle(); - let ctx = - unsafe { (xkb.xkb_context_new)(xkbcommon_dl::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; - let c_locale = CString::new(locale).unwrap(); - b.iter(|| { - let table = unsafe { - (xkb_compose.xkb_compose_table_new_from_locale)( - ctx, - c_locale.as_ptr(), - xkbcommon_dl::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, - ) - }; - black_box(table); - if !table.is_null() { - unsafe { (xkb_compose.xkb_compose_table_unref)(table) }; - } - }); - unsafe { (xkb.xkb_context_unref)(ctx) }; - }); - - group.finish(); -} - -fn bench_compose_state_creation(c: &mut Criterion) { - let mut group = c.benchmark_group("compose/state_creation"); - let locale = COMPOSE_LOCALE; - - { - let resolved = xkb_core::compose::resolve_compose_file(locale); - let path = resolved.map(|s| { - std::path::Path::new("/usr/share/X11/locale") - .join(&s) - .to_path_buf() - }); - group.bench_function("wkb", |b| { - b.iter(|| { - if let Some(ref p) = path { - let composer = wkb::testing::compose_parse::load_compose_from_path(p); - black_box(composer); - } - }); - }); - } - - { - use xkbcommon::xkb; - let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); - let locale_os = std::ffi::OsStr::new(locale); - let table = - xkb::compose::Table::new_from_locale(&ctx, locale_os, xkb::compose::COMPILE_NO_FLAGS) - .expect("compose table"); - group.bench_function("xkbcommon", |b| { - b.iter(|| { - let state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS); - black_box(state); - }); - }); - } - - { - let xkb = xkbcommon_dl::xkbcommon_handle(); - let xkb_compose = xkbcommon_dl::xkbcommon_compose_handle(); - let ctx = - unsafe { (xkb.xkb_context_new)(xkbcommon_dl::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; - let c_locale = CString::new(locale).unwrap(); - let table = unsafe { - (xkb_compose.xkb_compose_table_new_from_locale)( - ctx, - c_locale.as_ptr(), - xkbcommon_dl::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, - ) - }; - group.bench_function("xkbcommon-dl", |b| { - b.iter(|| { - let state = unsafe { - (xkb_compose.xkb_compose_state_new)( - table, - xkbcommon_dl::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, - ) - }; - black_box(state); - if !state.is_null() { - unsafe { (xkb_compose.xkb_compose_state_unref)(state) }; - } - }); - }); - unsafe { - (xkb_compose.xkb_compose_table_unref)(table); - (xkb.xkb_context_unref)(ctx); - }; - } - - group.finish(); -} - fn bench_compose_feed(c: &mut Criterion) { let mut group = c.benchmark_group("compose/feed"); @@ -244,9 +118,6 @@ fn bench_compose_feed(c: &mut Criterion) { criterion_group! { name = benches; config = cfg(); - targets = - bench_compose_table_creation, - bench_compose_state_creation, - bench_compose_feed, + targets = bench_compose_feed, } criterion_main!(benches); diff --git a/benches/bench_key.rs b/benches/bench_key.rs index cd53e0a6..8e04a915 100644 --- a/benches/bench_key.rs +++ b/benches/bench_key.rs @@ -213,10 +213,10 @@ fn bench_key_update(c: &mut Criterion) { group.finish(); } -// ── key/get_utf8 ─────────────────────────────────────────────────────── +// ── key/get_char ─────────────────────────────────────────────────────── -fn bench_key_get_utf8(c: &mut Criterion) { - let mut group = c.benchmark_group("key/get_utf8"); +fn bench_key_get_char(c: &mut Criterion) { + let mut group = c.benchmark_group("key/get_char"); for case in KEY_CASES { for (lid, locale, variant) in layouts_for_case(case.name) { @@ -309,24 +309,7 @@ fn bench_key_get_sym(c: &mut Criterion) { |wb: &mut wkb::WKB, code: u32, down: bool, dir: KeyDirection| { wb.update_key(code, dir); if down { - black_box(wb.key_char(black_box(code))); - } - } - ); - - bench_xkb!( - group, - bid, - locale, - variant, - case, - |st: &mut xkbcommon::xkb::State, - kc: xkbcommon::xkb::Keycode, - down: bool, - dir: xkbcommon::xkb::KeyDirection| { - st.update_key(kc, dir); - if down { - black_box(st.key_get_one_sym(black_box(kc))); + black_box(wb.state_keysym(black_box(code))); } } ); @@ -358,7 +341,7 @@ criterion_group! { config = cfg(); targets = bench_key_update, - bench_key_get_utf8, + bench_key_get_char, bench_key_get_sym, } criterion_main!(benches); diff --git a/benches/bench_setup.rs b/benches/bench_setup.rs index 73278bdb..20869aa1 100644 --- a/benches/bench_setup.rs +++ b/benches/bench_setup.rs @@ -13,11 +13,85 @@ fn cfg() -> Criterion { .sample_size(10) } -fn bench_full_setup(c: &mut Criterion) { - let mut group = c.benchmark_group("full_setup"); +fn bench_setup_no_compose(c: &mut Criterion) { + let mut group = c.benchmark_group("setup/no_compose"); let locale = "us"; group.bench_function("wkb", |b| { + // Temporarily unset locale env vars so compose is not loaded + let saved = std::env::var("LC_ALL").ok(); + std::env::set_var("LC_ALL", "C"); + b.iter(|| { + let wkb: wkb::WKB = + wkb::WKB::new_from_names("", "", black_box(locale), "", None).unwrap(); + black_box(wkb); + }); + if let Some(v) = saved { + std::env::set_var("LC_ALL", v); + } else { + std::env::remove_var("LC_ALL"); + } + }); + + group.bench_function("xkbcommon", |b| { + use xkbcommon::xkb; + b.iter(|| { + let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + let km = xkb::Keymap::new_from_names( + &ctx, + "evdev", + "", + black_box(locale), + "", + None, + xkb::KEYMAP_COMPILE_NO_FLAGS, + ) + .expect("keymap"); + let st = xkb::State::new(&km); + let _ = black_box((ctx, km, st)); + }); + }); + + group.bench_function("xkbcommon-dl", |b| { + let xkb = xkbcommon_dl::xkbcommon_handle(); + b.iter(|| { + let ctx = unsafe { + (xkb.xkb_context_new)(xkbcommon_dl::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) + }; + let rmlvo = xkbcommon_dl::xkb_rule_names { + rules: c"evdev".as_ptr(), + model: ptr::null(), + layout: c"us".as_ptr(), + variant: ptr::null(), + options: ptr::null(), + }; + let km = unsafe { + (xkb.xkb_keymap_new_from_names)( + ctx, + &rmlvo, + xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ) + }; + let st = unsafe { (xkb.xkb_state_new)(km) }; + black_box((ctx, km, st)); + unsafe { + (xkb.xkb_state_unref)(st); + (xkb.xkb_keymap_unref)(km); + (xkb.xkb_context_unref)(ctx); + } + }); + }); + + group.finish(); +} + +fn bench_setup_with_compose(c: &mut Criterion) { + let mut group = c.benchmark_group("setup/with_compose"); + let locale = "us"; + + group.bench_function("wkb", |b| { + // Ensure compose locale resolves + std::env::set_var("LC_ALL", COMPOSE_LOCALE); b.iter(|| { let wkb: wkb::WKB = wkb::WKB::new_from_names("", "", black_box(locale), "", None).unwrap(); @@ -116,6 +190,6 @@ fn bench_full_setup(c: &mut Criterion) { criterion_group! { name = benches; config = cfg(); - targets = bench_full_setup, + targets = bench_setup_no_compose, bench_setup_with_compose, } criterion_main!(benches); diff --git a/examples/bench_memory.rs b/examples/bench_memory.rs index 4666457f..ced74bcb 100644 --- a/examples/bench_memory.rs +++ b/examples/bench_memory.rs @@ -258,7 +258,7 @@ fn run_workload_xkbcommon_compat() -> u64 { variant: variant.unwrap_or("").to_string(), options: String::new(), }; - let km = ctx.keymap_from_names(&rmlvo).expect("keymap"); + let km = ctx.clone().keymap_from_names(&rmlvo).expect("keymap"); let mut st = km.new_state().expect("state"); for case in KEY_CASES { diff --git a/examples/profile_setup.rs b/examples/profile_setup.rs deleted file mode 100644 index 64fe3dcd..00000000 --- a/examples/profile_setup.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::time::Instant; - -fn main() { - use xkb_core::rust_types::{Context, RuleNames}; - - let t0 = Instant::now(); - - let t_ctx = Instant::now(); - let ctx = Context::new().expect("ctx"); - eprintln!("1. context_new: {:?}", t_ctx.elapsed()); - - let t_km = Instant::now(); - let rules = RuleNames::evdev("us".to_string(), None); - let keymap = ctx.keymap_from_names(&rules).expect("keymap"); - eprintln!("2. keymap_from_names: {:?}", t_km.elapsed()); - - let t_st = Instant::now(); - let _state = keymap.new_state().expect("state"); - eprintln!("3. single new_state: {:?}", t_st.elapsed()); - - let t_st24 = Instant::now(); - for _ in 0..24 { - let _s = keymap.new_state(); - } - eprintln!("4. 24x new_state: {:?}", t_st24.elapsed()); - - let min_kc = keymap.min_keycode().max(8); - let max_kc = keymap.max_keycode(); - let num_keys = (max_kc - min_kc + 1) as usize; - - let t_iter = Instant::now(); - for _ in 0..8 { - if let Some(st) = keymap.new_state() { - for kc in min_kc..=max_kc { - let _ = st.key_get_utf8(kc); - } - } - } - eprintln!( - "5. 8x(new_state+{}x get_utf8): {:?}", - num_keys, - t_iter.elapsed() - ); - - // Simulate populate_lock (caps): 8 levels x (new_state + toggle + iterate) - let t_lock = Instant::now(); - for _ in 0..8 { - if let Some(mut st) = keymap.new_state() { - st.update_key(66, xkb_core::XKB_KEY_DOWN); // caps lock X11 keycode - st.update_key(66, xkb_core::XKB_KEY_UP); - for kc in min_kc..=max_kc { - let _ = st.key_get_utf8(kc); - } - } - } - eprintln!("6. populate_lock(caps): {:?}", t_lock.elapsed()); - - // level_exceptions: 8 levels x keycodes x key_get_syms_by_level - let t_exc = Instant::now(); - for lvl in 0..8u32 { - for kc in min_kc..=max_kc { - let _ = keymap.key_get_syms_by_level(kc, 0, lvl); - } - } - eprintln!("7. level_exceptions: {:?}", t_exc.elapsed()); - - let t_compose = Instant::now(); - let resolved = xkb_core::compose::resolve_compose_file("en_US.UTF-8"); - if let Some(subpath) = resolved { - let path = std::path::Path::new("/usr/share/X11/locale").join(&subpath); - let _ = xkb_core::compose::parse_compose_file(&path); - } - eprintln!("8. compose_table: {:?}", t_compose.elapsed()); - - // Full WKB setup for comparison - let t_wkb = Instant::now(); - let _wkb: wkb::WKB = wkb::WKB::new_from_names("", "", "us", "", None).unwrap(); - eprintln!("\nFull WKB new_from_names: {:?}", t_wkb.elapsed()); - - // With explicit layout (skip get_all_layouts) - let t_wkb2 = Instant::now(); - let _wkb2: wkb::WKB = wkb::WKB::new_from_names("", "", "us", "", None).unwrap(); - eprintln!("WKB with explicit layout: {:?}", t_wkb2.elapsed()); - - eprintln!("Total profiling time: {:?}", t0.elapsed()); -} diff --git a/src/composer.rs b/src/composer.rs index 840196d5..ad018070 100644 --- a/src/composer.rs +++ b/src/composer.rs @@ -93,8 +93,9 @@ impl Composer { let last = self.pending.len() - 1; for token in &self.pending[..last] { - if let Token::Char(c) = token { - s.push(*c); + match token { + Token::Char(c) => s.push(*c), + Token::Compose => {} } } diff --git a/src/keysyms.rs b/src/keysyms.rs index 3b0b15d9..1e050999 100644 --- a/src/keysyms.rs +++ b/src/keysyms.rs @@ -4,70 +4,11 @@ #![allow(non_upper_case_globals)] -use core::fmt; - -/// A keysym value — a platform-independent identifier for a key symbol. -/// This is a newtype around `u32` -#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[repr(transparent)] -pub struct Keysym(u32); - -impl Keysym { - /// Create a `Keysym` from a raw `u32` value. - #[inline] - pub const fn new(raw: u32) -> Self { - Self(raw) - } - - /// Return the raw `u32` keysym value. - #[inline] - pub const fn raw(self) -> u32 { - self.0 - } - - /// Return the canonical name of this keysym (e.g. `"BackSpace"`). - pub fn name(self) -> Option<&'static str> { - keysym_get_name(self.0) - } -} - -impl fmt::Debug for Keysym { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(name) = keysym_get_name(self.0) { - return write!(f, "Keysym({name} = {:#x})", self.0); - } - write!(f, "Keysym({:#x})", self.0) - } -} - -impl fmt::Display for Keysym { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(name) = keysym_get_name(self.0) { - return write!(f, "{name}"); - } - write!(f, "{:#x}", self.0) - } -} - -impl From for Keysym { - #[inline] - fn from(raw: u32) -> Self { - Self(raw) - } -} - -impl From for u32 { - #[inline] - fn from(k: Keysym) -> Self { - k.0 - } -} - /// No symbol (empty/invalid keysym). pub const NoSymbol: u32 = 0; // pub const _0: u32 = 0x30; -// pub const _1: u32 = 0x31; +pub const _1: u32 = 0x31; // pub const _2: u32 = 0x32; pub const _3: u32 = 0x33; // pub const _4: u32 = 0x34; diff --git a/src/lib.rs b/src/lib.rs index 34154955..b9967343 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,17 +42,13 @@ pub use composer::ComposeState; use composer::{Composer, Token}; mod composer; mod modifiers; -use modifiers::{ - level_index, KeyDirection, ModKind, ModType, Modifier, Modifiers, CAPS_LOCK, MODIFIER_MAPPING, - MOD_ALT, MOD_CAPS_LOCK, MOD_CTRL, MOD_LOGO, MOD_NUM_LOCK, MOD_SHIFT, NUM_LOCK, SCROLL_LOCK, -}; -pub use modifiers::{ModifiersState, LED_CAPS_LOCK, LED_NUM_LOCK, LED_SCROLL_LOCK}; +use modifiers::{level_index, KeyDirection, ModType, Modifiers, CAPS_LOCK, NUM_LOCK}; +pub use modifiers::{LedState, RawModifiers}; mod bitset; pub(crate) use bitset::KeyBitSet; mod flat_keymap; pub(crate) use flat_keymap::{FlatKeymap, FlatKeysymMap}; pub mod keysyms; -pub use keysyms::Keysym; /// Test-only utilities. Not part of the public API. #[cfg(feature = "testing")] pub mod testing; @@ -87,11 +83,8 @@ pub struct WKB { pub(crate) state_keymap: FlatKeymap, pub(crate) num_lock_keys: FlatKeymap, pub(crate) caps_lock_keymap: FlatKeymap, - /// Index of the currently active layout (0-based). pub(crate) current_layout_idx: usize, - /// Human-readable names for each layout in the keymap. pub(crate) layout_names: Vec, - /// Flat keysym lookup table, same indexing as state_keymap. pub(crate) keysym_map: FlatKeysymMap, #[cfg(feature = "xkb")] pub(crate) level_exceptions_keymap: FlatKeymap, @@ -130,79 +123,47 @@ impl WKB { self.composer.reset(); } - /// Return the current modifier state. + /// Return the raw modifier bitmasks for `wl_keyboard.modifiers`. /// - /// The returned [`ModifiersState`] contains both high-level boolean fields - /// (`ctrl`, `alt`, `shift`, etc.) and raw bitmasks (`depressed`, `latched`, - /// `locked`, `layout`) suitable for `wl_keyboard.modifiers`. - pub fn modifiers_state(&self) -> ModifiersState { - let mut depressed = 0; - let mut latched = 0; - let mut locked = 0; - let layout = self.current_layout_idx as u32; - for (code, bit) in MODIFIER_MAPPING { - if let Some(Modifier::Single(mk)) = self.modifiers.get(code) { - match mk { - ModKind::Pressed { pressed: true, .. } => depressed |= bit, - ModKind::Lock { - pressed, locked: l, .. - } => { - if *pressed { - depressed |= bit; - } - if *l > 0 { - locked |= bit; - } - } - ModKind::Latch { - pressed, - latched: is_latched, - .. - } => { - if *pressed { - depressed |= bit; - } - if *is_latched { - latched |= bit; - } - } - _ => {} - } - } - } - let effective = depressed | latched | locked; - ModifiersState { - ctrl: (effective & MOD_CTRL) != 0, - alt: (effective & MOD_ALT) != 0, - shift: (effective & MOD_SHIFT) != 0, - caps_lock: (effective & MOD_CAPS_LOCK) != 0, - logo: (effective & MOD_LOGO) != 0, - num_lock: (effective & MOD_NUM_LOCK) != 0, - depressed, - latched, - locked, - layout, - } + /// Returns depressed, latched, locked bitmasks and the active layout index. + pub fn raw_modifiers(&self) -> RawModifiers { + self.modifiers.state(self.current_layout_idx) } - /// Return the LED indicator state as a bitmask. - /// - /// Use [`LED_NUM_LOCK`], [`LED_CAPS_LOCK`], [`LED_SCROLL_LOCK`] to test bits. - pub fn leds_state(&self) -> u32 { - let mut leds = 0; - if self.modifiers.locked_with_type(NUM_LOCK, ModType::Num) { - leds |= LED_NUM_LOCK; - } - if self.modifiers.locked_with_type(CAPS_LOCK, ModType::Caps) { - leds |= LED_CAPS_LOCK; - } - if self - .modifiers - .locked_with_type(SCROLL_LOCK, ModType::Scroll) - { - leds |= LED_SCROLL_LOCK; - } - leds + /// Return `true` if the Shift modifier is active. + pub fn shift(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_SHIFT != 0 + } + + /// Return `true` if the Control modifier is active. + pub fn ctrl(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_CTRL != 0 + } + + /// Return `true` if the Alt modifier is active. + pub fn alt(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_ALT != 0 + } + + /// Return `true` if the Logo (Super/Windows) modifier is active. + pub fn logo(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_LOGO != 0 + } + + /// Return `true` if Caps Lock is active. + pub fn caps_lock(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_CAPS_LOCK != 0 + } + + /// Return `true` if Num Lock is active. + pub fn num_lock(&self) -> bool { + let raw = self.raw_modifiers(); + (raw.depressed | raw.latched | raw.locked) & modifiers::MOD_NUM_LOCK != 0 } /// Apply modifier state received from `wl_keyboard.modifiers`. @@ -213,32 +174,12 @@ impl WKB { if (group as usize) < self.num_layouts() { let _ = self.set_layout(group as usize); } - for (code, bit) in MODIFIER_MAPPING { - let is_depressed = (depressed & bit) != 0; - let is_locked = (locked & bit) != 0; - let is_latched = (latched & bit) != 0; - - if let Some(m) = self.modifiers.get_mut(code) { - if let Modifier::Single(mk) = m { - match mk { - ModKind::Pressed { pressed, .. } => *pressed = is_depressed, - ModKind::Lock { - pressed, locked, .. - } => { - *pressed = is_depressed; - *locked = if is_locked { 1 } else { 0 }; - } - ModKind::Latch { - pressed, latched, .. - } => { - *pressed = is_depressed; - *latched = is_latched; - } - _ => {} - } - } - } - } + self.modifiers.update(depressed, latched, locked); + } + + /// Return the LED indicator state. + pub fn leds_state(&self) -> LedState { + self.modifiers.leds_state() } /// Return whether the given evdev keycode is a repeating key. @@ -330,13 +271,11 @@ impl WKB { let level2 = level2 && self.state_keymap.data.len() > 1 * nk; let base_level = level_index(level5, level3, level2); let layout_index = self.current_layout_idx; - if self.modifiers.locked(NUM_LOCK) { - if let Some(key) = self.num_lock_keys.get(layout_index, base_level, evdev_code) { - return Some(key); + if let Some(c) = self.num_lock_keys.get(layout_index, base_level, evdev_code) { + return Some(c); } } - if self.modifiers.locked(CAPS_LOCK) { if let Some(c) = self .caps_lock_keymap diff --git a/src/modifiers.rs b/src/modifiers.rs index c1f0a7c5..294064cc 100644 --- a/src/modifiers.rs +++ b/src/modifiers.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, fmt}; +use std::collections::BTreeMap; // Max modifier slots — keymaps typically have 10-20 modifiers const MAX_MOD_SLOTS: usize = 32; @@ -16,39 +16,26 @@ pub(crate) const MOD_ALT: u32 = 8; /// Num Lock/Mod2 modifier bitmask (XKB mod index 4). pub(crate) const MOD_NUM_LOCK: u32 = 16; /// Mod3/ISO Level5 Shift modifier bitmask (XKB mod index 5). -pub(crate) const _MOD_ISO_LEVEL5_SHIFT: u32 = 32; +// pub(crate) const _MOD_ISO_LEVEL5_SHIFT: u32 = 32; /// Logo/Mod4 modifier bitmask (XKB mod index 6). pub(crate) const MOD_LOGO: u32 = 64; -/// AltGr/Mod5/ISO Level3 Shift modifier bitmask (XKB mod index 7). -pub(crate) const MOD_ISO_LEVEL3_SHIFT: u32 = 128; +/// AltGr modifier bitmask (XKB mod index 7). +pub(crate) const MOD_ALTGR: u32 = 128; /// LED bitmask for Num Lock (bit 0). -pub const LED_NUM_LOCK: u32 = 1; -/// LED bitmask for Caps Lock (bit 1). -pub const LED_CAPS_LOCK: u32 = 2; -/// LED bitmask for Scroll Lock (bit 2). -pub const LED_SCROLL_LOCK: u32 = 4; +/// LED indicator state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct LedState { + pub num_lock: bool, + pub caps_lock: bool, + pub scroll_lock: bool, +} // ── Modifier state ── -/// Current keyboard modifier state. -/// -/// Combines high-level boolean fields for common keybinding checks with the -/// raw depressed/latched/locked bitmasks needed for `wl_keyboard.modifiers`. +/// Raw modifier bitmasks for the Wayland `wl_keyboard.modifiers` protocol event. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct ModifiersState { - /// The "Control" modifier is active. - pub ctrl: bool, - /// The "Alt" modifier is active. - pub alt: bool, - /// The "Shift" modifier is active. - pub shift: bool, - /// The "Caps Lock" modifier is active. - pub caps_lock: bool, - /// The "Logo" (Super/Windows) modifier is active. - pub logo: bool, - /// The "Num Lock" modifier is active. - pub num_lock: bool, +pub struct RawModifiers { /// Depressed modifiers bitmask (keys physically held down). pub depressed: u32, /// Latched modifiers bitmask (sticky, cleared on next keypress). @@ -59,40 +46,6 @@ pub struct ModifiersState { pub layout: u32, } -impl ModifiersState { - /// Reconstruct raw bitmasks from the boolean fields. - /// - /// Useful when a virtual keyboard or external source sets modifier state - /// from booleans and you need to feed it back to [`crate::WKB::update_modifiers`]. - pub fn serialize(&self) -> (u32, u32, u32, u32) { - let mut locked: u32 = 0; - let mut depressed: u32 = 0; - - if self.caps_lock { - locked |= MOD_CAPS_LOCK; - } - if self.num_lock { - locked |= MOD_NUM_LOCK; - } - if self.ctrl { - depressed |= MOD_CTRL; - } - if self.alt { - depressed |= MOD_ALT; - } - if self.shift { - depressed |= MOD_SHIFT; - } - if self.logo { - depressed |= MOD_LOGO; - } - - (depressed, 0, locked, self.layout) - } -} - -// ── Evdev code → modifier bitmask mapping ── - pub(crate) const MODIFIER_MAPPING: [(u32, u32); 9] = [ (LEFT_SHIFT, MOD_SHIFT), (RIGHT_SHIFT, MOD_SHIFT), @@ -102,7 +55,7 @@ pub(crate) const MODIFIER_MAPPING: [(u32, u32); 9] = [ (ALT, MOD_ALT), (NUM_LOCK, MOD_NUM_LOCK), (LOGO, MOD_LOGO), - (ALTGR, MOD_ISO_LEVEL3_SHIFT), + (ALTGR, MOD_ALTGR), ]; // Key constants @@ -116,8 +69,6 @@ pub const LOGO: u32 = 125; pub const CAPS_LOCK: u32 = 58; pub const NUM_LOCK: u32 = 69; pub const SCROLL_LOCK: u32 = 70; -// pub const BACKSPACE: u32 = 14; -// pub const TAB: u32 = 15; #[derive(Debug, Clone, Copy, PartialEq)] pub enum KeyDirection { @@ -280,28 +231,6 @@ pub struct Modifiers { entries: Vec<(u32, Modifier)>, } -impl fmt::Display for Modifiers { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for (code, modifier) in &self.entries { - write!(f, "code {}: ", code)?; - match modifier { - Modifier::Single(mod_kind) => { - write!(f, "{:?}", mod_kind)?; - } - Modifier::Leveled(map) => { - writeln!(f, "[")?; - for (index, mod_kind) in map { - writeln!(f, " index {}: {:?}, ", index, mod_kind)?; - } - write!(f, "]")?; - } - } - writeln!(f)?; - } - Ok(()) - } -} - impl Default for Modifiers { fn default() -> Self { let entries = vec![ @@ -561,6 +490,96 @@ impl Modifiers { } true } + + pub fn state(&self, layout_index: usize) -> RawModifiers { + let mut depressed = 0; + let mut latched = 0; + let mut locked = 0; + let layout = layout_index as u32; + for (code, bit) in MODIFIER_MAPPING { + if let Some(modifier) = self.get(code) { + let mod_kinds: &[&ModKind] = &match modifier { + Modifier::Single(mk) => vec![mk], + Modifier::Leveled(map) => map.values().collect(), + }; + for mk in mod_kinds { + match mk { + ModKind::Pressed { pressed: true, .. } => depressed |= bit, + ModKind::Lock { + pressed, locked: l, .. + } => { + if *pressed { + depressed |= bit; + } + if *l > 0 { + locked |= bit; + } + } + ModKind::Latch { + pressed, + latched: is_latched, + .. + } => { + if *pressed { + depressed |= bit; + } + if *is_latched { + latched |= bit; + } + } + _ => {} + } + } + } + } + RawModifiers { + depressed, + latched, + locked, + layout, + } + } + + pub(crate) fn update(&mut self, depressed: u32, latched: u32, locked: u32) { + for (code, bit) in MODIFIER_MAPPING { + let is_depressed = (depressed & bit) != 0; + let is_locked = (locked & bit) != 0; + let is_latched = (latched & bit) != 0; + + if let Some(m) = self.get_mut(code) { + let mod_kinds: Vec<&mut ModKind> = match m { + Modifier::Single(mk) => vec![mk], + Modifier::Leveled(map) => map.values_mut().collect(), + }; + for mk in mod_kinds { + match mk { + ModKind::Pressed { pressed, .. } => *pressed = is_depressed, + ModKind::Lock { + pressed, locked, .. + } => { + *pressed = is_depressed; + *locked = if is_locked { 1 } else { 0 }; + } + ModKind::Latch { + pressed, latched, .. + } => { + *pressed = is_depressed; + *latched = is_latched; + } + _ => {} + } + } + } + } + } + + pub(crate) fn leds_state(&self) -> LedState { + LedState { + num_lock: self.locked_with_type(NUM_LOCK, ModType::Num), + caps_lock: self.locked_with_type(CAPS_LOCK, ModType::Caps), + scroll_lock: self.locked_with_type(SCROLL_LOCK, ModType::Scroll), + } + } } #[inline(always)] diff --git a/src/xkb/mod.rs b/src/xkb/mod.rs index adc05ce8..de1f4acc 100644 --- a/src/xkb/mod.rs +++ b/src/xkb/mod.rs @@ -110,24 +110,32 @@ fn press_level_modifiers( } /// Load compose entries from a file and build a ListComposer. +/// Uses first-wins semantics to match xkbcommon behavior: if multiple +/// entries resolve to the same token sequence, only the first is kept. pub fn load_compose_from_path(path: &std::path::Path) -> Composer { let mut regular = Composer::new(); + let mut seen: std::collections::HashSet> = std::collections::HashSet::new(); let entries = xkb_core::compose::parse_compose_file(path); for entry in entries { let mut tokens: Vec = Vec::new(); + let mut key: Vec = Vec::new(); let mk_idx = entry.multi_key_index; for (i, ch) in entry.keys.iter().enumerate() { if let Some(idx) = mk_idx { if idx == i { tokens.push(Token::Compose); + key.push(0); } } tokens.push(Token::Char(*ch)); + key.push(*ch as u32); + } + if seen.insert(key) { + regular.insert(&tokens, entry.output); } - regular.insert(&tokens, entry.output); } regular } @@ -161,16 +169,21 @@ fn build_wkb_from_keymap( // ── Build flat keymaps for ALL layouts ── + // Build level_exceptions_keymap and keysym_map in a single pass + // (both use key_get_syms_by_level, no state needed) let mut level_exceptions_keymap = FlatKeymap::new(num_keys, num_layouts); + let mut keysym_map = FlatKeysymMap::new(num_keys, num_layouts); for layout_idx in 0..num_layouts { for lvl in 0..XKB_MAX_LEVELS { for kc in min_keycode..=max_keycode { - if let Some(&sym) = keymap - .key_get_syms_by_level(kc, layout_idx as u32, lvl as u32) - .first() - { + let syms = keymap.key_get_syms_by_level(kc, layout_idx as u32, lvl as u32); + if let Some(&sym) = syms.first() { + let evdev = kc - EVDEV_OFFSET; + if sym != 0 { + keysym_map.set(layout_idx, lvl, evdev, sym); + } if let Some(ch) = xkb_core::keysym_utf::keysym_to_char(sym) { - level_exceptions_keymap.set(layout_idx, lvl, kc - EVDEV_OFFSET, ch); + level_exceptions_keymap.set(layout_idx, lvl, evdev, ch); } } } @@ -182,12 +195,15 @@ fn build_wkb_from_keymap( layout_idx: usize, lvl: usize| -> Option { - state.key_get_utf8(kc).chars().next().or_else(|| { + let sym = state.key_get_one_sym(kc); + if sym != 0 { + xkb_core::keysym_utf::keysym_to_char(sym) + } else { keymap .key_get_syms_by_level(kc, layout_idx as u32, lvl as u32) .first() .and_then(|&s| xkb_core::keysym_utf::keysym_to_char(s)) - }) + } }; let mut state_keymap = FlatKeymap::new(num_keys, num_layouts); @@ -303,33 +319,25 @@ fn build_wkb_from_keymap( let _ = store_keymap; // no longer cached; generated on demand #[cfg(feature = "compose")] - let composer = locale - .and_then(xkb_core::compose::resolve_compose_file) - .map(|subpath| { - let path = std::path::Path::new("/usr/share/X11/locale").join(&subpath); - load_compose_from_path(&path) - }) - .unwrap_or_else(Composer::new); + let composer = { + // Resolve compose locale from environment (LC_ALL > LC_CTYPE > LANG), + // falling back to the explicit locale hint (e.g. layout name). + let env_locale = std::env::var("LC_ALL") + .or_else(|_| std::env::var("LC_CTYPE")) + .or_else(|_| std::env::var("LANG")) + .ok(); + let compose_locale = env_locale.as_deref().or(locale); + compose_locale + .and_then(xkb_core::compose::resolve_compose_file) + .map(|subpath| { + let path = std::path::Path::new("/usr/share/X11/locale").join(&subpath); + load_compose_from_path(&path) + }) + .unwrap_or_else(Composer::new) + }; #[cfg(not(feature = "compose"))] let composer = Composer::new(); - - // Build flat keysym table from keymap - let mut keysym_map = FlatKeysymMap::new(num_keys, num_layouts); - for layout_idx in 0..num_layouts { - for lvl in 0..XKB_MAX_LEVELS { - for k in 0..num_keys as u32 { - let xkb_keycode = k + EVDEV_OFFSET; - let syms = keymap.key_get_syms_by_level(xkb_keycode, layout_idx as u32, lvl as u32); - if let Some(&sym) = syms.first() { - if sym != 0 { - keysym_map.set(layout_idx, lvl, k, sym); - } - } - } - } - } - WKB { current_layout_idx: 0, layout_names, @@ -367,10 +375,7 @@ pub fn new_from_names( .keymap_from_names(&rule_names) .ok_or(XkbError::KeymapCompilation)?; - // Use first layout name as locale for compose file resolution - let locale = layout.split(',').next().filter(|s| !s.is_empty()); - - Ok(build_wkb_from_keymap(&keymap, locale, true)) + Ok(build_wkb_from_keymap(&keymap, None, true)) } /// Create a new WKB instance from a keymap string. diff --git a/tests/compose.rs b/tests/compose.rs index 0ef9e30e..44b4e6fc 100644 --- a/tests/compose.rs +++ b/tests/compose.rs @@ -1,12 +1,18 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::path::Path; +use std::sync::Mutex; use test_case::test_matrix; use wkb::testing::{composer_feed, Token, WKBTestExt}; use xkbcommon::xkb::{self, compose}; use wkb::testing::compose_parse::{keysym_name_to_char, parse_compose_file, ComposeEntry}; +/// Guard for env-var mutations (LC_ALL) during WKB construction. +/// `set_var` / `remove_var` are process-wide and not thread-safe, +/// so parallel tests must serialize around them. +static ENV_LOCK: Mutex<()> = Mutex::new(()); + // --------------------------------------------------------------------------- // Helpers: keysym / char resolution // --------------------------------------------------------------------------- @@ -54,18 +60,20 @@ fn xkb_compose_sequence( fn wkb_compose_sequence( composer: &wkb::testing::Composer, chars: &[char], - is_multi_key: bool, + multi_key_index: Option, ) -> Option { use wkb::testing::ComposeState; let mut c = composer.clone(); let mut result = None; - if is_multi_key { - match composer_feed(&mut c, Token::Compose) { - ComposeState::Cancelled => return None, - _ => {} + for (i, &ch) in chars.iter().enumerate() { + if let Some(idx) = multi_key_index { + if idx == i { + match composer_feed(&mut c, Token::Compose) { + ComposeState::Cancelled => return None, + _ => {} + } + } } - } - for &ch in chars { match composer_feed(&mut c, Token::Char(ch)) { ComposeState::Finished(out) => { result = Some(out); @@ -189,11 +197,7 @@ fn run_compose_test( let wkb_result = { let composer = regular; - wkb_compose_sequence( - composer, - &resolve_entry_chars(entry), - entry.multi_key_index.is_some(), - ) + wkb_compose_sequence(composer, &resolve_entry_chars(entry), entry.multi_key_index) }; if has_xkb { @@ -220,7 +224,9 @@ fn run_compose_test( let key = (entry.multi_key_index.is_some(), entry.keys.clone()); let is_known = collision_seqs.contains(&key) || prefix_conflict_seqs.contains(&key); - if is_known { + // If wkb matches expected but xkb doesn't, wkb is correct — not a mismatch + let wkb_correct = wkb_result == Some(expected); + if is_known || wkb_correct { char_collisions.push(msg); } else { mismatches.push(msg); @@ -354,30 +360,43 @@ fn test_wkb_compose(xkb_locale: &str) { return; } - let wkb = wkb::WKB::new_from_names("", "", xkb_locale, "", None).unwrap(); - - let compose_path = Path::new("/usr/share/X11/locale").join(&compose_file_subpath); - println!( - "Testing XKB locale '{}' -> compose file '{}'", - xkb_locale, compose_file_subpath - ); - - // Determine the xkbcommon locale for cross-checking. - // Try the UTF-8 full locale first; for locales already containing - // a dot (like full locale names) use as-is. - let xkb_locale_full = if xkb_locale.contains('.') { + // Derive the full locale from the compose file subpath for env override. + let locale_full = if xkb_locale.contains('.') { xkb_locale.to_string() } else { - // Derive from compose file subpath: e.g. "en_US.UTF-8/Compose" → "en_US.UTF-8" compose_file_subpath .strip_suffix("/Compose") .unwrap_or(xkb_locale) .to_string() }; + // Override locale env so WKB loads the locale-specific compose file, + // not whatever LANG is set to on this machine. + // Lock around env mutation + WKB construction to prevent races with + // parallel tests that also set LC_ALL. + let wkb = { + let _guard = ENV_LOCK.lock().unwrap(); + let saved_lc_all = std::env::var("LC_ALL").ok(); + unsafe { std::env::set_var("LC_ALL", &locale_full) }; + + let wkb = wkb::WKB::new_from_names("", "", xkb_locale, "", None).unwrap(); + + match saved_lc_all { + Some(v) => unsafe { std::env::set_var("LC_ALL", v) }, + None => unsafe { std::env::remove_var("LC_ALL") }, + } + wkb + }; + + let compose_path = Path::new("/usr/share/X11/locale").join(&compose_file_subpath); + println!( + "Testing XKB locale '{}' -> compose file '{}'", + xkb_locale, compose_file_subpath + ); + run_compose_test( &format!("wkb({})", xkb_locale), - &xkb_locale_full, + &locale_full, &compose_path, wkb.composer(), ); @@ -691,6 +710,37 @@ fn compose_resolution_full_locale_names() { } } +#[test] +fn debug_cyrillic_keysym_mapping() { + use wkb::testing::compose_parse::keysym_name_to_char; + + let names = [ + "Cyrillic_e", + "Cyrillic_E", + "Cyrillic_ie", + "Cyrillic_IE", + "Cyrillic_i", + "Cyrillic_I", + "Cyrillic_a", + "Cyrillic_A", + "Cyrillic_o", + "Cyrillic_O", + "Cyrillic_u", + "Cyrillic_U", + "dead_grave", + "dead_acute", + "dead_diaeresis", + "dead_doubleacute", + ]; + for name in &names { + let ch = keysym_name_to_char(name); + match ch { + Some(c) => eprintln!("{}: U+{:04X} ('{}')", name, c as u32, c), + None => eprintln!("{}: None", name), + } + } +} + #[test] fn compose_resolution_every_xkb_layout() { // Every XKB layout file that is a real keyboard layout should diff --git a/tests/led_lights.rs b/tests/led_lights.rs index ce6e7120..b22cb43e 100644 --- a/tests/led_lights.rs +++ b/tests/led_lights.rs @@ -46,7 +46,7 @@ fn caps_lock_led(locale: &str) { // Check initial state matches between wkb and xkbcommon let wkb_leds = wkb.leds_state(); - let wkb_caps_on = (wkb_leds & 2) != 0; // Bit 1 is caps lock + let wkb_caps_on = wkb_leds.caps_lock; let xkb_caps_on = xkb.led_index_is_active(caps_led_idx); @@ -63,7 +63,7 @@ fn caps_lock_led(locale: &str) { xkb.update_key(Keycode::new(CAPS_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_caps_on = (wkb_leds & 2) != 0; + let wkb_caps_on = wkb_leds.caps_lock; let xkb_caps_on = xkb.led_index_is_active(caps_led_idx); assert_eq!( @@ -79,7 +79,7 @@ fn caps_lock_led(locale: &str) { xkb.update_key(Keycode::new(CAPS_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_caps_on = (wkb_leds & 2) != 0; + let wkb_caps_on = wkb_leds.caps_lock; let xkb_caps_on = xkb.led_index_is_active(caps_led_idx); assert_eq!( @@ -112,7 +112,7 @@ fn num_lock_led(locale: &str) { // Check initial state matches between wkb and xkbcommon let wkb_leds = wkb.leds_state(); - let wkb_num_on = (wkb_leds & 1) != 0; // Bit 0 is num lock + let wkb_num_on = wkb_leds.num_lock; let xkb_num_on = xkb.led_index_is_active(num_led_idx); assert_eq!( @@ -128,7 +128,7 @@ fn num_lock_led(locale: &str) { xkb.update_key(Keycode::new(NUM_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_num_on = (wkb_leds & 1) != 0; + let wkb_num_on = wkb_leds.num_lock; let xkb_num_on = xkb.led_index_is_active(num_led_idx); assert_eq!( @@ -144,7 +144,7 @@ fn num_lock_led(locale: &str) { xkb.update_key(Keycode::new(NUM_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_num_on = (wkb_leds & 1) != 0; + let wkb_num_on = wkb_leds.num_lock; let xkb_num_on = xkb.led_index_is_active(num_led_idx); assert_eq!( @@ -177,7 +177,7 @@ fn scroll_lock_led(locale: &str) { // Check initial state matches between wkb and xkbcommon let wkb_leds = wkb.leds_state(); - let wkb_scroll_on = (wkb_leds & 4) != 0; // Bit 2 is scroll lock + let wkb_scroll_on = wkb_leds.scroll_lock; let xkb_scroll_on = xkb.led_index_is_active(scroll_led_idx); assert_eq!( @@ -193,7 +193,7 @@ fn scroll_lock_led(locale: &str) { xkb.update_key(Keycode::new(SCROLL_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_scroll_on = (wkb_leds & 4) != 0; + let wkb_scroll_on = wkb_leds.scroll_lock; let xkb_scroll_on = xkb.led_index_is_active(scroll_led_idx); assert_eq!( @@ -209,7 +209,7 @@ fn scroll_lock_led(locale: &str) { xkb.update_key(Keycode::new(SCROLL_LOCK + 8), xkb::KeyDirection::Up); let wkb_leds = wkb.leds_state(); - let wkb_scroll_on = (wkb_leds & 4) != 0; + let wkb_scroll_on = wkb_leds.scroll_lock; let xkb_scroll_on = xkb.led_index_is_active(scroll_led_idx); assert_eq!( @@ -243,9 +243,9 @@ fn all_locks_pressed(locale: &str) { // Compare LED states between wkb and xkbcommon let wkb_leds = wkb.leds_state(); - let wkb_caps = (wkb_leds & 2) != 0; - let wkb_num = (wkb_leds & 1) != 0; - let wkb_scroll = (wkb_leds & 4) != 0; + let wkb_caps = wkb_leds.caps_lock; + let wkb_num = wkb_leds.num_lock; + let wkb_scroll = wkb_leds.scroll_lock; let xkb_caps = xkb.led_index_is_active(caps_led_idx); let xkb_num = xkb.led_index_is_active(num_led_idx); diff --git a/tests/modifiers.rs b/tests/modifiers.rs index 32c75fdc..f49572e2 100644 --- a/tests/modifiers.rs +++ b/tests/modifiers.rs @@ -349,8 +349,8 @@ fn serialized_modifiers(state: &xkbcmn::State) -> (u32, u32, u32, u32) { } fn assert_same_modifiers_state(wkb: &wkb::WKB, xkb: &xkbcmn::State, context: &str) { - let ms = wkb.modifiers_state(); - let wkb_state = (ms.depressed, ms.latched, ms.locked, ms.layout); + let rm = wkb.raw_modifiers(); + let wkb_state = (rm.depressed, rm.latched, rm.locked, rm.layout); let xkb_state = serialized_modifiers(xkb); assert_eq!( wkb_state, xkb_state, diff --git a/tests/type.rs b/tests/type.rs index a5b1e192..e5f04bac 100644 --- a/tests/type.rs +++ b/tests/type.rs @@ -173,8 +173,8 @@ fn run_type_test(case_dir: &Path, key_names: &HashMap) -> Result<() xkb_state.serialize_mods(xkb::STATE_MODS_LATCHED), xkb_state.serialize_mods(xkb::STATE_MODS_LOCKED), ); - let ms = wkb.modifiers_state(); - let (wkb_dep, wkb_lat, wkb_lock) = (ms.depressed, ms.latched, ms.locked); + let rm = wkb.raw_modifiers(); + let (wkb_dep, wkb_lat, wkb_lock) = (rm.depressed, rm.latched, rm.locked); if xkb_mods != (wkb_dep, wkb_lat, wkb_lock) { diffs.push(format!( @@ -195,8 +195,8 @@ fn run_type_test(case_dir: &Path, key_names: &HashMap) -> Result<() xkb_state.serialize_mods(xkb::STATE_MODS_LATCHED), xkb_state.serialize_mods(xkb::STATE_MODS_LOCKED), ); - let ms = wkb.modifiers_state(); - let (wkb_dep, wkb_lat, wkb_lock) = (ms.depressed, ms.latched, ms.locked); + let rm = wkb.raw_modifiers(); + let (wkb_dep, wkb_lat, wkb_lock) = (rm.depressed, rm.latched, rm.locked); if xkb_mods != (wkb_dep, wkb_lat, wkb_lock) { diffs.push(format!( diff --git a/xkb-core/src/atom.rs b/xkb-core/src/atom.rs index 18657114..7a215f00 100644 --- a/xkb-core/src/atom.rs +++ b/xkb-core/src/atom.rs @@ -27,11 +27,17 @@ fn hash_buf(bytes: &[u8]) -> u32 { hash } -/// Create new atom table +/// Create new atom table with pre-allocated capacity pub fn atom_table_new() -> atom_table { + // Pre-allocate for typical keymap compilation (~300-500 atoms) + // Avoids 6+ resize+rehash cycles during compilation atom_table { - index: vec![0; 4], - strings: vec![None], // index 0 = XKB_ATOM_NONE + index: vec![0; 1024], + strings: { + let mut v = Vec::with_capacity(512); + v.push(None); // index 0 = XKB_ATOM_NONE + v + }, } } diff --git a/xkb-core/src/context.rs b/xkb-core/src/context.rs index fd33756f..fa7beca8 100644 --- a/xkb-core/src/context.rs +++ b/xkb-core/src/context.rs @@ -2,9 +2,7 @@ use std::env::VarError; use crate::atom::{atom_intern, atom_table_new, atom_text}; -pub use crate::messages::{ - XKB_ERROR_NO_VALID_DEFAULT_INCLUDE_PATH, XKB_LOG_VERBOSITY_DEFAULT, -}; +pub use crate::messages::{XKB_ERROR_NO_VALID_DEFAULT_INCLUDE_PATH, XKB_LOG_VERBOSITY_DEFAULT}; use crate::shared_types::{ DFLT_XKB_CONFIG_EXTRA_PATH, DFLT_XKB_CONFIG_ROOT, DFLT_XKB_CONFIG_UNVERSIONED_EXTENSIONS_PATH, DFLT_XKB_CONFIG_VERSIONED_EXTENSIONS_PATH, DFLT_XKB_LEGACY_ROOT, diff --git a/xkb-core/src/keymap.rs b/xkb-core/src/keymap.rs index 6bdf81ac..5d9f84ca 100644 --- a/xkb-core/src/keymap.rs +++ b/xkb-core/src/keymap.rs @@ -2,11 +2,10 @@ use std::rc::Rc; use crate::atom::{atom_lookup_ref, atom_text}; use crate::context::{xkb_atom_intern_bytes, xkb_context_sanitize_rule_names}; +pub use crate::shared_types::XKB_KEYMAP_COMPILE_FLAGS_VALUES; pub use crate::shared_types::{ - xkb_action, xkb_key, xkb_keymap, xkb_led, - xkb_level, xkb_mod_set, MOD_BOTH, MOD_REAL, + xkb_action, xkb_key, xkb_keymap, xkb_led, xkb_level, xkb_mod_set, MOD_BOTH, MOD_REAL, }; -pub use crate::shared_types::XKB_KEYMAP_COMPILE_FLAGS_VALUES; pub fn clear_level(leveli: &mut xkb_level) { leveli.syms.clear(); leveli.actions.clear(); @@ -18,7 +17,6 @@ pub fn xkb_keymap_new_from_names( flags: u32, ) -> Option> { let format = XKB_KEYMAP_FORMAT_TEXT_V2; - let mut keymap = xkb_keymap_new(ctx.clone(), "xkb_keymap_new_from_names2", format, flags)?; let mut rmlvo: xkb_rule_names = match rmlvo_in { Some(r) => r.clone(), None => xkb_rule_names { @@ -30,6 +28,7 @@ pub fn xkb_keymap_new_from_names( }, }; xkb_context_sanitize_rule_names(&ctx, &mut rmlvo); + let mut keymap = xkb_keymap_new(ctx, "xkb_keymap_new_from_names2", format, flags)?; if !crate::xkbcomp::xkbcomp::text_v1_keymap_new_from_names(&mut keymap, &rmlvo) { return None; } diff --git a/xkb-core/src/keysym_utf.rs b/xkb-core/src/keysym_utf.rs index 39fe2f14..76dd2468 100644 --- a/xkb-core/src/keysym_utf.rs +++ b/xkb-core/src/keysym_utf.rs @@ -3924,6 +3924,63 @@ fn keysym_to_codepoint(keysym: u32) -> Option { _ => {} } + // Dead keysyms → Unicode combining characters + // These are needed so dead keys get character entries in the flat table, + // allowing the character-based composer to process them. + match keysym { + 0xfe50 => return Some(0x0300), // dead_grave → COMBINING GRAVE ACCENT + 0xfe51 => return Some(0x0301), // dead_acute → COMBINING ACUTE ACCENT + 0xfe52 => return Some(0x0302), // dead_circumflex → COMBINING CIRCUMFLEX ACCENT + 0xfe53 => return Some(0x0303), // dead_tilde → COMBINING TILDE + 0xfe54 => return Some(0x0304), // dead_macron → COMBINING MACRON + 0xfe55 => return Some(0x0306), // dead_breve → COMBINING BREVE + 0xfe56 => return Some(0x0307), // dead_abovedot → COMBINING DOT ABOVE + 0xfe57 => return Some(0x0308), // dead_diaeresis → COMBINING DIAERESIS + 0xfe58 => return Some(0x030A), // dead_abovering → COMBINING RING ABOVE + 0xfe59 => return Some(0x030B), // dead_doubleacute → COMBINING DOUBLE ACUTE ACCENT + 0xfe5a => return Some(0x030C), // dead_caron → COMBINING CARON + 0xfe5b => return Some(0x0327), // dead_cedilla → COMBINING CEDILLA + 0xfe5c => return Some(0x0328), // dead_ogonek → COMBINING OGONEK + 0xfe5d => return Some(0x0345), // dead_iota → COMBINING GREEK YPOGEGRAMMENI + 0xfe5e => return Some(0x3099), // dead_voiced_sound → COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK + 0xfe5f => return Some(0x309A), // dead_semivoiced_sound → COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK + 0xfe60 => return Some(0x0323), // dead_belowdot → COMBINING DOT BELOW + 0xfe61 => return Some(0x0309), // dead_hook → COMBINING HOOK ABOVE + 0xfe62 => return Some(0x031B), // dead_horn → COMBINING HORN + 0xfe63 => return Some(0x0338), // dead_stroke → COMBINING LONG SOLIDUS OVERLAY + 0xfe64 => return Some(0x0313), // dead_abovecomma → COMBINING COMMA ABOVE + 0xfe65 => return Some(0x0314), // dead_abovereversedcomma → COMBINING REVERSED COMMA ABOVE + 0xfe66 => return Some(0x030F), // dead_doublegrave → COMBINING DOUBLE GRAVE ACCENT + 0xfe67 => return Some(0x0325), // dead_belowring → COMBINING RING BELOW + 0xfe68 => return Some(0x0332), // dead_belowmacron → COMBINING LOW LINE + 0xfe69 => return Some(0x032D), // dead_belowcircumflex → COMBINING CIRCUMFLEX ACCENT BELOW + 0xfe6a => return Some(0x0330), // dead_belowtilde → COMBINING TILDE BELOW + 0xfe6b => return Some(0x032E), // dead_belowbreve → COMBINING BREVE BELOW + 0xfe6c => return Some(0x0324), // dead_belowdiaeresis → COMBINING DIAERESIS BELOW + 0xfe6d => return Some(0x0311), // dead_invertedbreve → COMBINING INVERTED BREVE + 0xfe6e => return Some(0x0326), // dead_belowcomma → COMBINING COMMA BELOW + 0xfe6f => return Some(0x00A4), // dead_currency → CURRENCY SIGN + 0xfe80 => return Some(0x0061), // dead_a → 'a' + 0xfe81 => return Some(0x0041), // dead_A → 'A' + 0xfe82 => return Some(0x0065), // dead_e → 'e' + 0xfe83 => return Some(0x0045), // dead_E → 'E' + 0xfe84 => return Some(0x0069), // dead_i → 'i' + 0xfe85 => return Some(0x0049), // dead_I → 'I' + 0xfe86 => return Some(0x006F), // dead_o → 'o' + 0xfe87 => return Some(0x004F), // dead_O → 'O' + 0xfe88 => return Some(0x0075), // dead_u → 'u' + 0xfe89 => return Some(0x0055), // dead_U → 'U' + 0xfe8a => return Some(0x0259), // dead_schwa → 'ə' + 0xfe8b => return Some(0x018F), // dead_SCHWA → 'Ə' + 0xfe8c => return Some(0x03B1), // dead_greek → GREEK SMALL LETTER ALPHA (approximate) + 0xfe8d => return Some(0x0654), // dead_hamza → ARABIC HAMZA ABOVE + 0xfe90 => return Some(0x0331), // dead_lowline → COMBINING MACRON BELOW + 0xfe91 => return Some(0x030D), // dead_aboveverticalline → COMBINING VERTICAL LINE ABOVE + 0xfe92 => return Some(0x0329), // dead_belowverticalline → COMBINING VERTICAL LINE BELOW + 0xfe93 => return Some(0x0338), // dead_longsolidusoverlay → COMBINING LONG SOLIDUS OVERLAY + _ => {} + } + // Look up in the static table bin_search_keysym(keysym) } diff --git a/xkb-core/src/rust_types.rs b/xkb-core/src/rust_types.rs index af375f12..8ea8d8e6 100644 --- a/xkb-core/src/rust_types.rs +++ b/xkb-core/src/rust_types.rs @@ -168,6 +168,7 @@ fn preprocess_unicode_keysyms(input: &str) -> std::borrow::Cow<'_, str> { // ============================================================================ /// Safe wrapper around xkb_context with automatic cleanup +#[derive(Clone)] pub struct Context { entity: xkb_context, } @@ -180,27 +181,27 @@ impl Context { Some(Context { entity: ctx }) } - /// Create a keymap from RMLVO names - pub fn keymap_from_names(&self, rules: &RuleNames) -> Option { + /// Create a keymap from RMLVO names. Consumes the context. + pub fn keymap_from_names(self, rules: &RuleNames) -> Option { use crate::shared_types::XKB_KEYMAP_COMPILE_NO_FLAGS; let rmlvo_c = rules.to_c_keymap(); let keymap = super::keymap::xkb_keymap_new_from_names( - self.entity.clone(), + self.entity, Some(&rmlvo_c), XKB_KEYMAP_COMPILE_NO_FLAGS, )?; Some(Keymap { inner: keymap }) } - /// Create a keymap from a keymap string - pub fn keymap_from_string(&self, keymap_str: &str) -> Option { + /// Create a keymap from a keymap string. Consumes the context. + pub fn keymap_from_string(self, keymap_str: &str) -> Option { use crate::shared_types::{XKB_KEYMAP_COMPILE_NO_FLAGS, XKB_KEYMAP_FORMAT_TEXT_V1}; let processed = preprocess_unicode_keysyms(keymap_str); let keymap_cstr = CString::new(processed.as_ref()).ok()?; let keymap = super::keymap::xkb_keymap_new_from_string( - self.entity.clone(), + self.entity, &keymap_cstr, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS, diff --git a/xkb-core/src/shared_types.rs b/xkb-core/src/shared_types.rs index 51963901..b248f7ba 100644 --- a/xkb-core/src/shared_types.rs +++ b/xkb-core/src/shared_types.rs @@ -1,5 +1,9 @@ //! Shared type definitions used across multiple modules. +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::Arc; + // ── xkbcommon public types ─────────────────────────────────────────── pub const XKB_LOG_LEVEL_DEBUG: u32 = 50; @@ -67,17 +71,48 @@ pub struct xkb_context { pub refcnt: i32, pub log_level: u32, pub log_verbosity: i32, - // pub user_data: *mut ::core::ffi::c_void, pub names_dflt: xkb_rule_names, pub includes: Vec, pub failed_includes: Vec, pub atom_table: atom_table, - // pub x11_atom_cache: *mut ::core::ffi::c_void, pub use_environment_names: bool, pub use_secure_getenv: bool, pub pending_default_includes: bool, } +thread_local! { + /// Thread-local file cache shared across all xkb_context instances. + /// Survives context clones and keymap compilations within the same thread. + static FILE_CACHE: RefCell>>> = RefCell::new(HashMap::new()); +} + +/// Read a file from the thread-local cache, or read from disk and cache it. +pub fn read_file_cached(path: &str) -> Option>> { + FILE_CACHE + .with(|cache| { + let cache = cache.borrow(); + cache.get(path).cloned() + }) + .or_else(|| { + use std::io::Read; + let mut file = std::fs::File::open(path).ok()?; + let mut data = Vec::new(); + file.read_to_end(&mut data).ok()?; + let arc = Arc::new(data); + FILE_CACHE.with(|cache| { + cache.borrow_mut().insert(path.to_string(), arc.clone()); + }); + Some(arc) + }) +} + +/// Clear the thread-local file cache. +pub fn clear_file_cache() { + FILE_CACHE.with(|cache| { + cache.borrow_mut().clear(); + }); +} + // ── keymap_h types (from keymap_priv.rs) ──────────────────────────── pub struct xkb_keymap { diff --git a/xkb-core/src/xkbcomp/compat.rs b/xkb-core/src/xkbcomp/compat.rs index 0182b987..6b075f34 100644 --- a/xkb-core/src/xkbcomp/compat.rs +++ b/xkb-core/src/xkbcomp/compat.rs @@ -1193,7 +1193,7 @@ fn CopyCompatToKeymap(ki: &mut xkb_keymap_info<'_>, info: &mut CompatInfo) -> bo // Collect sym_interprets first (doesn't need keymap) let sym_interprets = if !info.interps.is_empty() { let mut collect: collect = collect { - sym_interprets: Vec::new(), + sym_interprets: Vec::with_capacity(info.interps.len()), }; CopyInterps(info, true, MATCH_EXACTLY, &mut collect); CopyInterps(info, true, MATCH_ALL, &mut collect); diff --git a/xkb-core/src/xkbcomp/include.rs b/xkb-core/src/xkbcomp/include.rs index 4e43c1fd..1ddce6b7 100644 --- a/xkb-core/src/xkbcomp/include.rs +++ b/xkb-core/src/xkbcomp/include.rs @@ -12,15 +12,11 @@ pub const MERGE_AUGMENT_PREFIX: i32 = '|' as i32; pub const MERGE_REPLACE_PREFIX: i32 = '^' as i32; pub use crate::messages::{ - XKB_ERROR_INCLUDED_FILE_NOT_FOUND, - XKB_ERROR_INSUFFICIENT_BUFFER_SIZE, XKB_ERROR_INVALID_INCLUDED_FILE, XKB_ERROR_INVALID_PATH, - XKB_ERROR_RECURSIVE_INCLUDE, + XKB_ERROR_INCLUDED_FILE_NOT_FOUND, XKB_ERROR_INSUFFICIENT_BUFFER_SIZE, + XKB_ERROR_INVALID_INCLUDED_FILE, XKB_ERROR_INVALID_PATH, XKB_ERROR_RECURSIVE_INCLUDE, }; -pub use crate::shared_ast_types::{ - xkb_file_type_to_string, IncludeStmt, - XkbFile, MAP_IS_DEFAULT, -}; -use crate::xkbcomp::scanner::XkbParseFile; +pub use crate::shared_ast_types::{xkb_file_type_to_string, IncludeStmt, XkbFile, MAP_IS_DEFAULT}; +use crate::xkbcomp::scanner::XkbParseString; /// Parsed result from one segment of an include statement. pub struct ParsedIncludeMap { @@ -221,7 +217,7 @@ pub fn FindFileInXkbPath( type_0: u32, offset: &mut u32, required: bool, -) -> Option<(std::fs::File, String)> { +) -> Option<(std::sync::Arc>, String)> { let type_dir = DirectoryForInclude(type_0); let mut i: u32 = *offset; while i < xkb_context_num_include_paths(ctx) { @@ -238,9 +234,9 @@ pub fn FindFileInXkbPath( 4096, &path ); - } else if let Ok(file) = std::fs::File::open(&path) { + } else if let Some(data) = crate::shared_types::read_file_cached(&path) { *offset = i; - return Some((file, path)); + return Some((data, path)); } i += 1; } @@ -287,10 +283,8 @@ pub fn ProcessIncludeFile( let absolute_path = stmt_file.starts_with('/'); let mut offset: u32 = 0; - let mut file_and_path: Option<(std::fs::File, String)> = if absolute_path { - std::fs::File::open(&stmt_file) - .ok() - .map(|f| (f, stmt_file.clone())) + let mut file_and_path: Option<(std::sync::Arc>, String)> = if absolute_path { + crate::shared_types::read_file_cached(&stmt_file).map(|data| (data, stmt_file.clone())) } else if expanded { // Expanded but not absolute — don't search include paths None @@ -298,9 +292,8 @@ pub fn ProcessIncludeFile( FindFileInXkbPath(ctx, "(unknown)", &stmt_file, file_type, &mut offset, true) }; - while let Some((ref open_file, ref _path)) = file_and_path { - if let Some(parsed) = XkbParseFile(ctx, open_file, &stmt.file, map_str) { - // Drop the file (closes it) + while let Some((ref file_data, ref _path)) = file_and_path { + if let Some(parsed) = XkbParseString(ctx, file_data, &stmt.file, map_str) { let _ = file_and_path.take(); if parsed.file_type != file_type { diff --git a/xkb-core/src/xkbcomp/keymap.rs b/xkb-core/src/xkbcomp/keymap.rs index 85c04734..ee07093e 100644 --- a/xkb-core/src/xkbcomp/keymap.rs +++ b/xkb-core/src/xkbcomp/keymap.rs @@ -2,7 +2,8 @@ use super::prelude::*; pub use crate::shared_ast_types::xkb_file_type_to_string; pub use crate::shared_types::{ areOverlappingOverlaysSupported, format_max_groups, format_max_overlays, - isGroupLockOnReleaseSupported, isModsLatchOnPressSupported, isModsUnLockOnPressSupported, MAX_ACTIONS_PER_LEVEL, XKB_ALL_GROUPS, XKB_MAX_GROUPS, _XKB_MOD_INDEX_NUM_ENTRIES, + isGroupLockOnReleaseSupported, isModsLatchOnPressSupported, isModsUnLockOnPressSupported, + MAX_ACTIONS_PER_LEVEL, XKB_ALL_GROUPS, XKB_MAX_GROUPS, _XKB_MOD_INDEX_NUM_ENTRIES, }; use crate::shared_types::{MOD_REAL_MASK_ALL, XKB_KEYMAP_FORMAT_TEXT_V1}; pub use crate::state::mod_mask_get_effective; @@ -57,7 +58,7 @@ fn default_interpret() -> xkb_sym_interpret { required: false, num_actions: 0, action: xkb_action::None, - actions: Vec::new(), + actions: Vec::new(), // Note: xkb_action is Copy, so Vec::new() is zero-alloc (no heap until push) } } /// Returns interp indices into `keymap.sym_interprets`, or `usize::MAX` for default interprets. @@ -69,11 +70,16 @@ fn FindInterpForKey( interp_indices: &mut Vec, ) -> bool { let keycode = keymap.keys[key_idx].keycode; - let syms = xkb_keymap_key_get_syms_by_level_ref(keymap, keycode, group, level).to_vec(); + let syms_ref = xkb_keymap_key_get_syms_by_level_ref(keymap, keycode, group, level); - if syms.is_empty() { + if syms_ref.is_empty() { return false; } + // Copy syms to stack to release borrow on keymap (most keys have 1-2 syms) + let mut syms_buf = [0u32; 8]; + let num_syms = syms_ref.len().min(8); + syms_buf[..num_syms].copy_from_slice(&syms_ref[..num_syms]); + let syms = &syms_buf[..num_syms]; let key_modmap = keymap.keys[key_idx].modmap; let key_name = keymap.keys[key_idx].name; let num_syms = syms.len() as i32; @@ -160,8 +166,8 @@ fn FindInterpForKey( fn ApplyInterpsToKey(keymap: &mut xkb_keymap, key_idx: usize) -> bool { let mut vmodmap: u32 = 0_u32; let mut level: u32; - let mut interp_indices: Vec = Vec::new(); - let mut actions: Vec = Vec::new(); + let mut interp_indices: Vec = Vec::with_capacity(4); + let mut actions: Vec = Vec::with_capacity(4); let num_groups = keymap.keys[key_idx].num_groups; let mut group: u32 = 0_u32; while group < num_groups { @@ -212,11 +218,13 @@ fn ApplyInterpsToKey(keymap: &mut xkb_keymap, key_idx: usize) -> bool { actions.truncate(MAX_ACTIONS_PER_LEVEL as usize); } keymap.keys[key_idx].groups[group as usize].levels[level as usize].actions = - actions.clone(); - if !actions.is_empty() { + std::mem::take(&mut actions); + if !keymap.keys[key_idx].groups[group as usize].levels[level as usize] + .actions + .is_empty() + { keymap.keys[key_idx].groups[group as usize].implicit_actions = true; } - actions.clear(); } level = level.wrapping_add(1); } diff --git a/xkb-core/src/xkbcomp/rules.rs b/xkb-core/src/xkbcomp/rules.rs index 87cad0b6..0074a6e6 100644 --- a/xkb-core/src/xkbcomp/rules.rs +++ b/xkb-core/src/xkbcomp/rules.rs @@ -1,12 +1,13 @@ pub const OPTIONS_GROUP_SPECIFIER_PREFIX: i32 = '!' as i32; pub use crate::messages::{ - XKB_ERROR_CANNOT_RESOLVE_RMLVO, XKB_ERROR_INVALID_FILE_ENCODING, XKB_ERROR_INVALID_RULES_SYNTAX, XKB_ERROR_RULES_INVALID_LAYOUT_INDEX_PERCENT_EXPANSION, + XKB_ERROR_CANNOT_RESOLVE_RMLVO, XKB_ERROR_INVALID_FILE_ENCODING, + XKB_ERROR_INVALID_RULES_SYNTAX, XKB_ERROR_RULES_INVALID_LAYOUT_INDEX_PERCENT_EXPANSION, }; pub use crate::scanner_utils::{scanner, scanner_loc, sval, svaleq}; pub use crate::shared_ast_types::FILE_TYPE_RULES; -pub use crate::shared_types::XKB_MAX_GROUPS; pub use crate::shared_types::XKB_ERROR_UNSUPPORTED_LAYOUT_INDEX; +pub use crate::shared_types::XKB_MAX_GROUPS; pub use crate::xkbcomp::include::{ expand_path_str, FindFileInXkbPath, MERGE_AUGMENT_PREFIX, MERGE_OVERRIDE_PREFIX, @@ -556,10 +557,8 @@ fn matcher_include( let absolute_path = stmt_file.starts_with('/'); let mut offset: u32 = 0; - let mut file_and_path: Option<(std::fs::File, String)> = if absolute_path { - std::fs::File::open(&stmt_file) - .ok() - .map(|f| (f, stmt_file.clone())) + let mut file_and_path: Option<(std::sync::Arc>, String)> = if absolute_path { + crate::shared_types::read_file_cached(&stmt_file).map(|data| (data, stmt_file.clone())) } else if expanded { None } else { @@ -573,8 +572,8 @@ fn matcher_include( ) }; - while let Some((ref open_file, ref path)) = file_and_path { - let ret: bool = read_rules_file(m, include_depth.wrapping_add(1_u32), open_file, path); + while let Some((ref file_data, ref path)) = file_and_path { + let ret: bool = read_rules_file(m, include_depth.wrapping_add(1_u32), file_data, path); let path_str = path.clone(); let _ = file_and_path.take(); if ret { @@ -2019,23 +2018,13 @@ fn matcher_match(m: &mut matcher, s: &mut scanner, include_depth: u32, _file_nam fn read_rules_file( matcher: &mut matcher<'_>, include_depth: u32, - file: &std::fs::File, + file_data: &[u8], path: &str, ) -> bool { #[allow(unused_assignments)] let mut scanner: scanner = scanner::new(&[], ""); - use crate::utils::MappedFile; - - let mapped = match MappedFile::new(file) { - Ok(m) => m, - Err(e) => { - log::error!("Couldn't read rules file \"{}\": {}\n", path, e); - return false; - } - }; - - scanner = scanner::new(mapped.as_bytes(), path); + scanner = scanner::new(file_data, path); if !scanner.check_supported_char_encoding() { let loc: scanner_loc = scanner.token_location(); log::error!("[XKB-{:03}] {}:{}:{}: This could be a file encoding issue. Supported encodings must be backward compatible with ASCII.\n", @@ -2075,9 +2064,11 @@ fn xkb_resolve_partial_rules(rules: &str, suffix: &str, matcher: &mut matcher<'_ &mut offset, false, ); - let Some((file, path)) = found else { break }; - let ok: bool = read_rules_file(matcher, 0, &file, &path); - drop(file); + let Some((file_data, path)) = found else { + break; + }; + let ok: bool = read_rules_file(matcher, 0, &file_data, &path); + drop(file_data); if !ok { log::error!( "[XKB-{:03}] Error while parsing XKB rules \"{}\"\n", @@ -2107,7 +2098,7 @@ fn xkb_resolve_rules( &mut offset, true, ); - let Some((file, path)) = found else { + let Some((file_data, path)) = found else { log::error!( "[XKB-{:03}] Cannot load XKB rules \"{}\"\n", XKB_ERROR_CANNOT_RESOLVE_RMLVO as i32, @@ -2117,7 +2108,7 @@ fn xkb_resolve_rules( }; ret = xkb_resolve_partial_rules(rules_str, ".pre", matcher); if ret { - ret = read_rules_file(matcher, 0, &file, &path); + ret = read_rules_file(matcher, 0, &file_data, &path); if !ret { log::error!( "[XKB-{:03}] Error while parsing XKB rules \"{}\"\n", diff --git a/xkb-core/src/xkbcomp/symbols.rs b/xkb-core/src/xkbcomp/symbols.rs index 32e040bd..96ca32de 100644 --- a/xkb-core/src/xkbcomp/symbols.rs +++ b/xkb-core/src/xkbcomp/symbols.rs @@ -103,7 +103,7 @@ impl SymbolsInfo { include_depth: 0, explicit_group: 0, max_groups: 0, - keys: Vec::new(), + keys: Vec::with_capacity(256), default_key: KeyInfo::new_zeroed(), default_actions: ActionsInfo { actions: [xkb_action::None; 21], @@ -1711,14 +1711,15 @@ fn HandleSymbolsBody( ) -> bool { let mut all_valid_entries: bool = true; for def in defs.iter_mut() { - let field: String; + let field_owned: String; + let field: &str; let mut arrayNdx_opt: Option<&ExprDef> = None; let mut ok: bool = true; if def.name.is_none() { if def.value.is_none() || !is_action_list_value(def.value.as_ref().unwrap()) { - field = "symbols".to_owned(); + field = "symbols"; } else { - field = "actions".to_owned(); + field = "actions"; } } else { let mut elem_atom: u32 = 0; @@ -1730,7 +1731,8 @@ fn HandleSymbolsBody( &mut arrayNdx_opt, ); let elem = xkb_atom_text(&ki.ctx().atom_table, elem_atom); - field = xkb_atom_text(&ki.ctx().atom_table, field_atom).to_owned(); + field_owned = xkb_atom_text(&ki.ctx().atom_table, field_atom).to_owned(); + field = &field_owned; if ok as i32 != 0 && !elem.is_empty() { log::error!("[XKB-{:03}] Cannot set global defaults for \"{}\" element within a key statement: move statements to the global file scope. Assignment to \"{}.{}\" ignored.\n", XKB_ERROR_GLOBAL_DEFAULTS_WRONG_SCOPE as i32, @@ -1792,22 +1794,23 @@ fn HandleSymbolsDef( info: &mut SymbolsInfo, stmt: &mut SymbolsDef, ) -> bool { - #[allow(unused_assignments)] - let mut keyi: KeyInfo = KeyInfo::new_zeroed(); - keyi = info.default_key.clone(); - keyi.groups = Vec::new(); - if !info.default_key.groups.is_empty() { - // Shallow copy the GroupInfo structs (bitwise), then deep-copy inner pointers - keyi.groups.extend_from_slice(&info.default_key.groups); - } - let mut i: u32 = 0_u32; - while i < keyi.groups.len() as u32 { - CopyGroupInfo( - &mut keyi.groups[i as usize], - &info.default_key.groups[i as usize], - ); - i = i.wrapping_add(1); - } + // Clone scalar fields from default_key, deep-copy groups + let dk = &info.default_key; + let mut keyi = KeyInfo { + name: dk.name, + vmodmap: dk.vmodmap, + default_type: dk.default_type, + out_of_range_group_number: dk.out_of_range_group_number, + groups: dk.groups.clone(), // deep clone via derive(Clone) + out_of_range_group_policy: dk.out_of_range_group_policy, + defined: dk.defined, + merge: dk.merge, + repeat: dk.repeat, + out_of_range_pending_group: dk.out_of_range_pending_group, + overlays_clear: dk.overlays_clear, + overlays: dk.overlays, + overlay_keys: dk.overlay_keys.clone(), + }; keyi.merge = stmt.merge as merge_mode; keyi.name = stmt.keyName; if HandleSymbolsBody(ki, info, &mut stmt.symbols, &mut keyi) as i32 != 0 diff --git a/xkbcommon-compat/src/lib.rs b/xkbcommon-compat/src/lib.rs index 1acbb71c..13c2903d 100644 --- a/xkbcommon-compat/src/lib.rs +++ b/xkbcommon-compat/src/lib.rs @@ -91,7 +91,7 @@ pub unsafe extern "C" fn xkb_keymap_new_from_names( } }; - match ctx.keymap_from_names(&rules) { + match (*ctx).clone().keymap_from_names(&rules) { Some(km) => Box::into_raw(Box::new(xkb_keymap(km))), None => ptr::null_mut(), } @@ -111,7 +111,7 @@ pub unsafe extern "C" fn xkb_keymap_new_from_string( let ctx = &(*ctx).0; let s = CStr::from_ptr(string).to_string_lossy(); - match ctx.keymap_from_string(&s) { + match (*ctx).clone().keymap_from_string(&s) { Some(km) => Box::into_raw(Box::new(xkb_keymap(km))), None => ptr::null_mut(), }