Merge branch 'multicodepoint' into 'master'

Support submitting multi-codepoint sequences

Closes #96

See merge request Librem5/squeekboard!207
This commit is contained in:
David Boddie
2019-10-10 16:30:54 +00:00
13 changed files with 341 additions and 264 deletions

View File

@ -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) {

View File

@ -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);

View File

@ -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);
} }

View File

@ -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() -> () {

View File

@ -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,
}

View File

@ -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())),
},
} }
); );
} }

View File

@ -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

View File

@ -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);
}
}

View File

@ -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()),
}
})) }))
} }

View File

@ -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;

View File

@ -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))
)
}

View File

@ -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
View 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 }