Compare commits

...

10 Commits
main ... x11kb

Author SHA1 Message Date
523caa99c5 build: Avoid MaybeUninit on older Debian 2020-10-03 13:11:17 +00:00
20c44476a3 Merge branch 'master' into x11kb 2020-10-03 12:34:50 +00:00
e6c136918c keymaps: Use multiple key maps, each within the limit of what Xorg can accept.
Key maps are switched on key press whenever needed.
2020-10-03 12:22:01 +00:00
c686cf7e81 syntax: Let older rustc understand symbolmap's lifetime 2020-10-01 14:12:15 +00:00
2959d27ea3 tests: Check for missing return in builtin layouts except emoji 2020-09-28 20:37:34 +00:00
88d3a45083 keymap: Concentrate special handling of BackSpace, which is implicit in Erase action 2020-09-28 20:37:34 +00:00
edc330d683 data: Restore testability of action->keysym conversion 2020-09-28 20:37:31 +00:00
44e06bc0dc keymap: Generate from symbol map, not layout 2020-09-28 17:52:00 +00:00
8e2e8b0f5f vkeyboard: Use a generic slice instead of a vector 2020-09-28 17:36:59 +00:00
fea4ea7392 keymap: Make acceptable by Xwayland
Xwayland is more strict about accepting key maps than Wayland, and it also fails silently. Instead of fixing the other parts of the stack to reshape accepted Wayland key maps into acceptable Xorg key maps, this patch makes Squeekboard serve the maximum compatibility version in the first place.

Compatibility not actually guaranteed, that's purely observational.

Layouts with many characters (above 240) may lose some characters. This is again due to Xwayland accepting stricter layouts than Wayland.
2020-09-25 09:12:01 +00:00
18 changed files with 559 additions and 264 deletions

View File

@ -19,6 +19,7 @@ path = "@path@/examples/test_layout.rs"
[features] [features]
gio_v0_5 = [] gio_v0_5 = []
gtk_v0_5 = [] gtk_v0_5 = []
rustc_less_1_36 = []
# Dependencies which don't change based on build flags # Dependencies which don't change based on build flags
[dependencies.cairo-sys-rs] [dependencies.cairo-sys-rs]

View File

@ -31,30 +31,19 @@
#include "eek-keyboard.h" #include "eek-keyboard.h"
void level_keyboard_free(LevelKeyboard *self) { /// External linkage for Rust.
xkb_keymap_unref(self->keymap); /// Don't call multiple times on the same copy, just in Drop.
close(self->keymap_fd); void eek_key_map_deinit(struct KeyMap *self) {
squeek_layout_free(self->layout); close(self->fd);
g_free(self);
} }
LevelKeyboard* /// External linkage for Rust.
level_keyboard_new (struct squeek_layout *layout) struct KeyMap eek_key_map_from_str(char *keymap_str) {
{
LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1);
if (!keyboard) {
g_error("Failed to create a keyboard");
}
keyboard->layout = layout;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) { if (!context) {
g_error("No context created"); g_error("No context created");
} }
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str, struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
@ -62,10 +51,9 @@ level_keyboard_new (struct squeek_layout *layout)
g_error("Bad keymap:\n%s", keymap_str); g_error("Bad keymap:\n%s", keymap_str);
xkb_context_unref(context); xkb_context_unref(context);
keyboard->keymap = keymap;
char * xkb_keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1); char *xkb_keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
keyboard->keymap_len = strlen(xkb_keymap_str) + 1; size_t keymap_len = strlen(xkb_keymap_str) + 1;
g_autofree char *path = strdup("/eek_keymap-XXXXXX"); g_autofree char *path = strdup("/eek_keymap-XXXXXX");
char *r = &path[strlen(path) - 6]; char *r = &path[strlen(path) - 6];
@ -79,18 +67,39 @@ level_keyboard_new (struct squeek_layout *layout)
if (keymap_fd < 0) { if (keymap_fd < 0) {
g_error("Failed to set up keymap fd"); g_error("Failed to set up keymap fd");
} }
keyboard->keymap_fd = keymap_fd;
shm_unlink(path); shm_unlink(path);
if (ftruncate(keymap_fd, (off_t)keyboard->keymap_len)) { if (ftruncate(keymap_fd, (off_t)keymap_len)) {
g_error("Failed to increase keymap fd size"); g_error("Failed to increase keymap fd size");
} }
char *ptr = mmap(NULL, keyboard->keymap_len, PROT_WRITE, MAP_SHARED, char *ptr = mmap(NULL, keymap_len, PROT_WRITE, MAP_SHARED,
keymap_fd, 0); keymap_fd, 0);
if ((void*)ptr == (void*)-1) { if ((void*)ptr == (void*)-1) {
g_error("Failed to set up mmap"); g_error("Failed to set up mmap");
} }
strncpy(ptr, xkb_keymap_str, keyboard->keymap_len); strncpy(ptr, xkb_keymap_str, keymap_len);
munmap(ptr, keymap_len);
free(xkb_keymap_str); free(xkb_keymap_str);
munmap(ptr, keyboard->keymap_len); xkb_keymap_unref(keymap);
struct KeyMap km = {
.fd = keymap_fd,
.fd_len = keymap_len,
};
return km;
}
void level_keyboard_free(LevelKeyboard *self) {
squeek_layout_free(self->layout);
g_free(self);
}
LevelKeyboard*
level_keyboard_new (struct squeek_layout *layout)
{
LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1);
if (!keyboard) {
g_error("Failed to create a keyboard");
}
keyboard->layout = layout;
return keyboard; return keyboard;
} }

View File

@ -35,16 +35,18 @@ G_BEGIN_DECLS
/// Keyboard state holder /// Keyboard state holder
struct _LevelKeyboard { struct _LevelKeyboard {
struct squeek_layout *layout; // owned struct squeek_layout *layout; // owned
struct xkb_keymap *keymap; // owned // FIXME: This no longer needs to exist, keymap was folded into layout.
int keymap_fd; // keymap formatted as XKB string
size_t keymap_len; // length of the data inside keymap_fd
guint id; // as a key to layout choices guint id; // as a key to layout choices
}; };
typedef struct _LevelKeyboard LevelKeyboard; typedef struct _LevelKeyboard LevelKeyboard;
gchar * eek_keyboard_get_keymap /// Keymap container for Rust interoperability.
(LevelKeyboard *keyboard); struct KeyMap {
uint32_t fd; // keymap formatted as XKB string
size_t fd_len; // length of the data inside keymap_fd
};
gchar *eek_keyboard_get_keymap(LevelKeyboard *keyboard);
LevelKeyboard* LevelKeyboard*
level_keyboard_new (struct squeek_layout *layout); level_keyboard_new (struct squeek_layout *layout);

View File

@ -159,7 +159,7 @@ eekboard_context_service_use_layout(EekboardContextService *context, struct sque
// Update the keymap if necessary. // Update the keymap if necessary.
// TODO: Update submission on change event // TODO: Update submission on change event
if (context->submission) { if (context->submission) {
submission_set_keyboard(context->submission, keyboard, timestamp); submission_use_layout(context->submission, keyboard->layout, timestamp);
} }
// Update UI // Update UI
@ -345,7 +345,7 @@ void eekboard_context_service_set_submission(EekboardContextService *context, st
context->submission = submission; context->submission = submission;
if (context->submission) { if (context->submission) {
uint32_t time = gdk_event_get_time(NULL); uint32_t time = gdk_event_get_time(NULL);
submission_set_keyboard(context->submission, context->keyboard, time); submission_use_layout(context->submission, context->keyboard->layout, time);
} }
} }

View File

@ -5,6 +5,7 @@ use std::env;
fn main() -> () { fn main() -> () {
check_builtin_layout( check_builtin_layout(
env::args().nth(1).expect("No argument given").as_str() env::args().nth(1).expect("No argument given").as_str(),
env::args().nth(2).map(|s| s == "allow_missing_return").unwrap_or(false),
); );
} }

View File

@ -83,7 +83,7 @@ cargo_toml_base = configure_file(
cargo_deps = files('Cargo.deps') cargo_deps = files('Cargo.deps')
if get_option('legacy') == true if get_option('legacy') == true
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5'] cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5,rustc_less_1_36']
cargo_deps = files('Cargo.deps.legacy') cargo_deps = files('Cargo.deps.legacy')
endif endif

View File

@ -18,7 +18,7 @@ use xkbcommon::xkb;
use ::action; use ::action;
use ::keyboard::{ use ::keyboard::{
KeyState, PressType, KeyState, PressType,
generate_keymap, generate_keycodes, FormattingError generate_keymaps, generate_keycodes, KeyCode, FormattingError
}; };
use ::layout; use ::layout;
use ::layout::ArrangementKind; use ::layout::ArrangementKind;
@ -382,56 +382,45 @@ impl Layout {
) )
)}).collect(); )}).collect();
let keymap: HashMap<String, u32> = generate_keycodes( let symbolmap: HashMap<String, KeyCode> = generate_keycodes(
button_actions.iter() extract_symbol_names(&button_actions)
.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()
},
action::Action::Erase => vec![
*keymap.get("BackSpace")
.expect(&format!("BackSpace missing from keymap")),
],
_ => Vec::new(),
};
(
name.into(),
KeyState {
pressed: PressType::Released,
keycodes,
action,
}
)
});
let button_states = HashMap::<String, KeyState>::from_iter( let button_states = HashMap::<String, KeyState>::from_iter(
button_states button_actions.into_iter().map(|(name, action)| {
let keycodes = match &action {
::action::Action::Submit { text: _, keys } => {
keys.iter().map(|named_keysym| {
symbolmap.get(named_keysym.0.as_str())
.expect(
format!(
"keysym {} in key {} missing from symbol map",
named_keysym.0,
name
).as_str()
)
.clone()
}).collect()
},
action::Action::Erase => vec![
symbolmap.get("BackSpace")
.expect(&format!("BackSpace missing from symbol map"))
.clone(),
],
_ => Vec::new(),
};
(
name.into(),
KeyState {
pressed: PressType::Released,
keycodes,
action,
}
)
})
); );
// TODO: generate from symbols let keymaps = match generate_keymaps(symbolmap) {
let keymap_str = match generate_keymap(&button_states) {
Err(e) => { return (Err(e), warning_handler) }, Err(e) => { return (Err(e), warning_handler) },
Ok(v) => v, Ok(v) => v,
}; };
@ -495,10 +484,10 @@ impl Layout {
( (
Ok(::layout::LayoutData { Ok(::layout::LayoutData {
views: views, views: views,
keymap_str: { keymaps: keymaps.into_iter().map(|keymap_str|
CString::new(keymap_str) CString::new(keymap_str)
.expect("Invalid keymap string generated") .expect("Invalid keymap string generated")
}, ).collect(),
// FIXME: use a dedicated field // FIXME: use a dedicated field
margins: layout::Margins { margins: layout::Margins {
top: self.margins.top, top: self.margins.top,
@ -734,6 +723,23 @@ fn create_button<H: logging::Handler>(
} }
} }
fn extract_symbol_names<'a>(actions: &'a [(&str, action::Action)])
-> impl Iterator<Item=String> + 'a
{
actions.iter()
.filter_map(|(_name, act)| {
match act {
action::Action::Submit {
text: _, keys,
} => Some(keys.clone()),
action::Action::Erase => Some(vec!(action::KeySym("BackSpace".into()))),
_ => None,
}
})
.flatten()
.map(|named_keysym| named_keysym.0)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -862,6 +868,23 @@ mod tests {
); );
} }
/// Test if erase yields a useable keycode
#[test]
fn test_layout_erase() {
let out = Layout::from_file(path_from_root("tests/layout_erase.yaml"))
.unwrap()
.build(ProblemPanic).0
.unwrap();
assert_eq!(
out.views["base"].1
.get_rows()[0].1
.buttons[0].1
.state.borrow()
.keycodes.len(),
1
);
}
#[test] #[test]
fn parsing_fallback() { fn parsing_fallback() {
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME) assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
@ -939,4 +962,35 @@ mod tests {
} }
); );
} }
#[test]
fn test_extract_symbols() {
let actions = [(
"ac",
action::Action::Submit {
text: None,
keys: vec![
action::KeySym("a".into()),
action::KeySym("c".into()),
],
},
)];
assert_eq!(
extract_symbol_names(&actions[..]).collect::<Vec<_>>(),
vec!["a", "c"],
);
}
#[test]
fn test_extract_symbols_erase() {
let actions = [(
"Erase",
action::Action::Erase,
)];
assert_eq!(
extract_symbol_names(&actions[..]).collect::<Vec<_>>(),
vec!["BackSpace"],
);
}
} }

View File

@ -5,11 +5,13 @@ 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::mem;
use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use ::action::Action; use ::action::Action;
use ::logging; use ::util;
// Traits // Traits
use std::io::Write; use std::io::Write;
@ -21,7 +23,12 @@ pub enum PressType {
Pressed = 1, Pressed = 1,
} }
pub type KeyCode = u32; /// The extended, unambiguous layout-keycode
#[derive(Debug, Clone)]
pub struct KeyCode {
pub code: u32,
pub keymap_idx: usize,
}
bitflags!{ bitflags!{
/// Map to `virtual_keyboard.modifiers` modifiers values /// Map to `virtual_keyboard.modifiers` modifiers values
@ -80,10 +87,10 @@ impl KeyState {
} }
/// Sorts an iterator by converting it to a Vector and back /// Sorts an iterator by converting it to a Vector and back
fn sorted<'a, I: Iterator<Item=&'a str>>( fn sorted<'a, I: Iterator<Item=String>>(
iter: I iter: I
) -> impl Iterator<Item=&'a str> { ) -> impl Iterator<Item=String> {
let mut v: Vec<&'a str> = iter.collect(); let mut v: Vec<String> = iter.collect();
v.sort(); v.sort();
v.into_iter() v.into_iter()
} }
@ -91,15 +98,17 @@ fn sorted<'a, I: Iterator<Item=&'a str>>(
/// Generates a mapping where each key gets a keycode, starting from ~~8~~ /// Generates a mapping where each key gets a keycode, starting from ~~8~~
/// HACK: starting from 9, because 8 results in keycode 0, /// HACK: starting from 9, because 8 results in keycode 0,
/// which the compositor likes to discard /// which the compositor likes to discard
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>( pub fn generate_keycodes<'a, C: IntoIterator<Item=String>>(
key_names: C key_names: C,
) -> HashMap<String, u32> { ) -> HashMap<String, KeyCode> {
let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s);
HashMap::from_iter( HashMap::from_iter(
// sort to remove a source of indeterminism in keycode assignment // Sort to remove a source of indeterminism in keycode assignment.
sorted(key_names.into_iter().chain(special_keysyms)) sorted(key_names.into_iter())
.map(|name| String::from(name)) .zip(util::cycle_count(9..255))
.zip(9..) .map(|(name, (code, keymap_idx))| (
String::from(name),
KeyCode { code, keymap_idx },
))
) )
} }
@ -124,12 +133,54 @@ impl From<io::Error> for FormattingError {
} }
} }
/// Index is the key code, String is the occupant.
/// Starts all empty.
/// https://gitlab.freedesktop.org/xorg/xserver/-/issues/260
type SingleKeyMap = [Option<String>; 256];
fn single_key_map_new() -> SingleKeyMap {
// Why can't we just initialize arrays without tricks -_- ?
unsafe {
// Inspired by
// https://www.reddit.com/r/rust/comments/5n7bh1/how_to_create_an_array_of_a_type_with_clone_but/
#[cfg(feature = "rustc_less_1_36")]
let mut array: SingleKeyMap = mem::uninitialized();
#[cfg(not(feature = "rustc_less_1_36"))]
let mut array: SingleKeyMap = mem::MaybeUninit::uninit().assume_init();
for element in array.iter_mut() {
ptr::write(element, None);
}
array
}
}
pub fn generate_keymaps(symbolmap: HashMap::<String, KeyCode>)
-> Result<Vec<String>, FormattingError>
{
let mut bins: Vec<SingleKeyMap> = Vec::new();
for (name, KeyCode { code, keymap_idx }) in symbolmap.into_iter() {
if keymap_idx >= bins.len() {
bins.resize_with(
keymap_idx + 1,
|| single_key_map_new(),
);
}
bins[keymap_idx][code as usize] = Some(name);
}
let mut out = Vec::new();
for bin in bins {
out.push(generate_keymap(&bin)?);
}
Ok(out)
}
/// Generates a de-facto single level keymap. /// Generates a de-facto single level keymap.
// TODO: don't rely on keys and their order, /// Key codes must not repeat and must remain between 9 and 255.
// but rather on what keysyms and keycodes are in use. fn generate_keymap(
// Iterating actions makes it hard to deduplicate keysyms. symbolmap: &SingleKeyMap,
pub fn generate_keymap(
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!(
@ -140,86 +191,80 @@ pub fn generate_keymap(
minimum = 8; minimum = 8;
maximum = 255;" maximum = 255;"
)?; )?;
let pairs: Vec<(&String, usize)> = symbolmap.iter()
// Attach a key code to each cell.
.enumerate()
// Get rid of empty keycodes.
.filter_map(|(code, name)| name.as_ref().map(|n| (n, code)))
.collect();
for (name, state) in keystates.iter() { // Xorg can only consume up to 255 keys, so this may not work in Xwayland.
match &state.action { // Two possible solutions:
Action::Submit { text: _, keys } => { // - use levels to cram multiple characters into one key
if let 0 = keys.len() { // - swap layouts on key presses
log_print!( for (_name, keycode) in &pairs {
logging::Level::Warning, write!(
"Key {} has no keysyms", name, buf,
); "
}; <I{}> = {0};",
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) { keycode,
write!( )?;
buf,
"
<{}> = {};",
named_keysym.0,
keycode,
)?;
}
},
Action::Erase => {
let mut keycodes = state.keycodes.iter();
write!(
buf,
"
<BackSpace> = {};",
keycodes.next().expect("Erase key has no keycode"),
)?;
if let Some(_) = keycodes.next() {
log_print!(
logging::Level::Bug,
"Erase key has multiple keycodes",
);
}
},
_ => {},
}
} }
writeln!( writeln!(
buf, buf,
" "
indicator 1 = \"Caps Lock\"; // Xwayland won't accept without it.
}}; }};
xkb_symbols \"squeekboard\" {{ xkb_symbols \"squeekboard\" {{
"
name[Group1] = \"Letters\";
name[Group2] = \"Numbers/Symbols\";
key <BackSpace> {{ [ BackSpace ] }};"
)?; )?;
for (_name, state) in keystates.iter() { for (name, keycode) in pairs {
if let Action::Submit { text: _, keys } = &state.action { write!(
for keysym in keys.iter() { buf,
write!( "
buf, key <I{}> {{ [ {} ] }};",
" keycode,
key <{}> {{ [ {0} ] }};", name,
keysym.0, )?;
)?;
}
}
} }
writeln!( writeln!(
buf, buf,
" "
}}; }};
xkb_types \"squeekboard\" {{ xkb_types \"squeekboard\" {{
virtual_modifiers Squeekboard; // No modifiers! Needed for Xorg for some reason.
// Those names are needed for Xwayland.
type \"ONE_LEVEL\" {{
modifiers= none;
level_name[Level1]= \"Any\";
}};
type \"TWO_LEVEL\" {{
level_name[Level1]= \"Base\";
}};
type \"ALPHABETIC\" {{
level_name[Level1]= \"Base\";
}};
type \"KEYPAD\" {{
level_name[Level1]= \"Base\";
}};
type \"SHIFT+ALT\" {{
level_name[Level1]= \"Base\";
}};
type \"TWO_LEVEL\" {{
modifiers = Shift;
map[Shift] = Level2;
level_name[Level1] = \"Base\";
level_name[Level2] = \"Shift\";
}};
}}; }};
xkb_compatibility \"squeekboard\" {{ xkb_compatibility \"squeekboard\" {{
// Needed for Xwayland again.
interpret Any+AnyOf(all) {{
action= SetMods(modifiers=modMapMods,clearLocks);
}};
}}; }};
}};" }};"
)?; )?;
@ -234,22 +279,15 @@ mod tests {
use xkbcommon::xkb; use xkbcommon::xkb;
use ::action::KeySym;
#[test] #[test]
fn test_keymap_multi() { fn test_keymap_single_resolve() {
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); let mut key_map = single_key_map_new();
key_map[9] = Some("a".into());
key_map[10] = Some("c".into());
let keymap_str = generate_keymap(&hashmap!{ let keymap_str = generate_keymap(&key_map).unwrap();
"ac".into() => KeyState {
action: Action::Submit { let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
text: None,
keys: vec!(KeySym("a".into()), KeySym("c".into())),
},
keycodes: vec!(9, 10),
pressed: PressType::Released,
},
}).unwrap();
let keymap = xkb::Keymap::new_from_string( let keymap = xkb::Keymap::new_from_string(
&context, &context,
@ -263,4 +301,36 @@ mod tests {
assert_eq!(state.key_get_one_sym(9), xkb::KEY_a); assert_eq!(state.key_get_one_sym(9), xkb::KEY_a);
assert_eq!(state.key_get_one_sym(10), xkb::KEY_c); assert_eq!(state.key_get_one_sym(10), xkb::KEY_c);
} }
#[test]
fn test_keymap_second_resolve() {
let keymaps = generate_keymaps(hashmap!(
"a".into() => KeyCode { keymap_idx: 1, code: 9 },
)).unwrap();
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = xkb::Keymap::new_from_string(
&context,
keymaps[1].clone(), // this index is part of the test
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);
}
#[test]
fn test_symbolmap_overflow() {
// The 257th key (U1101) is interesting.
// Use Unicode encoding for being able to use in xkb keymaps.
let keynames = (0..258).map(|num| format!("U{:04X}", 0x1000 + num));
let keycodes = generate_keycodes(keynames);
// test now
let code = keycodes.get("U1101").expect("Did not find the tested keysym");
assert_eq!(code.keymap_idx, 1);
}
} }

View File

@ -39,7 +39,6 @@ struct transformation squeek_layout_calculate_transformation(
double allocation_width, double allocation_size); double allocation_width, double allocation_size);
struct squeek_layout *squeek_load_layout(const char *name, uint32_t type); struct squeek_layout *squeek_load_layout(const char *name, uint32_t type);
const char *squeek_layout_get_keymap(const struct squeek_layout*);
enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *); enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *);
void squeek_layout_free(struct squeek_layout*); void squeek_layout_free(struct squeek_layout*);

View File

@ -236,13 +236,6 @@ pub mod c {
height: allocation_height, height: allocation_height,
}) })
} }
#[no_mangle]
pub extern "C"
fn squeek_layout_get_keymap(layout: *const Layout) -> *const c_char {
let layout = unsafe { &*layout };
layout.keymap_str.as_ptr()
}
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
@ -632,8 +625,8 @@ pub struct Layout {
pub views: HashMap<String, (c::Point, View)>, pub views: HashMap<String, (c::Point, View)>,
// Non-UI stuff // Non-UI stuff
/// xkb keymap applicable to the contained keys. Unchangeable /// xkb keymaps applicable to the contained keys. Unchangeable
pub keymap_str: CString, pub keymaps: Vec<CString>,
// Changeable state // Changeable state
// a Vec would be enough, but who cares, this will be small & fast enough // a Vec would be enough, but who cares, this will be small & fast enough
// TODO: turn those into per-input point *_buttons to track dragging. // TODO: turn those into per-input point *_buttons to track dragging.
@ -649,7 +642,7 @@ pub struct Layout {
pub struct LayoutData { pub struct LayoutData {
/// Point is the offset within layout /// Point is the offset within layout
pub views: HashMap<String, (c::Point, View)>, pub views: HashMap<String, (c::Point, View)>,
pub keymap_str: CString, pub keymaps: Vec<CString>,
pub margins: Margins, pub margins: Margins,
} }
@ -672,7 +665,7 @@ impl Layout {
kind, kind,
current_view: "base".to_owned(), current_view: "base".to_owned(),
views: data.views, views: data.views,
keymap_str: data.keymap_str, keymaps: data.keymaps,
pressed_keys: HashSet::new(), pressed_keys: HashSet::new(),
margins: data.margins, margins: data.margins,
} }
@ -1135,7 +1128,7 @@ mod test {
]); ]);
let layout = Layout { let layout = Layout {
current_view: String::new(), current_view: String::new(),
keymap_str: CString::new("").unwrap(), keymaps: Vec::new(),
kind: ArrangementKind::Base, kind: ArrangementKind::Base,
pressed_keys: HashSet::new(), pressed_keys: HashSet::new(),
// Lots of bottom margin // Lots of bottom margin

View File

@ -6,6 +6,7 @@
#include "eek/eek-types.h" #include "eek/eek-types.h"
struct submission; struct submission;
struct squeek_layout;
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager, struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager, struct zwp_virtual_keyboard_manager_v1 *vkmanager,
@ -15,5 +16,5 @@ struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
// Defined in Rust // Defined in Rust
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state); struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state);
void submission_set_ui(struct submission *self, ServerContextService *ui_context); void submission_set_ui(struct submission *self, ServerContextService *ui_context);
void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard, uint32_t time); void submission_use_layout(struct submission *self, struct squeek_layout *layout, uint32_t time);
#endif #endif

View File

@ -23,8 +23,9 @@ use ::action::Modifier;
use ::imservice; use ::imservice;
use ::imservice::IMService; use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType }; use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
use ::layout::c::LevelKeyboard; use ::layout;
use ::util::vec_remove; use ::util::vec_remove;
use ::vkeyboard;
use ::vkeyboard::VirtualKeyboard; use ::vkeyboard::VirtualKeyboard;
// traits // traits
@ -68,6 +69,8 @@ pub mod c {
modifiers_active: Vec::new(), modifiers_active: Vec::new(),
virtual_keyboard: VirtualKeyboard(vk), virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(), pressed: Vec::new(),
keymap_fds: Vec::new(),
keymap_idx: None,
} }
)) ))
} }
@ -91,16 +94,17 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn submission_set_keyboard( fn submission_use_layout(
submission: *mut Submission, submission: *mut Submission,
keyboard: LevelKeyboard, layout: *const layout::Layout,
time: u32, time: u32,
) { ) {
if submission.is_null() { if submission.is_null() {
panic!("Null submission pointer"); panic!("Null submission pointer");
} }
let submission: &mut Submission = unsafe { &mut *submission }; let submission: &mut Submission = unsafe { &mut *submission };
submission.update_keymap(keyboard, Timestamp(time)); let layout = unsafe { &*layout };
submission.use_layout(layout, Timestamp(time));
} }
} }
@ -119,6 +123,8 @@ pub struct Submission {
virtual_keyboard: VirtualKeyboard, virtual_keyboard: VirtualKeyboard,
modifiers_active: Vec<(KeyStateId, Modifier)>, modifiers_active: Vec<(KeyStateId, Modifier)>,
pressed: Vec<(KeyStateId, SubmittedAction)>, pressed: Vec<(KeyStateId, SubmittedAction)>,
keymap_fds: Vec<vkeyboard::c::KeyMap>,
keymap_idx: Option<usize>,
} }
pub enum SubmitData<'a> { pub enum SubmitData<'a> {
@ -177,11 +183,34 @@ impl Submission {
let submit_action = match was_committed_as_text { let submit_action = match was_committed_as_text {
true => SubmittedAction::IMService, true => SubmittedAction::IMService,
false => { false => {
self.virtual_keyboard.switch( let keycodes_count = keycodes.len();
keycodes, for keycode in keycodes.iter() {
PressType::Pressed, self.select_keymap(keycode.keymap_idx, time);
time, let keycode = keycode.code;
); match keycodes_count {
// Pressing a key made out of a single keycode is simple:
// press on press, release on release.
1 => self.virtual_keyboard.switch(
keycode,
PressType::Pressed,
time,
),
// A key made of multiple keycodes
// has to submit them one after the other.
_ => {
self.virtual_keyboard.switch(
keycode.clone(),
PressType::Pressed,
time,
);
self.virtual_keyboard.switch(
keycode.clone(),
PressType::Released,
time,
);
},
};
}
SubmittedAction::VirtualKeyboard(keycodes.clone()) SubmittedAction::VirtualKeyboard(keycodes.clone())
}, },
}; };
@ -199,11 +228,21 @@ impl Submission {
// no matter if the imservice got activated, // no matter if the imservice got activated,
// keys must be released // keys must be released
SubmittedAction::VirtualKeyboard(keycodes) => { SubmittedAction::VirtualKeyboard(keycodes) => {
self.virtual_keyboard.switch( let keycodes_count = keycodes.len();
&keycodes, match keycodes_count {
PressType::Released, 1 => {
time, let keycode = &keycodes[0];
) self.select_keymap(keycode.keymap_idx, time);
self.virtual_keyboard.switch(
keycode.code,
PressType::Released,
time,
);
},
// Design choice here: submit multiple all at press time
// and do nothing at release time.
_ => {},
};
}, },
} }
}; };
@ -274,6 +313,7 @@ impl Submission {
} }
} }
/// Changes keymap and clears pressed keys and modifiers. /// Changes keymap and clears pressed keys and modifiers.
/// ///
/// It's not obvious if clearing is the right thing to do, /// It's not obvious if clearing is the right thing to do,
@ -283,9 +323,28 @@ impl Submission {
/// Alternatively, modifiers could be restored on the new keymap. /// Alternatively, modifiers could be restored on the new keymap.
/// That approach might be difficult /// That approach might be difficult
/// due to modifiers meaning different things in different keymaps. /// due to modifiers meaning different things in different keymaps.
pub fn update_keymap(&mut self, keyboard: LevelKeyboard, time: Timestamp) { fn select_keymap(&mut self, idx: usize, time: Timestamp) {
self.clear_all_modifiers(); if self.keymap_idx != Some(idx) {
self.release_all_virtual_keys(time); self.keymap_idx = Some(idx);
self.virtual_keyboard.update_keymap(keyboard); self.clear_all_modifiers();
self.release_all_virtual_keys(time);
let keymap = &self.keymap_fds[idx];
self.virtual_keyboard.update_keymap(keymap);
}
}
pub fn use_layout(&mut self, layout: &layout::Layout, time: Timestamp) {
self.keymap_fds = layout.keymaps.iter()
.map(|keymap_str| vkeyboard::c::KeyMap::from_cstr(
keymap_str.as_c_str()
))
.collect();
self.keymap_idx = None;
// This can probably be eliminated,
// because key presses can trigger an update anyway.
// However, self.keymap_idx needs to become Option<>
// in order to force update on new layouts.
self.select_keymap(0, time);
} }
} }

View File

@ -26,49 +26,104 @@ impl CountAndPrint {
} }
} }
pub fn check_builtin_layout(name: &str) { pub fn check_builtin_layout(name: &str, missing_return: bool) {
check_layout(Layout::from_resource(name).expect("Invalid layout data")) check_layout(
Layout::from_resource(name).expect("Invalid layout data"),
missing_return,
)
} }
pub fn check_layout_file(path: &str) { pub fn check_layout_file(path: &str) {
check_layout(Layout::from_file(path.into()).expect("Invalid layout file")) check_layout(
Layout::from_file(path.into()).expect("Invalid layout file"),
false,
)
} }
fn check_layout(layout: Layout) { fn check_sym_in_keymap(state: &xkb::State, sym_name: &str) -> bool {
let sym = xkb::keysym_from_name(sym_name, xkb::KEYSYM_NO_FLAGS);
if sym == xkb::KEY_NoSymbol {
panic!(format!("Entered invalid keysym: {}", sym_name));
}
let map = state.get_keymap();
let range = map.min_keycode()..=map.max_keycode();
range.flat_map(|code| state.key_get_syms(code))
.find(|s| **s == sym)
.is_some()
}
fn check_sym_presence(
states: &[xkb::State],
sym_name: &str,
handler: &mut dyn logging::Handler,
) {
let found = states.iter()
.position(|state| {
check_sym_in_keymap(&state, sym_name)
});
if let None = found {
handler.handle(
logging::Level::Surprise,
&format!("There's no way to input the keysym {} on this layout", sym_name),
)
}
}
fn check_layout(layout: Layout, allow_missing_return: bool) {
let handler = CountAndPrint::new(); let handler = CountAndPrint::new();
let (layout, handler) = layout.build(handler); let (layout, mut handler) = layout.build(handler);
if handler.0 > 0 { if handler.0 > 0 {
println!("{} problems while parsing layout", handler.0) println!("{} problems while parsing layout", handler.0)
} }
let layout = layout.expect("layout broken"); let layout = layout.expect("layout broken");
let xkb_states: Vec<xkb::State> = layout.keymaps.iter()
.map(|keymap_str| {
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap_str = keymap_str
.clone()
.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_COMPILE_NO_FLAGS,
).expect("Failed to create keymap");
xkb::State::new(&keymap)
})
.collect();
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); check_sym_presence(&xkb_states, "BackSpace", &mut handler);
let mut printer = logging::Print;
let keymap_str = layout.keymap_str check_sym_presence(
.clone() &xkb_states,
.into_string().expect("Failed to decode keymap string"); "Return",
if allow_missing_return { &mut printer }
let keymap = xkb::Keymap::new_from_string( else { &mut handler },
&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);
// "Press" each button with keysyms // "Press" each button with keysyms
for (_pos, view) in layout.views.values() { for (_pos, view) in layout.views.values() {
for (_y, row) in &view.get_rows() { for (_y, row) in &view.get_rows() {
for (_x, button) in &row.buttons { for (_x, button) in &row.buttons {
let keystate = button.state.borrow(); let keystate = button.state.borrow();
for keycode in &keystate.keycodes { for keycode in &keystate.keycodes {
match state.key_get_one_sym(*keycode) { match xkb_states[keycode.keymap_idx].key_get_one_sym(keycode.code) {
xkb::KEY_NoSymbol => { xkb::KEY_NoSymbol => {
eprintln!("{}", keymap_str); eprintln!(
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name); "keymap {}: {}",
keycode.keymap_idx,
layout.keymaps[keycode.keymap_idx].to_str().unwrap(),
);
panic!(
"Keysym for code {:?} on key {} ({:?}) can't be resolved",
keycode,
button.name.to_string_lossy(),
button.name,
);
}, },
_ => {}, _ => {},
} }

View File

@ -203,6 +203,23 @@ pub fn vec_remove<T, F: FnMut(&T) -> bool>(v: &mut Vec<T>, pred: F) -> Option<T>
idx.map(|idx| v.remove(idx)) idx.map(|idx| v.remove(idx))
} }
/// Repeats all the items of the iterator forever,
/// but returns the cycle number alongside.
/// Inefficient due to all the vectors, but doesn't have to be fast.
pub fn cycle_count<T, I: Clone + Iterator<Item=T>>(iter: I)
-> impl Iterator<Item=(T, usize)>
{
let numbered_copies = vec![iter].into_iter()
.cycle()
.enumerate();
numbered_copies.flat_map(|(idx, cycle)|
// Pair each element from the cycle with a copy of the index.
cycle.zip(
vec![idx].into_iter().cycle() // Repeat the index forever.
)
)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -217,4 +234,12 @@ mod tests {
assert_eq!(s.insert(Pointer(Rc::new(2u32))), true); assert_eq!(s.insert(Pointer(Rc::new(2u32))), true);
assert_eq!(s.remove(&Pointer(first)), true); assert_eq!(s.remove(&Pointer(first)), true);
} }
#[test]
fn check_count() {
assert_eq!(
cycle_count(5..8).take(7).collect::<Vec<_>>(),
vec![(5, 0), (6, 0), (7, 0), (5, 1), (6, 1), (7, 1), (5, 2)]
);
}
} }

View File

@ -1,18 +1,42 @@
/*! Managing the events belonging to virtual-keyboard interface. */ /*! Managing the events belonging to virtual-keyboard interface. */
use ::keyboard::{ KeyCode, Modifiers, PressType }; use ::keyboard::{ Modifiers, PressType };
use ::layout::c::LevelKeyboard;
use ::submission::Timestamp; use ::submission::Timestamp;
/// Standard xkb keycode
type KeyCode = u32;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
pub mod c { pub mod c {
use super::*; use std::ffi::CStr;
use std::os::raw::c_void; use std::os::raw::{ c_char, c_void };
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct ZwpVirtualKeyboardV1(*const c_void); pub struct ZwpVirtualKeyboardV1(*const c_void);
#[repr(C)]
pub struct KeyMap {
fd: u32,
fd_len: usize,
}
impl KeyMap {
pub fn from_cstr(s: &CStr) -> KeyMap {
unsafe {
eek_key_map_from_str(s.as_ptr())
}
}
}
impl Drop for KeyMap {
fn drop(&mut self) {
unsafe {
eek_key_map_deinit(self as *mut KeyMap);
}
}
}
#[no_mangle] #[no_mangle]
extern "C" { extern "C" {
pub fn eek_virtual_keyboard_v1_key( pub fn eek_virtual_keyboard_v1_key(
@ -24,13 +48,16 @@ pub mod c {
pub fn eek_virtual_keyboard_update_keymap( pub fn eek_virtual_keyboard_update_keymap(
virtual_keyboard: ZwpVirtualKeyboardV1, virtual_keyboard: ZwpVirtualKeyboardV1,
keyboard: LevelKeyboard, keymap: *const KeyMap,
); );
pub fn eek_virtual_keyboard_set_modifiers( pub fn eek_virtual_keyboard_set_modifiers(
virtual_keyboard: ZwpVirtualKeyboardV1, virtual_keyboard: ZwpVirtualKeyboardV1,
modifiers: u32, modifiers: u32,
); );
pub fn eek_key_map_from_str(keymap_str: *const c_char) -> KeyMap;
pub fn eek_key_map_deinit(keymap: *mut KeyMap);
} }
} }
@ -41,35 +68,15 @@ impl VirtualKeyboard {
// TODO: error out if keymap not set // TODO: error out if keymap not set
pub fn switch( pub fn switch(
&self, &self,
keycodes: &Vec<KeyCode>, keycode: KeyCode,
action: PressType, action: PressType,
timestamp: Timestamp, timestamp: Timestamp,
) { ) {
let keycodes_count = keycodes.len(); let keycode = keycode - 8;
for keycode in keycodes.iter() { unsafe {
let keycode = keycode - 8; c::eek_virtual_keyboard_v1_key(
match (action, keycodes_count) { self.0, timestamp.0, keycode, action.clone() as u32
// Pressing a key made out of a single keycode is simple: );
// press on press, release on release.
(_, 1) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, action.clone() as u32
);
},
// A key made of multiple keycodes
// has to submit them one after the other
(PressType::Pressed, _) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Pressed as u32
);
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Released as u32
);
},
// Design choice here: submit multiple all at press time
// and do nothing at release time
(PressType::Released, _) => {},
}
} }
} }
@ -80,9 +87,12 @@ impl VirtualKeyboard {
} }
} }
pub fn update_keymap(&self, keyboard: LevelKeyboard) { pub fn update_keymap(&self, keymap: &c::KeyMap) {
unsafe { unsafe {
c::eek_virtual_keyboard_update_keymap(self.0, keyboard); c::eek_virtual_keyboard_update_keymap(
self.0,
keymap as *const c::KeyMap,
);
} }
} }
} }

View File

@ -14,10 +14,10 @@ eek_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard
} }
void eek_virtual_keyboard_update_keymap(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, const LevelKeyboard *keyboard) { void eek_virtual_keyboard_update_keymap(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, struct KeyMap *keymap) {
zwp_virtual_keyboard_v1_keymap(zwp_virtual_keyboard_v1, zwp_virtual_keyboard_v1_keymap(zwp_virtual_keyboard_v1,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keyboard->keymap_fd, keyboard->keymap_len); keymap->fd, keymap->fd_len);
} }
void void

10
tests/layout_erase.yaml Normal file
View File

@ -0,0 +1,10 @@
---
# Erase only
views:
base:
- "BackSpace"
outlines:
default: { width: 0, height: 0 }
buttons:
BackSpace:
action: erase

View File

@ -69,11 +69,17 @@ foreach layout : [
'emoji', 'emoji',
] ]
extra = []
if layout == 'emoji'
extra += ['allow_missing_return']
endif
test( test(
'test_layout_' + layout, 'test_layout_' + layout,
cargo_script, cargo_script,
args: ['run'] + cargo_build_flags args: ['run'] + cargo_build_flags
+ [ '--example', 'test_layout', '--', layout], + ['--example', 'test_layout', '--', layout]
+ extra,
workdir: meson.build_root(), workdir: meson.build_root(),
) )
endforeach endforeach