#[cfg(test)]
mod tests;
#[expect(unused_imports)]
use crate::{Components, builder::Builder};
use {
crate::{
GroupType, Keycode, Keysym, ModifierMask, builder::Redirect, group::GroupIndex,
key_storage::KeyStorage,
},
smallvec::SmallVec,
std::fmt::{Debug, Formatter},
};
#[derive(Clone, Debug)]
pub struct LookupTable {
pub(crate) ctrl: Option<ModifierMask>,
pub(crate) caps: Option<ModifierMask>,
pub(crate) keys: KeyStorage<KeyGroups>,
}
#[derive(Default, Clone, Debug)]
pub(crate) struct KeyGroups {
pub(crate) repeats: bool,
pub(crate) redirect: Redirect,
pub(crate) groups: Box<[Option<KeyGroup>]>,
}
#[derive(Clone, Debug)]
pub(crate) struct KeyGroup {
pub(crate) ty: GroupType,
pub(crate) levels: Box<[KeyLevel]>,
}
#[derive(Default, Clone, Debug)]
pub(crate) struct KeyLevel {
pub(crate) symbols: SmallVec<[Keysym; 1]>,
}
#[derive(Copy, Clone)]
pub struct Lookup<'a> {
original_mods: ModifierMask,
remaining_mods: ModifierMask,
use_ctrl_fallback: bool,
do_ctrl_transform: bool,
do_caps_transform: bool,
repeats: bool,
lookup: &'a LookupTable,
groups: &'a [Option<KeyGroup>],
syms: &'a [Keysym],
}
#[derive(Clone, Debug)]
pub struct LookupIter<'a> {
did_ctrl_fallback: bool,
do_ctrl_transform: bool,
do_caps_transform: bool,
syms: &'a [Keysym],
}
#[derive(Copy, Clone, Debug)]
pub struct KeysymProps {
keysym: Keysym,
char: Option<char>,
did_ctrl_fallback: bool,
did_ctrl_transform: bool,
did_caps_transform: bool,
}
impl Lookup<'_> {
pub fn with_ctrl_fallback(mut self, fallback: bool) -> Self {
self.use_ctrl_fallback = fallback;
self
}
pub fn set_ctrl_fallback(&mut self, fallback: bool) {
self.use_ctrl_fallback = fallback;
}
pub fn with_ctrl_transform(mut self, transform: bool) -> Self {
self.do_ctrl_transform = transform;
self
}
pub fn set_ctrl_transform(&mut self, transform: bool) {
self.do_ctrl_transform = transform;
}
pub fn with_caps_transform(mut self, transform: bool) -> Self {
self.do_caps_transform = transform;
self
}
pub fn set_caps_transform(&mut self, transform: bool) {
self.do_caps_transform = transform;
}
pub fn repeats(&self) -> bool {
self.repeats
}
pub fn remaining_mods(&self) -> ModifierMask {
self.remaining_mods
}
}
impl KeysymProps {
pub fn keysym(&self) -> Keysym {
self.keysym
}
pub fn char(&self) -> Option<char> {
self.char
}
pub fn did_ctrl_fallback(&self) -> bool {
self.did_ctrl_fallback
}
pub fn did_ctrl_transform(&self) -> bool {
self.did_ctrl_transform
}
pub fn did_caps_transform(&self) -> bool {
self.did_caps_transform
}
}
impl Debug for Lookup<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keysyms")
.field("original_mods", &self.original_mods)
.field("remaining_mods", &self.remaining_mods)
.field("use_ctrl_fallback", &self.use_ctrl_fallback)
.field("do_ctrl_transform", &self.do_ctrl_transform)
.field("do_caps_transform", &self.do_caps_transform)
.field("repeats", &self.repeats)
.finish_non_exhaustive()
}
}
impl LookupTable {
pub fn lookup(&self, group: GroupIndex, mods: ModifierMask, keycode: Keycode) -> Lookup<'_> {
let mut consumed = ModifierMask::default();
let mut groups = &[][..];
let mut syms = &[][..];
let mut repeats = true;
if let Some(key) = self.keys.get(keycode) {
repeats = key.repeats;
groups = &key.groups;
if key.groups.len() > 0 {
let group = key.redirect.apply(group, key.groups.len());
if let Some(group) = &key.groups[group] {
let mapping = group.ty.map(mods);
consumed = mapping.consumed;
if let Some(level) = group.levels.get(mapping.level) {
syms = &level.symbols;
}
}
}
}
Lookup {
original_mods: mods,
remaining_mods: mods & !consumed,
use_ctrl_fallback: true,
do_ctrl_transform: true,
do_caps_transform: true,
lookup: self,
repeats,
groups,
syms,
}
}
pub fn effective_group(&self, group: GroupIndex, keycode: Keycode) -> Option<GroupIndex> {
if let Some(key) = self.keys.get(keycode) {
if key.groups.len() > 0 {
let group = key.redirect.apply(group, key.groups.len());
return Some(GroupIndex(group as u32));
}
}
None
}
pub fn repeats(&self, keycode: Keycode) -> bool {
if let Some(key) = self.keys.get(keycode) {
return key.repeats;
}
true
}
}
impl<'a> IntoIterator for Lookup<'a> {
type Item = KeysymProps;
type IntoIter = LookupIter<'a>;
fn into_iter(self) -> Self::IntoIter {
let mut do_ctrl_transform = false;
if let Some(mask) = self.lookup.ctrl {
if self.do_ctrl_transform && self.remaining_mods.contains(mask) {
do_ctrl_transform = true;
}
}
let mut do_caps_transform = false;
if let Some(mask) = self.lookup.caps {
if self.do_caps_transform && self.remaining_mods.contains(mask) {
do_caps_transform = true;
}
}
let mut syms = self.syms;
let mut did_ctrl_fallback = false;
if self.use_ctrl_fallback && do_ctrl_transform && syms.len() == 1 && syms[0].0 > 127 {
for group in self.groups.iter().flatten() {
let level = group.ty.map(self.original_mods).level;
if let Some(level) = group.levels.get(level) {
if level.symbols.len() == 1 && level.symbols[0].0 <= 127 {
syms = &level.symbols;
did_ctrl_fallback = true;
break;
}
}
}
}
LookupIter {
did_ctrl_fallback,
do_ctrl_transform,
do_caps_transform,
syms,
}
}
}
impl Iterator for LookupIter<'_> {
type Item = KeysymProps;
fn next(&mut self) -> Option<Self::Item> {
let mut sym = self.syms.first().copied()?;
self.syms = &self.syms[1..];
let mut did_caps_transform = false;
if self.do_caps_transform {
let prev = sym;
sym = sym.to_uppercase();
did_caps_transform = prev != sym;
}
let mut char = None;
let mut did_ctrl_transform = false;
if self.do_ctrl_transform && sym.0 <= 127 {
let s = sym.0 as u8;
'transform: {
let c = match s {
b'@'..=b'~' | b' ' => s & 0x1f,
b'2' => 0,
b'3'..=b'7' => s - 0x1b,
b'8' => 0x7f,
b'/' => 0x1f,
_ => break 'transform,
};
char = Some(c as char);
did_ctrl_transform = true;
}
}
if char.is_none() {
char = sym.char();
}
Some(KeysymProps {
keysym: sym,
char,
did_ctrl_fallback: self.did_ctrl_fallback,
did_ctrl_transform,
did_caps_transform,
})
}
}