diff --git a/src/data.rs b/src/data.rs index 98266c90..44cf2268 100644 --- a/src/data.rs +++ b/src/data.rs @@ -131,11 +131,15 @@ fn load_layout( fn log_attempt_info(attempt: Option<(LoadError, DataSource)>) { match attempt { Some(( - LoadError::BadData(Error::Missing(_e)), - DataSource::File(_file) + LoadError::BadData(Error::Missing(e)), + DataSource::File(file) )) => { + eprintln!( + "Tried file {:?}, but it's missing: {}", + file, e + ); // Missing file, not to worry. TODO: print in debug logging level - } + }, Some((e, source)) => { eprintln!( "Failed to load layout from {}: {}, trying builtin", @@ -300,32 +304,56 @@ impl Layout { let button_names: HashSet<&str> = HashSet::from_iter(button_names); - let keycodes = generate_keycodes( - button_names.iter() - .map(|name| *name) - .filter(|name| { - match self.buttons.get(*name) { - // buttons with defined action can't emit keysyms - // and so don't need keycodes - Some(ButtonMeta { action: Some(_), .. }) => false, - _ => true, - } - }) - ); - - let button_states = button_names.iter().map(|name| {( - String::from(*name), - Rc::new(RefCell::new(KeyState { - pressed: false, - locked: false, - keycode: keycodes.get(*name).map(|k| *k), - action: create_action( + let button_actions: Vec<(&str, ::action::Action)> + = button_names.iter().map(|name| {( + *name, + create_action( &self.buttons, name, self.views.keys().collect() - ), - })) - )}); + ) + )}).collect(); + + let keymap: 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()) + ); + + 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()) + .expect( + format!( + "keycode {} in key {} missing from keymap", + named_keycode.0, + name + ).as_str() + ) + }).collect() + }, + _ => Vec::new(), + }; + ( + name.into(), + Rc::new(RefCell::new(KeyState { + pressed: false, + locked: false, + keycodes, + action, + })) + ) + }); let button_states = HashMap::>>::from_iter( @@ -410,33 +438,45 @@ fn create_action( xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol } - let keysym = match &symbol_meta.action { - Some(_) => None, - None => Some(match &symbol_meta.keysym { - Some(keysym) => match keysym_valid(keysym.as_str()) { + let keysyms = match &symbol_meta.action { + // Non-submit action + Some(_) => Vec::new(), + // Submit action + None => match &symbol_meta.keysym { + // Keysym given explicitly + Some(keysym) => vec!(match keysym_valid(keysym.as_str()) { true => keysym.clone(), false => { eprintln!("Keysym name invalid: {}", keysym); "space".into() // placeholder }, - }, + }), + // Keysyms left open to derive + // TODO: when button name is meant diretly as xkb keysym name, + // mark it so, e.g. with a "#" None => match keysym_valid(name) { - true => String::from(name), - false => match name.chars().count() { - 1 => format!("U{:04X}", name.chars().next().unwrap() as u32), - // If the name is longer than 1 char, - // then it's not a single Unicode char, - // but was trying to be an identifier - _ => { - eprintln!( - "Could not derive a valid keysym for key {}", - name - ); - "space".into() // placeholder + // Button name is actually a valid xkb name + true => vec!(String::from(name)), + // Button name is not a valid xkb name, + // so assume it's a literal string to be submitted + false => { + if name.chars().count() == 0 { + // A name read from yaml with no valid Unicode. + // Highly improbable, but let's be safe. + eprintln!("Key {} doesn't have any characters", name); + vec!("space".into()) // placeholder + } else { + name.chars().map(|codepoint| { + let codepoint_string = codepoint.to_string(); + match keysym_valid(codepoint_string.as_str()) { + true => codepoint_string, + false => format!("U{:04X}", codepoint as u32), + } + }).collect() } }, }, - }), + }, }; match &symbol_meta.action { @@ -459,9 +499,7 @@ fn create_action( }, None => ::action::Action::Submit { text: None, - keys: vec!( - ::action::KeySym(keysym.unwrap()), - ), + keys: keysyms.into_iter().map(::action::KeySym).collect(), }, } } @@ -632,7 +670,24 @@ mod tests { ::layout::Label::Text(CString::new("test").unwrap()) ); } - + + /// Test multiple codepoints + #[test] + fn test_layout_unicode_multi() { + let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml")) + .unwrap() + .build() + .unwrap(); + assert_eq!( + out.views["base"] + .rows[0] + .buttons[0] + .state.borrow() + .keycodes.len(), + 2 + ); + } + #[test] fn parsing_fallback() { assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME) diff --git a/src/keyboard.rs b/src/keyboard.rs index 025f9563..1041b8fb 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -78,12 +78,30 @@ pub mod c { let mut key = key.borrow_mut(); key.pressed = press != 0; - if let Some(keycode) = key.keycode { + let keycodes_count = key.keycodes.len(); + for keycode in key.keycodes.iter() { let keycode = keycode - 8; - unsafe { - eek_virtual_keyboard_v1_key( - virtual_keyboard, timestamp, keycode, press - ); + match (key.pressed, keycodes_count) { + // Pressing a key made out of a single keycode is simple: + // press on press, release on release. + (_, 1) => unsafe { + eek_virtual_keyboard_v1_key( + virtual_keyboard, timestamp, keycode, press + ); + }, + // A key made of multiple keycodes + // has to submit them one after the other + (true, _) => unsafe { + eek_virtual_keyboard_v1_key( + virtual_keyboard, timestamp, keycode, 1 + ); + eek_virtual_keyboard_v1_key( + virtual_keyboard, timestamp, keycode, 0 + ); + }, + // Design choice here: submit multiple all at press time + // and do nothing at release time + (false, _) => {}, } } } @@ -93,7 +111,8 @@ pub mod c { pub struct KeyState { pub pressed: bool, pub locked: bool, - pub keycode: Option, + /// A cache of raw keycodes derived from Action::Sumbit given a keymap + pub keycodes: Vec, /// Static description of what the key does when pressed or released pub action: Action, } @@ -147,22 +166,16 @@ pub fn generate_keymap( for (name, state) in keystates.iter() { let state = state.borrow(); if let Action::Submit { text: _, keys } = &state.action { - match keys.len() { - 0 => eprintln!("Key {} has no keysyms", name), - a => { - // TODO: don't ignore any keysyms - if a > 1 { - eprintln!("Key {} multiple keysyms", name); - } - write!( - buf, - " + if let 0 = keys.len() { eprintln!("Key {} has no keysyms", name); }; + for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) { + write!( + buf, + " <{}> = {};", - keys[0].0, - state.keycode.unwrap() - )?; - }, - }; + named_keysym.0, + keycode, + )?; + } } } @@ -179,7 +192,7 @@ pub fn generate_keymap( for (name, state) in keystates.iter() { if let Action::Submit { text: _, keys } = &state.borrow().action { - if let Some(keysym) = keys.iter().next() { + for keysym in keys.iter() { write!( buf, " @@ -209,5 +222,6 @@ pub fn generate_keymap( }};" )?; + //println!("{}", String::from_utf8(buf.clone()).unwrap()); String::from_utf8(buf).map_err(FormattingError::Utf) } diff --git a/src/layout.rs b/src/layout.rs index bbbf3263..a37b8875 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -484,7 +484,7 @@ pub mod c { Rc::new(RefCell::new(::keyboard::KeyState { pressed: false, locked: false, - keycode: None, + keycodes: Vec::new(), action: Action::SetLevel("default".into()), })) } diff --git a/tests/layout_key3.yaml b/tests/layout_key3.yaml new file mode 100644 index 00000000..7336830c --- /dev/null +++ b/tests/layout_key3.yaml @@ -0,0 +1,17 @@ +--- +# punctuation +row_spacing: 0 +button_spacing: 0 + +bounds: + x: 0 + y: 0 + width: 0 + height: 0 +views: + base: + - "か゚" # 2 codepoints +outlines: + default: + bounds: { x: 0, y: 0, width: 0, height: 0 } +