virtual_keyboard: Submit multi-codepoint strings
This commit is contained in:
149
src/data.rs
149
src/data.rs
@ -131,11 +131,15 @@ fn load_layout(
|
|||||||
fn log_attempt_info(attempt: Option<(LoadError, DataSource)>) {
|
fn log_attempt_info(attempt: Option<(LoadError, DataSource)>) {
|
||||||
match attempt {
|
match attempt {
|
||||||
Some((
|
Some((
|
||||||
LoadError::BadData(Error::Missing(_e)),
|
LoadError::BadData(Error::Missing(e)),
|
||||||
DataSource::File(_file)
|
DataSource::File(file)
|
||||||
)) => {
|
)) => {
|
||||||
|
eprintln!(
|
||||||
|
"Tried file {:?}, but it's missing: {}",
|
||||||
|
file, e
|
||||||
|
);
|
||||||
// Missing file, not to worry. TODO: print in debug logging level
|
// Missing file, not to worry. TODO: print in debug logging level
|
||||||
}
|
},
|
||||||
Some((e, source)) => {
|
Some((e, source)) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Failed to load layout from {}: {}, trying builtin",
|
"Failed to load layout from {}: {}, trying builtin",
|
||||||
@ -300,32 +304,56 @@ impl Layout {
|
|||||||
let button_names: HashSet<&str>
|
let button_names: HashSet<&str>
|
||||||
= HashSet::from_iter(button_names);
|
= HashSet::from_iter(button_names);
|
||||||
|
|
||||||
let keycodes = generate_keycodes(
|
let button_actions: Vec<(&str, ::action::Action)>
|
||||||
button_names.iter()
|
= button_names.iter().map(|name| {(
|
||||||
.map(|name| *name)
|
*name,
|
||||||
.filter(|name| {
|
create_action(
|
||||||
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(
|
|
||||||
&self.buttons,
|
&self.buttons,
|
||||||
name,
|
name,
|
||||||
self.views.keys().collect()
|
self.views.keys().collect()
|
||||||
),
|
)
|
||||||
}))
|
)}).collect();
|
||||||
)});
|
|
||||||
|
let keymap: HashMap<String, u32> = 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 =
|
let button_states =
|
||||||
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
|
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
|
||||||
@ -410,33 +438,45 @@ fn create_action(
|
|||||||
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
|
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
let keysym = match &symbol_meta.action {
|
let keysyms = match &symbol_meta.action {
|
||||||
Some(_) => None,
|
// Non-submit action
|
||||||
None => Some(match &symbol_meta.keysym {
|
Some(_) => Vec::new(),
|
||||||
Some(keysym) => match keysym_valid(keysym.as_str()) {
|
// Submit action
|
||||||
|
None => match &symbol_meta.keysym {
|
||||||
|
// Keysym given explicitly
|
||||||
|
Some(keysym) => vec!(match keysym_valid(keysym.as_str()) {
|
||||||
true => keysym.clone(),
|
true => keysym.clone(),
|
||||||
false => {
|
false => {
|
||||||
eprintln!("Keysym name invalid: {}", keysym);
|
eprintln!("Keysym name invalid: {}", keysym);
|
||||||
"space".into() // placeholder
|
"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) {
|
None => match keysym_valid(name) {
|
||||||
true => String::from(name),
|
// Button name is actually a valid xkb name
|
||||||
false => match name.chars().count() {
|
true => vec!(String::from(name)),
|
||||||
1 => format!("U{:04X}", name.chars().next().unwrap() as u32),
|
// Button name is not a valid xkb name,
|
||||||
// If the name is longer than 1 char,
|
// so assume it's a literal string to be submitted
|
||||||
// then it's not a single Unicode char,
|
false => {
|
||||||
// but was trying to be an identifier
|
if name.chars().count() == 0 {
|
||||||
_ => {
|
// A name read from yaml with no valid Unicode.
|
||||||
eprintln!(
|
// Highly improbable, but let's be safe.
|
||||||
"Could not derive a valid keysym for key {}",
|
eprintln!("Key {} doesn't have any characters", name);
|
||||||
name
|
vec!("space".into()) // placeholder
|
||||||
);
|
} else {
|
||||||
"space".into() // placeholder
|
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 {
|
match &symbol_meta.action {
|
||||||
@ -459,9 +499,7 @@ fn create_action(
|
|||||||
},
|
},
|
||||||
None => ::action::Action::Submit {
|
None => ::action::Action::Submit {
|
||||||
text: None,
|
text: None,
|
||||||
keys: vec!(
|
keys: keysyms.into_iter().map(::action::KeySym).collect(),
|
||||||
::action::KeySym(keysym.unwrap()),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -633,6 +671,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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]
|
#[test]
|
||||||
fn parsing_fallback() {
|
fn parsing_fallback() {
|
||||||
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
||||||
|
|||||||
@ -78,12 +78,30 @@ pub mod c {
|
|||||||
let mut key = key.borrow_mut();
|
let mut key = key.borrow_mut();
|
||||||
key.pressed = press != 0;
|
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;
|
let keycode = keycode - 8;
|
||||||
unsafe {
|
match (key.pressed, keycodes_count) {
|
||||||
eek_virtual_keyboard_v1_key(
|
// Pressing a key made out of a single keycode is simple:
|
||||||
virtual_keyboard, timestamp, keycode, press
|
// 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 struct KeyState {
|
||||||
pub pressed: bool,
|
pub pressed: bool,
|
||||||
pub locked: bool,
|
pub locked: bool,
|
||||||
pub keycode: Option<u32>,
|
/// A cache of raw keycodes derived from Action::Sumbit given a keymap
|
||||||
|
pub keycodes: Vec<u32>,
|
||||||
/// Static description of what the key does when pressed or released
|
/// Static description of what the key does when pressed or released
|
||||||
pub action: Action,
|
pub action: Action,
|
||||||
}
|
}
|
||||||
@ -147,22 +166,16 @@ pub fn generate_keymap(
|
|||||||
for (name, state) in keystates.iter() {
|
for (name, state) in keystates.iter() {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
if let Action::Submit { text: _, keys } = &state.action {
|
if let Action::Submit { text: _, keys } = &state.action {
|
||||||
match keys.len() {
|
if let 0 = keys.len() { eprintln!("Key {} has no keysyms", name); };
|
||||||
0 => eprintln!("Key {} has no keysyms", name),
|
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
|
||||||
a => {
|
write!(
|
||||||
// TODO: don't ignore any keysyms
|
buf,
|
||||||
if a > 1 {
|
"
|
||||||
eprintln!("Key {} multiple keysyms", name);
|
|
||||||
}
|
|
||||||
write!(
|
|
||||||
buf,
|
|
||||||
"
|
|
||||||
<{}> = {};",
|
<{}> = {};",
|
||||||
keys[0].0,
|
named_keysym.0,
|
||||||
state.keycode.unwrap()
|
keycode,
|
||||||
)?;
|
)?;
|
||||||
},
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +192,7 @@ pub fn generate_keymap(
|
|||||||
|
|
||||||
for (name, state) in keystates.iter() {
|
for (name, state) in keystates.iter() {
|
||||||
if let Action::Submit { text: _, keys } = &state.borrow().action {
|
if let Action::Submit { text: _, keys } = &state.borrow().action {
|
||||||
if let Some(keysym) = keys.iter().next() {
|
for keysym in keys.iter() {
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"
|
"
|
||||||
@ -209,5 +222,6 @@ pub fn generate_keymap(
|
|||||||
}};"
|
}};"
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
//println!("{}", String::from_utf8(buf.clone()).unwrap());
|
||||||
String::from_utf8(buf).map_err(FormattingError::Utf)
|
String::from_utf8(buf).map_err(FormattingError::Utf)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -484,7 +484,7 @@ pub mod c {
|
|||||||
Rc::new(RefCell::new(::keyboard::KeyState {
|
Rc::new(RefCell::new(::keyboard::KeyState {
|
||||||
pressed: false,
|
pressed: false,
|
||||||
locked: false,
|
locked: false,
|
||||||
keycode: None,
|
keycodes: Vec::new(),
|
||||||
action: Action::SetLevel("default".into()),
|
action: Action::SetLevel("default".into()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
17
tests/layout_key3.yaml
Normal file
17
tests/layout_key3.yaml
Normal file
@ -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 }
|
||||||
|
|
||||||
Reference in New Issue
Block a user