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 "eek-enumtypes.h"
#include "eekboard/eekboard-context-service.h"
#include "eekboard/key-emitter.h"
#include "keymap.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) {
squeek_key_set_pressed(key, TRUE);
keyboard->pressed_keys = g_list_prepend (keyboard->pressed_keys, key);
// 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);
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_PRESS, timestamp);
}
void eek_keyboard_release_key(LevelKeyboard *keyboard,
@ -118,12 +110,7 @@ void eek_keyboard_release_key(LevelKeyboard *keyboard,
}
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, FALSE, timestamp);
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_RELEASE, timestamp);
}
void level_keyboard_deinit(LevelKeyboard *self) {

View File

@ -193,12 +193,7 @@ static void render_button_in_context(EekRenderer *self,
/* render outline */
EekBounds bounds = squeek_button_get_bounds(place->button);
if (active)
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);
outline_surface = NULL;
if (!outline_surface) {
cairo_t *cr;
@ -221,10 +216,6 @@ static void render_button_in_context(EekRenderer *self,
cairo_restore (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_paint (cr);

View File

@ -131,7 +131,6 @@ emit_key_activated (EekboardContextService *manager,
*/
SeatEmitter emitter = {0};
emitter.virtual_keyboard = manager->virtual_keyboard;
emitter.keymap = keyboard->keymap;
update_modifier_info (&emitter);
send_fake_key (&emitter, keyboard, keycode, pressed, timestamp);
}

View File

@ -14,14 +14,37 @@ fn check_layout(name: &str) {
.expect("layout broken");
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
xkb::Keymap::new_from_string(
let keymap_str = layout.keymap_str
.clone()
.into_string().expect("Failed to decode keymap string");
let keymap = xkb::Keymap::new_from_string(
&context,
layout.keymap_str
.clone()
.into_string().expect("Failed to decode keymap string"),
keymap_str.clone(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).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() -> () {

View File

@ -37,10 +37,3 @@ pub enum Action {
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 ::util::c::as_str;
use ::util::hash_map_map;
use ::xdg;
// traits, derives
@ -131,11 +132,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,41 +305,73 @@ 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),
symbol: create_symbol(
let button_actions: Vec<(&str, ::action::Action)>
= button_names.iter().map(|name| {(
*name,
create_action(
&self.buttons,
name,
self.views.keys().collect()
),
}))
)});
)
)}).collect();
let button_states =
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
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(),
KeyState {
pressed: false,
locked: false,
keycodes,
action,
}
)
});
let button_states
= HashMap::<String, KeyState>::from_iter(
button_states
);
// TODO: generate from symbols
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(
self.views.iter().map(|(name, view)| {(
name.clone(),
@ -358,7 +395,7 @@ impl Layout {
&self.buttons,
&self.outlines,
name,
button_states.get(name.into())
button_states_cache.get(name.into())
.expect("Button state not created")
.clone()
))
@ -380,11 +417,11 @@ impl Layout {
}
}
fn create_symbol(
fn create_action(
button_info: &HashMap<String, ButtonMeta>,
name: &str,
view_names: Vec<&String>,
) -> ::symbol::Symbol {
) -> ::action::Action {
let default_meta = ButtonMeta::default();
let symbol_meta = button_info.get(name)
.unwrap_or(&default_meta);
@ -410,64 +447,68 @@ fn create_symbol(
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 {
Some(Action::SetView(view_name)) => ::symbol::Symbol {
action: ::symbol::Action::SetLevel(
filter_view_name(name, view_name.clone(), &view_names)
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
filter_view_name(name, view_name.clone(), &view_names)
),
Some(Action::Locking {
lock_view, unlock_view
}) => ::action::Action::LockLevel {
lock: filter_view_name(name, lock_view.clone(), &view_names),
unlock: filter_view_name(
name,
unlock_view.clone(),
&view_names
),
},
Some(Action::Locking { lock_view, unlock_view }) => ::symbol::Symbol {
action: ::symbol::Action::LockLevel {
lock: filter_view_name(name, lock_view.clone(), &view_names),
unlock: filter_view_name(
name,
unlock_view.clone(),
&view_names
),
},
Some(Action::ShowPrefs) => ::action::Action::Submit {
text: None,
keys: Vec::new(),
},
Some(Action::ShowPrefs) => ::symbol::Symbol {
action: ::symbol::Action::Submit {
text: None,
keys: Vec::new(),
},
},
None => ::symbol::Symbol {
action: ::symbol::Action::Submit {
text: None,
keys: vec!(
::symbol::KeySym(keysym.unwrap()),
),
},
None => ::action::Action::Submit {
text: None,
keys: keysyms.into_iter().map(::action::KeySym).collect(),
},
}
}
@ -638,7 +679,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)
@ -650,7 +708,7 @@ mod tests {
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
#[test]
fn fallbacks_order() {
let (layout, source, _failure) = load_layout(
let (_layout, source, _failure) = load_layout(
"nb",
Some(PathBuf::from("tests"))
);
@ -674,7 +732,7 @@ mod tests {
#[test]
fn test_key_unicode() {
assert_eq!(
create_symbol(
create_action(
&hashmap!{
".".into() => ButtonMeta {
icon: None,
@ -687,11 +745,9 @@ mod tests {
".",
Vec::new()
),
::symbol::Symbol {
action: ::symbol::Action::Submit {
text: None,
keys: vec!(::symbol::KeySym("U002E".into())),
},
::action::Action::Submit {
text: None,
keys: vec!(::action::KeySym("U002E".into())),
}
);
}

View File

@ -1,26 +1,21 @@
#ifndef __KEYBOARD_H
#define __KEYBOARD_H
#include "stdbool.h"
#include "inttypes.h"
#include "stdbool.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
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);
void squeek_key_set_pressed(struct squeek_key *key, uint32_t pressed);
uint32_t squeek_key_is_locked(struct squeek_key *key);
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);
struct squeek_symbol *squeek_key_get_symbol(struct squeek_key* key);
const char* squeek_key_to_keymap_entry(const char *key_name, struct squeek_key *key);
enum key_press {
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

View File

@ -1,13 +1,12 @@
/*! State of the emulated keyboard and keys */
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::rc::Rc;
use std::string::FromUtf8Error;
use ::symbol::{ Symbol, Action };
use ::action::Action;
use std::io::Write;
use std::iter::{ FromIterator, IntoIterator };
@ -16,21 +15,28 @@ use std::iter::{ FromIterator, IntoIterator };
pub mod c {
use super::*;
use ::util::c;
use ::util::c::as_cstr;
use std::ffi::CString;
use std::os::raw::c_char;
use std::os::raw::c_void;
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]
pub extern "C"
fn squeek_key_free(key: CKeyState) {
unsafe { key.unwrap() }; // reference dropped
extern "C" {
/// Checks if point falls within bounds,
/// 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
#[no_mangle]
pub extern "C"
@ -44,15 +50,7 @@ pub mod c {
//let key = unsafe { Rc::from_raw(key.0) };
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]
pub extern "C"
fn squeek_key_is_locked(key: CKeyState) -> u32 {
@ -66,73 +64,45 @@ pub mod c {
let mut key = key.borrow_mut();
key.locked = locked != 0;
}
#[no_mangle]
pub extern "C"
fn squeek_key_get_keycode(key: CKeyState) -> u32 {
return key.to_owned().keycode.unwrap_or(0u32);
}
#[no_mangle]
pub extern "C"
fn squeek_key_to_keymap_entry(
key_name: *const c_char,
fn squeek_key_press(
key: CKeyState,
) -> *const c_char {
let key_name = as_cstr(&key_name)
.expect("Missing key name")
.to_str()
.expect("Bad key name");
virtual_keyboard: *mut ZwpVirtualKeyboardV1,
press: u32,
timestamp: u32,
) {
let key = key.clone_ref();
let mut key = key.borrow_mut();
key.pressed = press != 0;
let symbol_name = match key.to_owned().symbol.action {
Action::Submit { text: Some(text), .. } => {
Some(
text.clone()
.into_string().expect("Bad symbol")
)
},
_ => 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()
}
#[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()
let keycodes_count = key.keycodes.len();
for keycode in key.keycodes.iter() {
let keycode = keycode - 8;
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, _) => {},
}
}
}
}
@ -140,8 +110,10 @@ pub mod c {
pub struct KeyState {
pub pressed: bool,
pub locked: bool,
pub keycode: Option<u32>,
pub symbol: Symbol,
/// 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
pub action: Action,
}
/// 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
pub fn generate_keymap(
keystates: &HashMap::<String, Rc<RefCell<KeyState>>>
keystates: &HashMap::<String, KeyState>
) -> Result<String, FormattingError> {
let mut buf: Vec<u8> = Vec::new();
writeln!(
@ -191,24 +163,17 @@ pub fn generate_keymap(
)?;
for (name, state) in keystates.iter() {
let state = state.borrow();
if let ::symbol::Action::Submit { text: _, keys } = &state.symbol.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 Action::Submit { text: _, keys } = &state.action {
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,
)?;
}
}
}
@ -224,8 +189,8 @@ pub fn generate_keymap(
)?;
for (name, state) in keystates.iter() {
if let ::symbol::Action::Submit { text: _, keys } = &state.borrow().symbol.action {
if let Some(keysym) = keys.iter().next() {
if let Action::Submit { text: _, keys } = &state.action {
for keysym in keys.iter() {
write!(
buf,
"
@ -255,5 +220,44 @@ pub fn generate_keymap(
}};"
)?;
//println!("{}", String::from_utf8(buf.clone()).unwrap());
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::vec::Vec;
use ::keyboard::*;
use ::action::Action;
use ::float_ord::FloatOrd;
use ::symbol::*;
use ::keyboard::*;
/// Gathers stuff defined in C or called by C
pub mod c {
@ -144,18 +144,7 @@ pub mod c {
let button = unsafe { &*button };
::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]
pub extern "C"
fn squeek_button_get_label(
@ -264,6 +253,8 @@ pub mod c {
angle: i32
) -> u32;
// CKeyState is safe to pass to C as long as nothing dereferences it
#[allow(improper_ctypes)]
pub fn eek_keyboard_set_key_locked(
keyboard: *mut LevelKeyboard,
key: ::keyboard::c::CKeyState,
@ -279,11 +270,11 @@ pub mod c {
) {
let layout = unsafe { &mut *layout };
let view_name = match key.to_owned().symbol.action {
::symbol::Action::SetLevel(name) => {
let view_name = match key.to_owned().action {
Action::SetLevel(name) => {
Some(name.clone())
},
::symbol::Action::LockLevel { lock, unlock } => {
Action::LockLevel { lock, unlock } => {
let locked = {
let key = key.clone_ref();
let mut key = key.borrow_mut();
@ -320,9 +311,7 @@ pub mod c {
/// Sets button and row sizes according to their contents.
#[no_mangle]
pub extern "C"
fn squeek_layout_place_contents(
layout: *mut Layout,
) {
fn squeek_layout_place_contents(layout: *mut Layout) {
let layout = unsafe { &mut *layout };
for view in layout.views.values_mut() {
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
@ -495,10 +484,8 @@ pub mod c {
Rc::new(RefCell::new(::keyboard::KeyState {
pressed: false,
locked: false,
keycode: None,
symbol: Symbol {
action: Action::SetLevel("default".into()),
}
keycodes: Vec::new(),
action: Action::SetLevel("default".into()),
}))
}

View File

@ -1,6 +1,7 @@
#[macro_use]
extern crate bitflags;
#[macro_use]
#[allow(unused_imports)]
#[macro_use] // only for tests
extern crate maplit;
extern crate serde;
extern crate xkbcommon;
@ -11,6 +12,6 @@ pub mod imservice;
mod keyboard;
mod layout;
mod resources;
mod symbol;
mod action;
mod util;
mod xdg;

View File

@ -1,3 +1,8 @@
/*! Assorted helpers */
use std::collections::HashMap;
use std::iter::FromIterator;
pub mod c {
use std::cell::RefCell;
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"
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 }