Merge branch 'multicodepoint' into 'master'
Support submitting multi-codepoint sequences Closes #96 See merge request Librem5/squeekboard!207
This commit is contained in:
@ -31,6 +31,7 @@
|
|||||||
#include <glib/gprintf.h>
|
#include <glib/gprintf.h>
|
||||||
|
|
||||||
#include "eek-enumtypes.h"
|
#include "eek-enumtypes.h"
|
||||||
|
#include "eekboard/eekboard-context-service.h"
|
||||||
#include "eekboard/key-emitter.h"
|
#include "eekboard/key-emitter.h"
|
||||||
#include "keymap.h"
|
#include "keymap.h"
|
||||||
#include "src/keyboard.h"
|
#include "src/keyboard.h"
|
||||||
@ -93,17 +94,8 @@ set_level_from_press (LevelKeyboard *keyboard, struct squeek_key *key)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void eek_keyboard_press_key(LevelKeyboard *keyboard, struct squeek_key *key, guint32 timestamp) {
|
void eek_keyboard_press_key(LevelKeyboard *keyboard, struct squeek_key *key, guint32 timestamp) {
|
||||||
squeek_key_set_pressed(key, TRUE);
|
|
||||||
keyboard->pressed_keys = g_list_prepend (keyboard->pressed_keys, key);
|
keyboard->pressed_keys = g_list_prepend (keyboard->pressed_keys, key);
|
||||||
|
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_PRESS, timestamp);
|
||||||
// Only take action about setting level *after* the key has taken effect, i.e. on release
|
|
||||||
//set_level_from_press (keyboard, key);
|
|
||||||
|
|
||||||
// "Borrowed" from eek-context-service; doesn't influence the state but forwards the event
|
|
||||||
|
|
||||||
guint keycode = squeek_key_get_keycode (key);
|
|
||||||
|
|
||||||
emit_key_activated(keyboard->manager, keyboard, keycode, TRUE, timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void eek_keyboard_release_key(LevelKeyboard *keyboard,
|
void eek_keyboard_release_key(LevelKeyboard *keyboard,
|
||||||
@ -118,12 +110,7 @@ void eek_keyboard_release_key(LevelKeyboard *keyboard,
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_level_from_press (keyboard, key);
|
set_level_from_press (keyboard, key);
|
||||||
|
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_RELEASE, timestamp);
|
||||||
// "Borrowed" from eek-context-service; doesn't influence the state but forwards the event
|
|
||||||
|
|
||||||
guint keycode = squeek_key_get_keycode (key);
|
|
||||||
|
|
||||||
emit_key_activated(keyboard->manager, keyboard, keycode, FALSE, timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void level_keyboard_deinit(LevelKeyboard *self) {
|
void level_keyboard_deinit(LevelKeyboard *self) {
|
||||||
|
|||||||
@ -193,12 +193,7 @@ static void render_button_in_context(EekRenderer *self,
|
|||||||
/* render outline */
|
/* render outline */
|
||||||
EekBounds bounds = squeek_button_get_bounds(place->button);
|
EekBounds bounds = squeek_button_get_bounds(place->button);
|
||||||
|
|
||||||
if (active)
|
outline_surface = NULL;
|
||||||
outline_surface_cache = priv->active_outline_surface_cache;
|
|
||||||
else
|
|
||||||
outline_surface_cache = priv->outline_surface_cache;
|
|
||||||
|
|
||||||
outline_surface = g_hash_table_lookup (outline_surface_cache, place->button);
|
|
||||||
|
|
||||||
if (!outline_surface) {
|
if (!outline_surface) {
|
||||||
cairo_t *cr;
|
cairo_t *cr;
|
||||||
@ -221,10 +216,6 @@ static void render_button_in_context(EekRenderer *self,
|
|||||||
cairo_restore (cr);
|
cairo_restore (cr);
|
||||||
|
|
||||||
cairo_destroy (cr);
|
cairo_destroy (cr);
|
||||||
|
|
||||||
g_hash_table_insert (outline_surface_cache,
|
|
||||||
(gpointer)place->button,
|
|
||||||
outline_surface);
|
|
||||||
}
|
}
|
||||||
cairo_set_source_surface (cr, outline_surface, 0.0, 0.0);
|
cairo_set_source_surface (cr, outline_surface, 0.0, 0.0);
|
||||||
cairo_paint (cr);
|
cairo_paint (cr);
|
||||||
|
|||||||
@ -131,7 +131,6 @@ emit_key_activated (EekboardContextService *manager,
|
|||||||
*/
|
*/
|
||||||
SeatEmitter emitter = {0};
|
SeatEmitter emitter = {0};
|
||||||
emitter.virtual_keyboard = manager->virtual_keyboard;
|
emitter.virtual_keyboard = manager->virtual_keyboard;
|
||||||
emitter.keymap = keyboard->keymap;
|
|
||||||
update_modifier_info (&emitter);
|
update_modifier_info (&emitter);
|
||||||
send_fake_key (&emitter, keyboard, keycode, pressed, timestamp);
|
send_fake_key (&emitter, keyboard, keycode, pressed, timestamp);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,14 +14,37 @@ fn check_layout(name: &str) {
|
|||||||
.expect("layout broken");
|
.expect("layout broken");
|
||||||
|
|
||||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
xkb::Keymap::new_from_string(
|
|
||||||
&context,
|
let keymap_str = layout.keymap_str
|
||||||
layout.keymap_str
|
|
||||||
.clone()
|
.clone()
|
||||||
.into_string().expect("Failed to decode keymap string"),
|
.into_string().expect("Failed to decode keymap string");
|
||||||
|
|
||||||
|
let keymap = xkb::Keymap::new_from_string(
|
||||||
|
&context,
|
||||||
|
keymap_str.clone(),
|
||||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
).expect("Failed to create keymap");
|
).expect("Failed to create keymap");
|
||||||
|
|
||||||
|
let state = xkb::State::new(&keymap);
|
||||||
|
|
||||||
|
// "Press" each button with keysyms
|
||||||
|
for view in layout.views.values() {
|
||||||
|
for row in &view.rows {
|
||||||
|
for button in &row.buttons {
|
||||||
|
let keystate = button.state.borrow();
|
||||||
|
for keycode in &keystate.keycodes {
|
||||||
|
match state.key_get_one_sym(*keycode) {
|
||||||
|
xkb::KEY_NoSymbol => {
|
||||||
|
eprintln!("{}", keymap_str);
|
||||||
|
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> () {
|
fn main() -> () {
|
||||||
|
|||||||
@ -37,10 +37,3 @@ pub enum Action {
|
|||||||
keys: Vec<KeySym>,
|
keys: Vec<KeySym>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains a static description of a particular key's actions
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Symbol {
|
|
||||||
/// The action that this key performs
|
|
||||||
pub action: Action,
|
|
||||||
}
|
|
||||||
196
src/data.rs
196
src/data.rs
@ -19,6 +19,7 @@ use ::keyboard::{
|
|||||||
};
|
};
|
||||||
use ::resources;
|
use ::resources;
|
||||||
use ::util::c::as_str;
|
use ::util::c::as_str;
|
||||||
|
use ::util::hash_map_map;
|
||||||
use ::xdg;
|
use ::xdg;
|
||||||
|
|
||||||
// traits, derives
|
// traits, derives
|
||||||
@ -131,11 +132,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,41 +305,73 @@ 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),
|
|
||||||
symbol: create_symbol(
|
|
||||||
&self.buttons,
|
&self.buttons,
|
||||||
name,
|
name,
|
||||||
self.views.keys().collect()
|
self.views.keys().collect()
|
||||||
),
|
)
|
||||||
}))
|
)}).collect();
|
||||||
)});
|
|
||||||
|
|
||||||
let button_states =
|
let keymap: HashMap<String, u32> = generate_keycodes(
|
||||||
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
|
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(),
|
||||||
|
KeyState {
|
||||||
|
pressed: false,
|
||||||
|
locked: false,
|
||||||
|
keycodes,
|
||||||
|
action,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let button_states
|
||||||
|
= HashMap::<String, KeyState>::from_iter(
|
||||||
button_states
|
button_states
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: generate from symbols
|
// TODO: generate from symbols
|
||||||
let keymap_str = generate_keymap(&button_states)?;
|
let keymap_str = generate_keymap(&button_states)?;
|
||||||
|
|
||||||
|
let button_states_cache = hash_map_map(
|
||||||
|
button_states,
|
||||||
|
|name, state| {(
|
||||||
|
name,
|
||||||
|
Rc::new(RefCell::new(state))
|
||||||
|
)}
|
||||||
|
);
|
||||||
|
|
||||||
let views = HashMap::from_iter(
|
let views = HashMap::from_iter(
|
||||||
self.views.iter().map(|(name, view)| {(
|
self.views.iter().map(|(name, view)| {(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
@ -358,7 +395,7 @@ impl Layout {
|
|||||||
&self.buttons,
|
&self.buttons,
|
||||||
&self.outlines,
|
&self.outlines,
|
||||||
name,
|
name,
|
||||||
button_states.get(name.into())
|
button_states_cache.get(name.into())
|
||||||
.expect("Button state not created")
|
.expect("Button state not created")
|
||||||
.clone()
|
.clone()
|
||||||
))
|
))
|
||||||
@ -380,11 +417,11 @@ impl Layout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_symbol(
|
fn create_action(
|
||||||
button_info: &HashMap<String, ButtonMeta>,
|
button_info: &HashMap<String, ButtonMeta>,
|
||||||
name: &str,
|
name: &str,
|
||||||
view_names: Vec<&String>,
|
view_names: Vec<&String>,
|
||||||
) -> ::symbol::Symbol {
|
) -> ::action::Action {
|
||||||
let default_meta = ButtonMeta::default();
|
let default_meta = ButtonMeta::default();
|
||||||
let symbol_meta = button_info.get(name)
|
let symbol_meta = button_info.get(name)
|
||||||
.unwrap_or(&default_meta);
|
.unwrap_or(&default_meta);
|
||||||
@ -410,43 +447,54 @@ fn create_symbol(
|
|||||||
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 {
|
||||||
Some(Action::SetView(view_name)) => ::symbol::Symbol {
|
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
|
||||||
action: ::symbol::Action::SetLevel(
|
|
||||||
filter_view_name(name, view_name.clone(), &view_names)
|
filter_view_name(name, view_name.clone(), &view_names)
|
||||||
),
|
),
|
||||||
},
|
Some(Action::Locking {
|
||||||
Some(Action::Locking { lock_view, unlock_view }) => ::symbol::Symbol {
|
lock_view, unlock_view
|
||||||
action: ::symbol::Action::LockLevel {
|
}) => ::action::Action::LockLevel {
|
||||||
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
||||||
unlock: filter_view_name(
|
unlock: filter_view_name(
|
||||||
name,
|
name,
|
||||||
@ -454,20 +502,13 @@ fn create_symbol(
|
|||||||
&view_names
|
&view_names
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
Some(Action::ShowPrefs) => ::action::Action::Submit {
|
||||||
Some(Action::ShowPrefs) => ::symbol::Symbol {
|
|
||||||
action: ::symbol::Action::Submit {
|
|
||||||
text: None,
|
text: None,
|
||||||
keys: Vec::new(),
|
keys: Vec::new(),
|
||||||
},
|
},
|
||||||
},
|
None => ::action::Action::Submit {
|
||||||
None => ::symbol::Symbol {
|
|
||||||
action: ::symbol::Action::Submit {
|
|
||||||
text: None,
|
text: None,
|
||||||
keys: vec!(
|
keys: keysyms.into_iter().map(::action::KeySym).collect(),
|
||||||
::symbol::KeySym(keysym.unwrap()),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,6 +680,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)
|
||||||
@ -650,7 +708,7 @@ mod tests {
|
|||||||
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
|
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
|
||||||
#[test]
|
#[test]
|
||||||
fn fallbacks_order() {
|
fn fallbacks_order() {
|
||||||
let (layout, source, _failure) = load_layout(
|
let (_layout, source, _failure) = load_layout(
|
||||||
"nb",
|
"nb",
|
||||||
Some(PathBuf::from("tests"))
|
Some(PathBuf::from("tests"))
|
||||||
);
|
);
|
||||||
@ -674,7 +732,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_key_unicode() {
|
fn test_key_unicode() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_symbol(
|
create_action(
|
||||||
&hashmap!{
|
&hashmap!{
|
||||||
".".into() => ButtonMeta {
|
".".into() => ButtonMeta {
|
||||||
icon: None,
|
icon: None,
|
||||||
@ -687,11 +745,9 @@ mod tests {
|
|||||||
".",
|
".",
|
||||||
Vec::new()
|
Vec::new()
|
||||||
),
|
),
|
||||||
::symbol::Symbol {
|
::action::Action::Submit {
|
||||||
action: ::symbol::Action::Submit {
|
|
||||||
text: None,
|
text: None,
|
||||||
keys: vec!(::symbol::KeySym("U002E".into())),
|
keys: vec!(::action::KeySym("U002E".into())),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,21 @@
|
|||||||
#ifndef __KEYBOARD_H
|
#ifndef __KEYBOARD_H
|
||||||
#define __KEYBOARD_H
|
#define __KEYBOARD_H
|
||||||
|
|
||||||
#include "stdbool.h"
|
|
||||||
#include "inttypes.h"
|
#include "inttypes.h"
|
||||||
|
#include "stdbool.h"
|
||||||
|
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
struct squeek_key;
|
struct squeek_key;
|
||||||
|
|
||||||
struct squeek_key *squeek_key_new(uint32_t keycode);
|
|
||||||
void squeek_key_free(struct squeek_key *key);
|
|
||||||
void squeek_key_add_symbol(struct squeek_key* key,
|
|
||||||
const char *element_name,
|
|
||||||
const char *text, uint32_t keyval,
|
|
||||||
const char *label, const char *icon,
|
|
||||||
const char *tooltip);
|
|
||||||
uint32_t squeek_key_is_pressed(struct squeek_key *key);
|
uint32_t squeek_key_is_pressed(struct squeek_key *key);
|
||||||
void squeek_key_set_pressed(struct squeek_key *key, uint32_t pressed);
|
|
||||||
uint32_t squeek_key_is_locked(struct squeek_key *key);
|
uint32_t squeek_key_is_locked(struct squeek_key *key);
|
||||||
void squeek_key_set_locked(struct squeek_key *key, uint32_t pressed);
|
void squeek_key_set_locked(struct squeek_key *key, uint32_t pressed);
|
||||||
uint32_t squeek_key_get_keycode(struct squeek_key *key);
|
|
||||||
void squeek_key_set_keycode(struct squeek_key *key, uint32_t keycode);
|
|
||||||
uint32_t squeek_key_equal(struct squeek_key* key, struct squeek_key* key1);
|
uint32_t squeek_key_equal(struct squeek_key* key, struct squeek_key* key1);
|
||||||
|
|
||||||
struct squeek_symbol *squeek_key_get_symbol(struct squeek_key* key);
|
enum key_press {
|
||||||
const char* squeek_key_to_keymap_entry(const char *key_name, struct squeek_key *key);
|
KEY_RELEASE = 0,
|
||||||
|
KEY_PRESS = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
void squeek_key_press(struct squeek_key *key, struct zwp_virtual_keyboard_v1*, enum key_press press, uint32_t timestamp);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
194
src/keyboard.rs
194
src/keyboard.rs
@ -1,13 +1,12 @@
|
|||||||
/*! State of the emulated keyboard and keys */
|
/*! State of the emulated keyboard and keys */
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
use ::symbol::{ Symbol, Action };
|
use ::action::Action;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::iter::{ FromIterator, IntoIterator };
|
use std::iter::{ FromIterator, IntoIterator };
|
||||||
@ -16,21 +15,28 @@ use std::iter::{ FromIterator, IntoIterator };
|
|||||||
pub mod c {
|
pub mod c {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ::util::c;
|
use ::util::c;
|
||||||
use ::util::c::as_cstr;
|
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::os::raw::c_void;
|
||||||
use std::os::raw::c_char;
|
|
||||||
|
|
||||||
pub type CKeyState = c::Wrapped<KeyState>;
|
pub type CKeyState = c::Wrapped<KeyState>;
|
||||||
|
|
||||||
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
|
#[repr(transparent)]
|
||||||
|
pub struct ZwpVirtualKeyboardV1(*const c_void);
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
extern "C" {
|
||||||
fn squeek_key_free(key: CKeyState) {
|
/// Checks if point falls within bounds,
|
||||||
unsafe { key.unwrap() }; // reference dropped
|
/// which are relative to origin and rotated by angle (I think)
|
||||||
|
pub fn eek_virtual_keyboard_v1_key(
|
||||||
|
virtual_keyboard: *mut ZwpVirtualKeyboardV1,
|
||||||
|
timestamp: u32,
|
||||||
|
keycode: u32,
|
||||||
|
press: u32,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
|
||||||
|
|
||||||
/// Compares pointers to the data
|
/// Compares pointers to the data
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
@ -45,14 +51,6 @@ pub mod c {
|
|||||||
return key.to_owned().pressed as u32;
|
return key.to_owned().pressed as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C"
|
|
||||||
fn squeek_key_set_pressed(key: CKeyState, pressed: u32) {
|
|
||||||
let key = key.clone_ref();
|
|
||||||
let mut key = key.borrow_mut();
|
|
||||||
key.pressed = pressed != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
fn squeek_key_is_locked(key: CKeyState) -> u32 {
|
fn squeek_key_is_locked(key: CKeyState) -> u32 {
|
||||||
@ -69,70 +67,42 @@ pub mod c {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
fn squeek_key_get_keycode(key: CKeyState) -> u32 {
|
fn squeek_key_press(
|
||||||
return key.to_owned().keycode.unwrap_or(0u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C"
|
|
||||||
fn squeek_key_to_keymap_entry(
|
|
||||||
key_name: *const c_char,
|
|
||||||
key: CKeyState,
|
key: CKeyState,
|
||||||
) -> *const c_char {
|
virtual_keyboard: *mut ZwpVirtualKeyboardV1,
|
||||||
let key_name = as_cstr(&key_name)
|
press: u32,
|
||||||
.expect("Missing key name")
|
timestamp: u32,
|
||||||
.to_str()
|
) {
|
||||||
.expect("Bad key name");
|
let key = key.clone_ref();
|
||||||
|
let mut key = key.borrow_mut();
|
||||||
|
key.pressed = press != 0;
|
||||||
|
|
||||||
let symbol_name = match key.to_owned().symbol.action {
|
let keycodes_count = key.keycodes.len();
|
||||||
Action::Submit { text: Some(text), .. } => {
|
for keycode in key.keycodes.iter() {
|
||||||
Some(
|
let keycode = keycode - 8;
|
||||||
text.clone()
|
match (key.pressed, keycodes_count) {
|
||||||
.into_string().expect("Bad symbol")
|
// 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
|
||||||
|
);
|
||||||
},
|
},
|
||||||
_ => None,
|
// A key made of multiple keycodes
|
||||||
};
|
// has to submit them one after the other
|
||||||
|
(true, _) => unsafe {
|
||||||
let inner = match symbol_name {
|
eek_virtual_keyboard_v1_key(
|
||||||
Some(name) => format!("[ {} ]", name),
|
virtual_keyboard, timestamp, keycode, 1
|
||||||
_ => format!("[ ]"),
|
);
|
||||||
};
|
eek_virtual_keyboard_v1_key(
|
||||||
|
virtual_keyboard, timestamp, keycode, 0
|
||||||
CString::new(format!(" key <{}> {{ {} }};\n", key_name, inner))
|
);
|
||||||
.expect("Couldn't convert string")
|
},
|
||||||
.into_raw()
|
// Design choice here: submit multiple all at press time
|
||||||
|
// and do nothing at release time
|
||||||
|
(false, _) => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C"
|
|
||||||
fn squeek_key_get_action_name(
|
|
||||||
key_name: *const c_char,
|
|
||||||
key: CKeyState,
|
|
||||||
) -> *const c_char {
|
|
||||||
let key_name = as_cstr(&key_name)
|
|
||||||
.expect("Missing key name")
|
|
||||||
.to_str()
|
|
||||||
.expect("Bad key name");
|
|
||||||
|
|
||||||
let symbol_name = match key.to_owned().symbol.action {
|
|
||||||
Action::Submit { text: Some(text), .. } => {
|
|
||||||
Some(
|
|
||||||
text.clone()
|
|
||||||
.into_string().expect("Bad symbol text")
|
|
||||||
)
|
|
||||||
},
|
|
||||||
_ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let inner = match symbol_name {
|
|
||||||
Some(name) => format!("[ {} ]", name),
|
|
||||||
_ => format!("[ ]"),
|
|
||||||
};
|
|
||||||
|
|
||||||
CString::new(format!(" key <{}> {{ {} }};\n", key_name, inner))
|
|
||||||
.expect("Couldn't convert string")
|
|
||||||
.into_raw()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +110,10 @@ 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 symbol: Symbol,
|
pub keycodes: Vec<u32>,
|
||||||
|
/// Static description of what the key does when pressed or released
|
||||||
|
pub action: Action,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a mapping where each key gets a keycode, starting from 8
|
/// Generates a mapping where each key gets a keycode, starting from 8
|
||||||
@ -178,7 +150,7 @@ impl From<io::Error> for FormattingError {
|
|||||||
|
|
||||||
/// Generates a de-facto single level keymap. TODO: actually drop second level
|
/// Generates a de-facto single level keymap. TODO: actually drop second level
|
||||||
pub fn generate_keymap(
|
pub fn generate_keymap(
|
||||||
keystates: &HashMap::<String, Rc<RefCell<KeyState>>>
|
keystates: &HashMap::<String, KeyState>
|
||||||
) -> Result<String, FormattingError> {
|
) -> Result<String, FormattingError> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
writeln!(
|
writeln!(
|
||||||
@ -191,24 +163,17 @@ pub fn generate_keymap(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for (name, state) in keystates.iter() {
|
for (name, state) in keystates.iter() {
|
||||||
let state = state.borrow();
|
if let Action::Submit { text: _, keys } = &state.action {
|
||||||
if let ::symbol::Action::Submit { text: _, keys } = &state.symbol.action {
|
if let 0 = keys.len() { eprintln!("Key {} has no keysyms", name); };
|
||||||
match keys.len() {
|
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
|
||||||
0 => eprintln!("Key {} has no keysyms", name),
|
|
||||||
a => {
|
|
||||||
// TODO: don't ignore any keysyms
|
|
||||||
if a > 1 {
|
|
||||||
eprintln!("Key {} multiple keysyms", name);
|
|
||||||
}
|
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"
|
"
|
||||||
<{}> = {};",
|
<{}> = {};",
|
||||||
keys[0].0,
|
named_keysym.0,
|
||||||
state.keycode.unwrap()
|
keycode,
|
||||||
)?;
|
)?;
|
||||||
},
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,8 +189,8 @@ pub fn generate_keymap(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for (name, state) in keystates.iter() {
|
for (name, state) in keystates.iter() {
|
||||||
if let ::symbol::Action::Submit { text: _, keys } = &state.borrow().symbol.action {
|
if let Action::Submit { text: _, keys } = &state.action {
|
||||||
if let Some(keysym) = keys.iter().next() {
|
for keysym in keys.iter() {
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"
|
"
|
||||||
@ -255,5 +220,44 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
locked: false,
|
||||||
|
pressed: false,
|
||||||
|
},
|
||||||
|
}).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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -23,9 +23,9 @@ use std::ffi::CString;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use ::keyboard::*;
|
use ::action::Action;
|
||||||
use ::float_ord::FloatOrd;
|
use ::float_ord::FloatOrd;
|
||||||
use ::symbol::*;
|
use ::keyboard::*;
|
||||||
|
|
||||||
/// Gathers stuff defined in C or called by C
|
/// Gathers stuff defined in C or called by C
|
||||||
pub mod c {
|
pub mod c {
|
||||||
@ -145,17 +145,6 @@ pub mod c {
|
|||||||
::keyboard::c::CKeyState::wrap(button.state.clone())
|
::keyboard::c::CKeyState::wrap(button.state.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Really should just return the label
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C"
|
|
||||||
fn squeek_button_get_symbol(
|
|
||||||
button: *const ::layout::Button,
|
|
||||||
) -> *const Symbol {
|
|
||||||
let button = unsafe { &*button };
|
|
||||||
let state = button.state.borrow();
|
|
||||||
&state.symbol as *const Symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
fn squeek_button_get_label(
|
fn squeek_button_get_label(
|
||||||
@ -264,6 +253,8 @@ pub mod c {
|
|||||||
angle: i32
|
angle: i32
|
||||||
) -> u32;
|
) -> u32;
|
||||||
|
|
||||||
|
// CKeyState is safe to pass to C as long as nothing dereferences it
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
pub fn eek_keyboard_set_key_locked(
|
pub fn eek_keyboard_set_key_locked(
|
||||||
keyboard: *mut LevelKeyboard,
|
keyboard: *mut LevelKeyboard,
|
||||||
key: ::keyboard::c::CKeyState,
|
key: ::keyboard::c::CKeyState,
|
||||||
@ -279,11 +270,11 @@ pub mod c {
|
|||||||
) {
|
) {
|
||||||
let layout = unsafe { &mut *layout };
|
let layout = unsafe { &mut *layout };
|
||||||
|
|
||||||
let view_name = match key.to_owned().symbol.action {
|
let view_name = match key.to_owned().action {
|
||||||
::symbol::Action::SetLevel(name) => {
|
Action::SetLevel(name) => {
|
||||||
Some(name.clone())
|
Some(name.clone())
|
||||||
},
|
},
|
||||||
::symbol::Action::LockLevel { lock, unlock } => {
|
Action::LockLevel { lock, unlock } => {
|
||||||
let locked = {
|
let locked = {
|
||||||
let key = key.clone_ref();
|
let key = key.clone_ref();
|
||||||
let mut key = key.borrow_mut();
|
let mut key = key.borrow_mut();
|
||||||
@ -320,9 +311,7 @@ pub mod c {
|
|||||||
/// Sets button and row sizes according to their contents.
|
/// Sets button and row sizes according to their contents.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C"
|
pub extern "C"
|
||||||
fn squeek_layout_place_contents(
|
fn squeek_layout_place_contents(layout: *mut Layout) {
|
||||||
layout: *mut Layout,
|
|
||||||
) {
|
|
||||||
let layout = unsafe { &mut *layout };
|
let layout = unsafe { &mut *layout };
|
||||||
for view in layout.views.values_mut() {
|
for view in layout.views.values_mut() {
|
||||||
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
|
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
|
||||||
@ -495,10 +484,8 @@ 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(),
|
||||||
symbol: Symbol {
|
|
||||||
action: Action::SetLevel("default".into()),
|
action: Action::SetLevel("default".into()),
|
||||||
}
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[allow(unused_imports)]
|
||||||
|
#[macro_use] // only for tests
|
||||||
extern crate maplit;
|
extern crate maplit;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate xkbcommon;
|
extern crate xkbcommon;
|
||||||
@ -11,6 +12,6 @@ pub mod imservice;
|
|||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod resources;
|
mod resources;
|
||||||
mod symbol;
|
mod action;
|
||||||
mod util;
|
mod util;
|
||||||
mod xdg;
|
mod xdg;
|
||||||
|
|||||||
15
src/util.rs
15
src/util.rs
@ -1,3 +1,8 @@
|
|||||||
|
/*! Assorted helpers */
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
pub mod c {
|
pub mod c {
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ffi::{ CStr, CString };
|
use std::ffi::{ CStr, CString };
|
||||||
@ -101,3 +106,13 @@ pub mod c {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash_map_map<K, V, F, K1, V1>(map: HashMap<K, V>, mut f: F)
|
||||||
|
-> HashMap<K1, V1>
|
||||||
|
where F: FnMut(K, V) -> (K1, V1),
|
||||||
|
K1: std::cmp::Eq + std::hash::Hash
|
||||||
|
{
|
||||||
|
HashMap::from_iter(
|
||||||
|
map.into_iter().map(|(key, value)| f(key, value))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
#include "wayland.h"
|
#include "wayland.h"
|
||||||
|
|
||||||
struct squeek_wayland *squeek_wayland = NULL;
|
struct squeek_wayland *squeek_wayland = NULL;
|
||||||
|
|
||||||
|
// The following functions only exist
|
||||||
|
// to create linkable symbols out of inline functions,
|
||||||
|
// because those are not directly callable from Rust
|
||||||
|
|
||||||
|
void
|
||||||
|
eek_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t time, uint32_t key, uint32_t state) {
|
||||||
|
zwp_virtual_keyboard_v1_key(zwp_virtual_keyboard_v1, time, key, state);
|
||||||
|
}
|
||||||
|
|||||||
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