Makes it more similar to release handling, removes some redundant checks. This makes it easier to integrate modifiers in the future.
249 lines
6.4 KiB
Rust
249 lines
6.4 KiB
Rust
/*! State of the emulated keyboard and keys.
|
|
* Regards the keyboard as if it was composed of switches. */
|
|
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::io;
|
|
use std::rc::Rc;
|
|
use std::string::FromUtf8Error;
|
|
|
|
use ::action::Action;
|
|
use ::logging;
|
|
|
|
// Traits
|
|
use std::io::Write;
|
|
use std::iter::{ FromIterator, IntoIterator };
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum PressType {
|
|
Released = 0,
|
|
Pressed = 1,
|
|
}
|
|
|
|
pub type KeyCode = u32;
|
|
|
|
/// When the submitted actions of keys need to be tracked,
|
|
/// they need a stable, comparable ID
|
|
#[derive(PartialEq)]
|
|
pub struct KeyStateId(*const KeyState);
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct KeyState {
|
|
pub pressed: PressType,
|
|
/// A cache of raw keycodes derived from Action::Sumbit given a keymap
|
|
pub keycodes: Vec<KeyCode>,
|
|
/// Static description of what the key does when pressed or released
|
|
pub action: Action,
|
|
}
|
|
|
|
impl KeyState {
|
|
#[must_use]
|
|
pub fn into_released(self) -> KeyState {
|
|
KeyState {
|
|
pressed: PressType::Released,
|
|
..self
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn into_pressed(self) -> KeyState {
|
|
KeyState {
|
|
pressed: PressType::Pressed,
|
|
..self
|
|
}
|
|
}
|
|
|
|
/// KeyStates instances are the unique identifiers of pressed keys,
|
|
/// and the actions submitted with them.
|
|
pub fn get_id(keystate: &Rc<RefCell<KeyState>>) -> KeyStateId {
|
|
KeyStateId(keystate.as_ptr() as *const KeyState)
|
|
}
|
|
}
|
|
|
|
/// Sorts an iterator by converting it to a Vector and back
|
|
fn sorted<'a, I: Iterator<Item=&'a str>>(
|
|
iter: I
|
|
) -> impl Iterator<Item=&'a str> {
|
|
let mut v: Vec<&'a str> = iter.collect();
|
|
v.sort();
|
|
v.into_iter()
|
|
}
|
|
|
|
/// Generates a mapping where each key gets a keycode, starting from ~~8~~
|
|
/// HACK: starting from 9, because 8 results in keycode 0,
|
|
/// which the compositor likes to discard
|
|
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
|
|
key_names: C
|
|
) -> HashMap<String, u32> {
|
|
let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s);
|
|
HashMap::from_iter(
|
|
// sort to remove a source of indeterminism in keycode assignment
|
|
sorted(key_names.into_iter().chain(special_keysyms))
|
|
.map(|name| String::from(name))
|
|
.zip(9..)
|
|
)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum FormattingError {
|
|
Utf(FromUtf8Error),
|
|
Format(io::Error),
|
|
}
|
|
|
|
impl fmt::Display for FormattingError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
FormattingError::Utf(e) => write!(f, "UTF: {}", e),
|
|
FormattingError::Format(e) => write!(f, "Format: {}", e),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<io::Error> for FormattingError {
|
|
fn from(e: io::Error) -> Self {
|
|
FormattingError::Format(e)
|
|
}
|
|
}
|
|
|
|
/// Generates a de-facto single level keymap.
|
|
// TODO: don't rely on keys and their order,
|
|
// but rather on what keysyms and keycodes are in use.
|
|
// Iterating actions makes it hard to deduplicate keysyms.
|
|
pub fn generate_keymap(
|
|
keystates: &HashMap::<String, KeyState>
|
|
) -> Result<String, FormattingError> {
|
|
let mut buf: Vec<u8> = Vec::new();
|
|
writeln!(
|
|
buf,
|
|
"xkb_keymap {{
|
|
|
|
xkb_keycodes \"squeekboard\" {{
|
|
minimum = 8;
|
|
maximum = 255;"
|
|
)?;
|
|
|
|
for (name, state) in keystates.iter() {
|
|
match &state.action {
|
|
Action::Submit { text: _, keys } => {
|
|
if let 0 = keys.len() {
|
|
log_print!(
|
|
logging::Level::Warning,
|
|
"Key {} has no keysyms", name,
|
|
);
|
|
};
|
|
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
|
|
write!(
|
|
buf,
|
|
"
|
|
<{}> = {};",
|
|
named_keysym.0,
|
|
keycode,
|
|
)?;
|
|
}
|
|
},
|
|
Action::Erase => {
|
|
let mut keycodes = state.keycodes.iter();
|
|
write!(
|
|
buf,
|
|
"
|
|
<BackSpace> = {};",
|
|
keycodes.next().expect("Erase key has no keycode"),
|
|
)?;
|
|
if let Some(_) = keycodes.next() {
|
|
log_print!(
|
|
logging::Level::Bug,
|
|
"Erase key has multiple keycodes",
|
|
);
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
|
|
writeln!(
|
|
buf,
|
|
"
|
|
}};
|
|
|
|
xkb_symbols \"squeekboard\" {{
|
|
|
|
name[Group1] = \"Letters\";
|
|
name[Group2] = \"Numbers/Symbols\";
|
|
|
|
key <BackSpace> {{ [ BackSpace ] }};"
|
|
)?;
|
|
|
|
for (name, state) in keystates.iter() {
|
|
if let Action::Submit { text: _, keys } = &state.action {
|
|
for keysym in keys.iter() {
|
|
write!(
|
|
buf,
|
|
"
|
|
key <{}> {{ [ {0} ] }};",
|
|
keysym.0,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
writeln!(
|
|
buf,
|
|
"
|
|
}};
|
|
|
|
xkb_types \"squeekboard\" {{
|
|
|
|
type \"TWO_LEVEL\" {{
|
|
modifiers = Shift;
|
|
map[Shift] = Level2;
|
|
level_name[Level1] = \"Base\";
|
|
level_name[Level2] = \"Shift\";
|
|
}};
|
|
}};
|
|
|
|
xkb_compatibility \"squeekboard\" {{
|
|
}};
|
|
}};"
|
|
)?;
|
|
|
|
//println!("{}", String::from_utf8(buf.clone()).unwrap());
|
|
String::from_utf8(buf).map_err(FormattingError::Utf)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use xkbcommon::xkb;
|
|
|
|
use ::action::KeySym;
|
|
|
|
#[test]
|
|
fn test_keymap_multi() {
|
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
|
|
|
let keymap_str = generate_keymap(&hashmap!{
|
|
"ac".into() => KeyState {
|
|
action: Action::Submit {
|
|
text: None,
|
|
keys: vec!(KeySym("a".into()), KeySym("c".into())),
|
|
},
|
|
keycodes: vec!(9, 10),
|
|
pressed: PressType::Released,
|
|
},
|
|
}).unwrap();
|
|
|
|
let keymap = xkb::Keymap::new_from_string(
|
|
&context,
|
|
keymap_str.clone(),
|
|
xkb::KEYMAP_FORMAT_TEXT_V1,
|
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
|
).expect("Failed to create keymap");
|
|
|
|
let state = xkb::State::new(&keymap);
|
|
|
|
assert_eq!(state.key_get_one_sym(9), xkb::KEY_a);
|
|
assert_eq!(state.key_get_one_sym(10), xkb::KEY_c);
|
|
}
|
|
}
|