From 2219eb67e124dc2cbfc31cdda25214617d0646f5 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Mon, 28 Sep 2020 17:52:00 +0000 Subject: [PATCH 1/3] keymap: Generate from symbol map, not layout Includes changes to the keymap string without which Xwayland won't work. --- src/data.rs | 17 +++---- src/keyboard.rs | 128 ++++++++++++++++++++---------------------------- 2 files changed, 60 insertions(+), 85 deletions(-) diff --git a/src/data.rs b/src/data.rs index c3cf5345..a0fa2a20 100644 --- a/src/data.rs +++ b/src/data.rs @@ -382,7 +382,7 @@ impl Layout { ) )}).collect(); - let keymap: HashMap = generate_keycodes( + let symbolmap: HashMap = generate_keycodes( button_actions.iter() .filter_map(|(_name, action)| { match action { @@ -399,20 +399,20 @@ impl Layout { let button_states = button_actions.into_iter().map(|(name, action)| { let keycodes = match &action { ::action::Action::Submit { text: _, keys } => { - keys.iter().map(|named_keycode| { - *keymap.get(named_keycode.0.as_str()) + keys.iter().map(|named_keysym| { + *symbolmap.get(named_keysym.0.as_str()) .expect( format!( - "keycode {} in key {} missing from keymap", - named_keycode.0, + "keysym {} in key {} missing from symbol map", + named_keysym.0, name ).as_str() ) }).collect() }, action::Action::Erase => vec![ - *keymap.get("BackSpace") - .expect(&format!("BackSpace missing from keymap")), + *symbolmap.get("BackSpace") + .expect(&format!("BackSpace missing from symbol map")), ], _ => Vec::new(), }; @@ -430,8 +430,7 @@ impl Layout { button_states ); - // TODO: generate from symbols - let keymap_str = match generate_keymap(&button_states) { + let keymap_str = match generate_keymap(symbolmap) { Err(e) => { return (Err(e), warning_handler) }, Ok(v) => v, }; diff --git a/src/keyboard.rs b/src/keyboard.rs index 749d21ad..8418a98e 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -9,7 +9,6 @@ use std::rc::Rc; use std::string::FromUtf8Error; use ::action::Action; -use ::logging; // Traits use std::io::Write; @@ -125,11 +124,9 @@ impl From for FormattingError { } /// 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. +/// Key codes must not repeat and should remain between 9 and 255. pub fn generate_keymap( - keystates: &HashMap:: + symbolmap: HashMap::, ) -> Result { let mut buf: Vec = Vec::new(); writeln!( @@ -138,88 +135,75 @@ pub fn generate_keymap( xkb_keycodes \"squeekboard\" {{ minimum = 8; - maximum = 255;" + maximum = 999;" )?; - 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, - " - = {};", - keycodes.next().expect("Erase key has no keycode"), - )?; - if let Some(_) = keycodes.next() { - log_print!( - logging::Level::Bug, - "Erase key has multiple keycodes", - ); - } - }, - _ => {}, - } + // Xorg can only consume up to 255 keys, so this may not work in Xwayland. + // Two possible solutions: + // - use levels to cram multiple characters into one key + // - swap layouts on key presses + for keycode in symbolmap.values() { + write!( + buf, + " + = {0};", + keycode, + )?; } - + writeln!( buf, " + indicator 1 = \"Caps Lock\"; // Xwayland won't accept without it. }}; xkb_symbols \"squeekboard\" {{ - - name[Group1] = \"Letters\"; - name[Group2] = \"Numbers/Symbols\"; - - key {{ [ 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, - )?; - } - } + for (name, keycode) in symbolmap.iter() { + write!( + buf, + " +key {{ [ {} ] }};", + keycode, + name, + )?; } + writeln!( buf, " }}; xkb_types \"squeekboard\" {{ + virtual_modifiers Squeekboard; // No modifiers! Needed for Xorg for some reason. + + // Those names are needed for Xwayland. + type \"ONE_LEVEL\" {{ + modifiers= none; + level_name[Level1]= \"Any\"; + }}; + type \"TWO_LEVEL\" {{ + level_name[Level1]= \"Base\"; + }}; + type \"ALPHABETIC\" {{ + level_name[Level1]= \"Base\"; + }}; + type \"KEYPAD\" {{ + level_name[Level1]= \"Base\"; + }}; + type \"SHIFT+ALT\" {{ + level_name[Level1]= \"Base\"; + }}; - type \"TWO_LEVEL\" {{ - modifiers = Shift; - map[Shift] = Level2; - level_name[Level1] = \"Base\"; - level_name[Level2] = \"Shift\"; - }}; }}; xkb_compatibility \"squeekboard\" {{ + // Needed for Xwayland again. + interpret Any+AnyOf(all) {{ + action= SetMods(modifiers=modMapMods,clearLocks); + }}; }}; }};" )?; @@ -234,21 +218,13 @@ mod tests { 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, - }, + let keymap_str = generate_keymap(hashmap!{ + "a".into() => 9, + "c".into() => 10, }).unwrap(); let keymap = xkb::Keymap::new_from_string( From de3bf54dc96851214160a92b229f65a56ddd07e4 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Mon, 28 Sep 2020 18:23:12 +0000 Subject: [PATCH 2/3] data: Restore testability of action->keysym conversion --- src/data.rs | 76 +++++++++++++++++++++++++++++++++++------ src/keyboard.rs | 2 +- tests/layout_erase.yaml | 10 ++++++ 3 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 tests/layout_erase.yaml diff --git a/src/data.rs b/src/data.rs index a0fa2a20..d8599fcb 100644 --- a/src/data.rs +++ b/src/data.rs @@ -383,17 +383,7 @@ impl Layout { )}).collect(); let symbolmap: HashMap = generate_keycodes( - button_actions.iter() - .filter_map(|(_name, action)| { - match action { - ::action::Action::Submit { - text: _, keys, - } => Some(keys), - _ => None, - } - }) - .flatten() - .map(|named_keysym| named_keysym.0.as_str()) + extract_symbol_names(&button_actions) ); let button_states = button_actions.into_iter().map(|(name, action)| { @@ -733,6 +723,22 @@ fn create_button( } } +fn extract_symbol_names<'a>(actions: &'a [(&str, action::Action)]) + -> impl Iterator +{ + actions.iter() + .filter_map(|(_name, act)| { + match act { + action::Action::Submit { + text: _, keys, + } => Some(keys), + _ => None, + } + }) + .flatten() + .map(|named_keysym| named_keysym.0.as_str()) +} + #[cfg(test)] mod tests { use super::*; @@ -861,6 +867,23 @@ mod tests { ); } + /// Test if erase yields a useable keycode + #[test] + fn test_layout_erase() { + let out = Layout::from_file(path_from_root("tests/layout_erase.yaml")) + .unwrap() + .build(ProblemPanic).0 + .unwrap(); + assert_eq!( + out.views["base"].1 + .get_rows()[0].1 + .buttons[0].1 + .state.borrow() + .keycodes.len(), + 1 + ); + } + #[test] fn parsing_fallback() { assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME) @@ -938,4 +961,35 @@ mod tests { } ); } + + #[test] + fn test_extract_symbols() { + let actions = [( + "ac", + action::Action::Submit { + text: None, + keys: vec![ + action::KeySym("a".into()), + action::KeySym("c".into()), + ], + }, + )]; + assert_eq!( + extract_symbol_names(&actions[..]).collect::>(), + vec!["a", "c"], + ); + } + + #[test] + fn test_extract_symbols_erase() { + let actions = [( + "Erase", + action::Action::Erase, + )]; + assert_eq!( + extract_symbol_names(&actions[..]).collect::>(), + Vec::<&str>::new(), //"BackSpace"], // TODO: centralize handling of BackSpace + ); + } + } diff --git a/src/keyboard.rs b/src/keyboard.rs index 8418a98e..7194c465 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -91,7 +91,7 @@ fn sorted<'a, I: Iterator>( /// HACK: starting from 9, because 8 results in keycode 0, /// which the compositor likes to discard pub fn generate_keycodes<'a, C: IntoIterator>( - key_names: C + key_names: C, ) -> HashMap { let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s); HashMap::from_iter( diff --git a/tests/layout_erase.yaml b/tests/layout_erase.yaml new file mode 100644 index 00000000..3039b3f6 --- /dev/null +++ b/tests/layout_erase.yaml @@ -0,0 +1,10 @@ +--- +# Erase only +views: + base: + - "BackSpace" +outlines: + default: { width: 0, height: 0 } +buttons: + BackSpace: + action: erase From 8cf6c5f94836db866d9a64a8391eaf8a59bfa633 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Thu, 1 Oct 2020 14:12:15 +0000 Subject: [PATCH 3/3] syntax: Let older rustc understand symbolmap's lifetime --- src/data.rs | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/data.rs b/src/data.rs index d8599fcb..a08f2bdb 100644 --- a/src/data.rs +++ b/src/data.rs @@ -386,38 +386,36 @@ impl Layout { extract_symbol_names(&button_actions) ); - let button_states = button_actions.into_iter().map(|(name, action)| { - let keycodes = match &action { - ::action::Action::Submit { text: _, keys } => { - keys.iter().map(|named_keysym| { - *symbolmap.get(named_keysym.0.as_str()) - .expect( - format!( - "keysym {} in key {} missing from symbol map", - named_keysym.0, - name - ).as_str() - ) - }).collect() - }, - action::Action::Erase => vec![ - *symbolmap.get("BackSpace") - .expect(&format!("BackSpace missing from symbol map")), - ], - _ => Vec::new(), - }; - ( - name.into(), - KeyState { - pressed: PressType::Released, - keycodes, - action, - } - ) - }); - let button_states = HashMap::::from_iter( - button_states + button_actions.into_iter().map(|(name, action)| { + let keycodes = match &action { + ::action::Action::Submit { text: _, keys } => { + keys.iter().map(|named_keysym| { + *symbolmap.get(named_keysym.0.as_str()) + .expect( + format!( + "keysym {} in key {} missing from symbol map", + named_keysym.0, + name + ).as_str() + ) + }).collect() + }, + action::Action::Erase => vec![ + *symbolmap.get("BackSpace") + .expect(&format!("BackSpace missing from symbol map")), + ], + _ => Vec::new(), + }; + ( + name.into(), + KeyState { + pressed: PressType::Released, + keycodes, + action, + } + ) + }) ); let keymap_str = match generate_keymap(symbolmap) {