use std::path::Path;
use xkbcommon::xkb;
use crate::InputError;
use crate::event::KeyEvent;
use crate::leds::Leds;
const XKB_OFFSET: u32 = 8;
const KEY_CAPS_LOCK: u32 = 58;
const KEY_NUM_LOCK: u32 = 69;
const KEY_SCROLL_LOCK: u32 = 70;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct KeymapNames {
pub rules: String,
pub model: String,
pub layout: String,
pub variant: String,
pub options: String,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub logo: bool,
}
pub struct Keyboard {
context: xkb::Context,
keymap: xkb::Keymap,
state: xkb::State,
held: Vec<u32>,
}
impl Keyboard {
pub fn from_names(names: &KeymapNames) -> Result<Self, InputError> {
let context = Self::context();
let keymap = compile(&context, names)?;
Ok(Self::with(context, keymap))
}
pub fn from_file(path: &Path) -> Result<Self, InputError> {
let text = std::fs::read_to_string(path).map_err(|source| InputError::Io {
path: path.to_path_buf(),
source,
})?;
Self::from_string(&text)
}
pub fn from_string(keymap: &str) -> Result<Self, InputError> {
let context = Self::context();
let keymap = xkb::Keymap::new_from_string(
&context,
keymap.to_owned(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
.ok_or(InputError::Keymap)?;
Ok(Self::with(context, keymap))
}
fn context() -> xkb::Context {
let mut context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
context.set_log_level(match drmkit_log::log_level() {
drmkit_log::LogLevel::Silent => xkb::LogLevel::Critical,
drmkit_log::LogLevel::Debug => xkb::LogLevel::Debug,
_ => xkb::LogLevel::Error,
});
context
}
fn with(context: xkb::Context, keymap: xkb::Keymap) -> Self {
let state = xkb::State::new(&keymap);
Self {
context,
keymap,
state,
held: Vec::new(),
}
}
pub fn reload(&mut self, names: &KeymapNames) -> Result<(), InputError> {
let keymap = compile(&self.context, names)?;
let mut state = xkb::State::new(&keymap);
let leds = self.leds();
for key in &self.held {
state.update_key(keycode(*key), xkb::KeyDirection::Down);
}
self.keymap = keymap;
self.state = state;
self.set_leds(leds);
Ok(())
}
pub fn resolve(&self, event: &mut KeyEvent) {
let code = keycode(event.key);
event.sym = self.state.key_get_one_sym(code).raw();
event.utf8 = self.state.key_get_utf8(code);
}
pub fn process_key(&mut self, event: &mut KeyEvent) {
self.resolve(event);
let code = keycode(event.key);
let direction = if event.pressed {
xkb::KeyDirection::Down
} else {
xkb::KeyDirection::Up
};
self.state.update_key(code, direction);
if event.pressed {
if !self.held.contains(&event.key) {
self.held.push(event.key);
}
} else {
self.held.retain(|key| *key != event.key);
}
}
pub fn release_all(&mut self) {
let leds = self.leds();
for key in std::mem::take(&mut self.held) {
self.state.update_key(keycode(key), xkb::KeyDirection::Up);
}
self.set_leds(leds);
}
#[must_use]
pub fn should_repeat(&self, key: u32) -> bool {
self.keymap.key_repeats(keycode(key))
}
#[must_use]
pub fn modifiers(&self) -> Modifiers {
Modifiers {
shift: self.modifier(xkb::MOD_NAME_SHIFT),
ctrl: self.modifier(xkb::MOD_NAME_CTRL),
alt: self.modifier(xkb::MOD_NAME_ALT),
logo: self.modifier(xkb::MOD_NAME_LOGO),
}
}
#[must_use]
pub fn leds(&self) -> Leds {
Leds {
caps_lock: self.state.led_name_is_active(xkb::LED_NAME_CAPS),
num_lock: self.state.led_name_is_active(xkb::LED_NAME_NUM),
scroll_lock: self.state.led_name_is_active(xkb::LED_NAME_SCROLL),
}
}
pub fn set_leds(&mut self, desired: Leds) {
let current = self.leds();
if desired.caps_lock != current.caps_lock {
self.tap(KEY_CAPS_LOCK);
}
if desired.num_lock != current.num_lock {
self.tap(KEY_NUM_LOCK);
}
if desired.scroll_lock != current.scroll_lock {
self.tap(KEY_SCROLL_LOCK);
}
}
#[must_use]
pub fn held_keys(&self) -> &[u32] {
&self.held
}
#[must_use]
pub const fn keymap(&self) -> &xkb::Keymap {
&self.keymap
}
fn modifier(&self, name: &str) -> bool {
self.state
.mod_name_is_active(name, xkb::STATE_MODS_EFFECTIVE)
}
fn tap(&mut self, key: u32) {
let code = keycode(key);
self.state.update_key(code, xkb::KeyDirection::Down);
self.state.update_key(code, xkb::KeyDirection::Up);
}
}
impl std::fmt::Debug for Keyboard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Keyboard")
.field("layouts", &self.keymap.layouts().collect::<Vec<_>>())
.field("held", &self.held)
.field("leds", &self.leds())
.finish_non_exhaustive()
}
}
fn keycode(key: u32) -> xkb::Keycode {
xkb::Keycode::new(key.saturating_add(XKB_OFFSET))
}
fn compile(context: &xkb::Context, names: &KeymapNames) -> Result<xkb::Keymap, InputError> {
xkb::Keymap::new_from_names(
context,
names.rules.as_str(),
names.model.as_str(),
names.layout.as_str(),
names.variant.as_str(),
Some(names.options.clone()),
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
.ok_or(InputError::Keymap)
}