Compare commits
10 Commits
squeekboar
...
x11kb
| Author | SHA1 | Date | |
|---|---|---|---|
| 523caa99c5 | |||
| 20c44476a3 | |||
| e6c136918c | |||
| c686cf7e81 | |||
| 2959d27ea3 | |||
| 88d3a45083 | |||
| edc330d683 | |||
| 44e06bc0dc | |||
| 8e2e8b0f5f | |||
| fea4ea7392 |
@ -19,6 +19,7 @@ path = "@path@/examples/test_layout.rs"
|
||||
[features]
|
||||
gio_v0_5 = []
|
||||
gtk_v0_5 = []
|
||||
rustc_less_1_36 = []
|
||||
|
||||
# Dependencies which don't change based on build flags
|
||||
[dependencies.cairo-sys-rs]
|
||||
|
||||
@ -31,30 +31,19 @@
|
||||
|
||||
#include "eek-keyboard.h"
|
||||
|
||||
void level_keyboard_free(LevelKeyboard *self) {
|
||||
xkb_keymap_unref(self->keymap);
|
||||
close(self->keymap_fd);
|
||||
squeek_layout_free(self->layout);
|
||||
g_free(self);
|
||||
/// External linkage for Rust.
|
||||
/// Don't call multiple times on the same copy, just in Drop.
|
||||
void eek_key_map_deinit(struct KeyMap *self) {
|
||||
close(self->fd);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/// External linkage for Rust.
|
||||
struct KeyMap eek_key_map_from_str(char *keymap_str) {
|
||||
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
||||
if (!context) {
|
||||
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,
|
||||
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);
|
||||
|
||||
xkb_context_unref(context);
|
||||
keyboard->keymap = keymap;
|
||||
|
||||
char * xkb_keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
|
||||
keyboard->keymap_len = strlen(xkb_keymap_str) + 1;
|
||||
char *xkb_keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
|
||||
size_t keymap_len = strlen(xkb_keymap_str) + 1;
|
||||
|
||||
g_autofree char *path = strdup("/eek_keymap-XXXXXX");
|
||||
char *r = &path[strlen(path) - 6];
|
||||
@ -79,18 +67,39 @@ level_keyboard_new (struct squeek_layout *layout)
|
||||
if (keymap_fd < 0) {
|
||||
g_error("Failed to set up keymap fd");
|
||||
}
|
||||
keyboard->keymap_fd = keymap_fd;
|
||||
|
||||
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");
|
||||
}
|
||||
char *ptr = mmap(NULL, keyboard->keymap_len, PROT_WRITE, MAP_SHARED,
|
||||
char *ptr = mmap(NULL, keymap_len, PROT_WRITE, MAP_SHARED,
|
||||
keymap_fd, 0);
|
||||
if ((void*)ptr == (void*)-1) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -35,16 +35,18 @@ G_BEGIN_DECLS
|
||||
/// Keyboard state holder
|
||||
struct _LevelKeyboard {
|
||||
struct squeek_layout *layout; // owned
|
||||
struct xkb_keymap *keymap; // owned
|
||||
int keymap_fd; // keymap formatted as XKB string
|
||||
size_t keymap_len; // length of the data inside keymap_fd
|
||||
|
||||
// FIXME: This no longer needs to exist, keymap was folded into layout.
|
||||
guint id; // as a key to layout choices
|
||||
};
|
||||
typedef struct _LevelKeyboard LevelKeyboard;
|
||||
|
||||
gchar * eek_keyboard_get_keymap
|
||||
(LevelKeyboard *keyboard);
|
||||
/// Keymap container for Rust interoperability.
|
||||
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*
|
||||
level_keyboard_new (struct squeek_layout *layout);
|
||||
|
||||
@ -159,7 +159,7 @@ eekboard_context_service_use_layout(EekboardContextService *context, struct sque
|
||||
// Update the keymap if necessary.
|
||||
// TODO: Update submission on change event
|
||||
if (context->submission) {
|
||||
submission_set_keyboard(context->submission, keyboard, timestamp);
|
||||
submission_use_layout(context->submission, keyboard->layout, timestamp);
|
||||
}
|
||||
|
||||
// Update UI
|
||||
@ -345,7 +345,7 @@ void eekboard_context_service_set_submission(EekboardContextService *context, st
|
||||
context->submission = submission;
|
||||
if (context->submission) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ use std::env;
|
||||
|
||||
fn main() -> () {
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ cargo_toml_base = configure_file(
|
||||
cargo_deps = files('Cargo.deps')
|
||||
|
||||
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')
|
||||
endif
|
||||
|
||||
|
||||
150
src/data.rs
150
src/data.rs
@ -18,7 +18,7 @@ use xkbcommon::xkb;
|
||||
use ::action;
|
||||
use ::keyboard::{
|
||||
KeyState, PressType,
|
||||
generate_keymap, generate_keycodes, FormattingError
|
||||
generate_keymaps, generate_keycodes, KeyCode, FormattingError
|
||||
};
|
||||
use ::layout;
|
||||
use ::layout::ArrangementKind;
|
||||
@ -382,56 +382,45 @@ impl Layout {
|
||||
)
|
||||
)}).collect();
|
||||
|
||||
let keymap: HashMap<String, u32> = generate_keycodes(
|
||||
button_actions.iter()
|
||||
.filter_map(|(_name, action)| {
|
||||
match action {
|
||||
::action::Action::Submit {
|
||||
text: _, keys,
|
||||
} => Some(keys),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|named_keysym| named_keysym.0.as_str())
|
||||
let symbolmap: HashMap<String, KeyCode> = generate_keycodes(
|
||||
extract_symbol_names(&button_actions)
|
||||
);
|
||||
|
||||
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(
|
||||
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 keymap_str = match generate_keymap(&button_states) {
|
||||
let keymaps = match generate_keymaps(symbolmap) {
|
||||
Err(e) => { return (Err(e), warning_handler) },
|
||||
Ok(v) => v,
|
||||
};
|
||||
@ -495,10 +484,10 @@ impl Layout {
|
||||
(
|
||||
Ok(::layout::LayoutData {
|
||||
views: views,
|
||||
keymap_str: {
|
||||
keymaps: keymaps.into_iter().map(|keymap_str|
|
||||
CString::new(keymap_str)
|
||||
.expect("Invalid keymap string generated")
|
||||
},
|
||||
).collect(),
|
||||
// FIXME: use a dedicated field
|
||||
margins: layout::Margins {
|
||||
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)]
|
||||
mod tests {
|
||||
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]
|
||||
fn parsing_fallback() {
|
||||
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"],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
252
src/keyboard.rs
252
src/keyboard.rs
@ -5,11 +5,13 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use ::action::Action;
|
||||
use ::logging;
|
||||
use ::util;
|
||||
|
||||
// Traits
|
||||
use std::io::Write;
|
||||
@ -21,7 +23,12 @@ pub enum PressType {
|
||||
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!{
|
||||
/// Map to `virtual_keyboard.modifiers` modifiers values
|
||||
@ -80,10 +87,10 @@ impl KeyState {
|
||||
}
|
||||
|
||||
/// 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
|
||||
) -> impl Iterator<Item=&'a str> {
|
||||
let mut v: Vec<&'a str> = iter.collect();
|
||||
) -> impl Iterator<Item=String> {
|
||||
let mut v: Vec<String> = iter.collect();
|
||||
v.sort();
|
||||
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~~
|
||||
/// HACK: starting from 9, because 8 results in keycode 0,
|
||||
/// which the compositor likes to discard
|
||||
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
|
||||
key_names: C
|
||||
) -> HashMap<String, u32> {
|
||||
let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s);
|
||||
pub fn generate_keycodes<'a, C: IntoIterator<Item=String>>(
|
||||
key_names: C,
|
||||
) -> HashMap<String, KeyCode> {
|
||||
HashMap::from_iter(
|
||||
// sort to remove a source of indeterminism in keycode assignment
|
||||
sorted(key_names.into_iter().chain(special_keysyms))
|
||||
.map(|name| String::from(name))
|
||||
.zip(9..)
|
||||
// Sort to remove a source of indeterminism in keycode assignment.
|
||||
sorted(key_names.into_iter())
|
||||
.zip(util::cycle_count(9..255))
|
||||
.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.
|
||||
// TODO: don't rely on keys and their order,
|
||||
// but rather on what keysyms and keycodes are in use.
|
||||
// Iterating actions makes it hard to deduplicate keysyms.
|
||||
pub fn generate_keymap(
|
||||
keystates: &HashMap::<String, KeyState>
|
||||
/// Key codes must not repeat and must remain between 9 and 255.
|
||||
fn generate_keymap(
|
||||
symbolmap: &SingleKeyMap,
|
||||
) -> Result<String, FormattingError> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
writeln!(
|
||||
@ -140,86 +191,80 @@ pub fn generate_keymap(
|
||||
minimum = 8;
|
||||
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() {
|
||||
match &state.action {
|
||||
Action::Submit { text: _, keys } => {
|
||||
if let 0 = keys.len() {
|
||||
log_print!(
|
||||
logging::Level::Warning,
|
||||
"Key {} has no keysyms", name,
|
||||
);
|
||||
};
|
||||
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
|
||||
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",
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
// Xorg can only consume up to 255 keys, so this may not work in Xwayland.
|
||||
// Two possible solutions:
|
||||
// - use levels to cram multiple characters into one key
|
||||
// - swap layouts on key presses
|
||||
for (_name, keycode) in &pairs {
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
<I{}> = {0};",
|
||||
keycode,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"
|
||||
indicator 1 = \"Caps Lock\"; // Xwayland won't accept without it.
|
||||
}};
|
||||
|
||||
xkb_symbols \"squeekboard\" {{
|
||||
|
||||
name[Group1] = \"Letters\";
|
||||
name[Group2] = \"Numbers/Symbols\";
|
||||
|
||||
key <BackSpace> {{ [ BackSpace ] }};"
|
||||
"
|
||||
)?;
|
||||
|
||||
for (_name, state) in keystates.iter() {
|
||||
if let Action::Submit { text: _, keys } = &state.action {
|
||||
for keysym in keys.iter() {
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
key <{}> {{ [ {0} ] }};",
|
||||
keysym.0,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for (name, keycode) in pairs {
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
key <I{}> {{ [ {} ] }};",
|
||||
keycode,
|
||||
name,
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"
|
||||
}};
|
||||
|
||||
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\" {{
|
||||
// Needed for Xwayland again.
|
||||
interpret Any+AnyOf(all) {{
|
||||
action= SetMods(modifiers=modMapMods,clearLocks);
|
||||
}};
|
||||
}};
|
||||
}};"
|
||||
)?;
|
||||
@ -234,22 +279,15 @@ mod tests {
|
||||
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use ::action::KeySym;
|
||||
|
||||
#[test]
|
||||
fn test_keymap_multi() {
|
||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
fn test_keymap_single_resolve() {
|
||||
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!{
|
||||
"ac".into() => KeyState {
|
||||
action: Action::Submit {
|
||||
text: None,
|
||||
keys: vec!(KeySym("a".into()), KeySym("c".into())),
|
||||
},
|
||||
keycodes: vec!(9, 10),
|
||||
pressed: PressType::Released,
|
||||
},
|
||||
}).unwrap();
|
||||
let keymap_str = generate_keymap(&key_map).unwrap();
|
||||
|
||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
|
||||
let keymap = xkb::Keymap::new_from_string(
|
||||
&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(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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,6 @@ struct transformation squeek_layout_calculate_transformation(
|
||||
double allocation_width, double allocation_size);
|
||||
|
||||
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 *);
|
||||
void squeek_layout_free(struct squeek_layout*);
|
||||
|
||||
|
||||
@ -236,13 +236,6 @@ pub mod c {
|
||||
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]
|
||||
pub extern "C"
|
||||
@ -632,8 +625,8 @@ pub struct Layout {
|
||||
pub views: HashMap<String, (c::Point, View)>,
|
||||
|
||||
// Non-UI stuff
|
||||
/// xkb keymap applicable to the contained keys. Unchangeable
|
||||
pub keymap_str: CString,
|
||||
/// xkb keymaps applicable to the contained keys. Unchangeable
|
||||
pub keymaps: Vec<CString>,
|
||||
// Changeable state
|
||||
// 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.
|
||||
@ -649,7 +642,7 @@ pub struct Layout {
|
||||
pub struct LayoutData {
|
||||
/// Point is the offset within layout
|
||||
pub views: HashMap<String, (c::Point, View)>,
|
||||
pub keymap_str: CString,
|
||||
pub keymaps: Vec<CString>,
|
||||
pub margins: Margins,
|
||||
}
|
||||
|
||||
@ -672,7 +665,7 @@ impl Layout {
|
||||
kind,
|
||||
current_view: "base".to_owned(),
|
||||
views: data.views,
|
||||
keymap_str: data.keymap_str,
|
||||
keymaps: data.keymaps,
|
||||
pressed_keys: HashSet::new(),
|
||||
margins: data.margins,
|
||||
}
|
||||
@ -1135,7 +1128,7 @@ mod test {
|
||||
]);
|
||||
let layout = Layout {
|
||||
current_view: String::new(),
|
||||
keymap_str: CString::new("").unwrap(),
|
||||
keymaps: Vec::new(),
|
||||
kind: ArrangementKind::Base,
|
||||
pressed_keys: HashSet::new(),
|
||||
// Lots of bottom margin
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "eek/eek-types.h"
|
||||
|
||||
struct submission;
|
||||
struct squeek_layout;
|
||||
|
||||
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
|
||||
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
|
||||
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_keyboard(struct submission *self, LevelKeyboard *keyboard, uint32_t time);
|
||||
void submission_use_layout(struct submission *self, struct squeek_layout *layout, uint32_t time);
|
||||
#endif
|
||||
|
||||
@ -23,8 +23,9 @@ use ::action::Modifier;
|
||||
use ::imservice;
|
||||
use ::imservice::IMService;
|
||||
use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
|
||||
use ::layout::c::LevelKeyboard;
|
||||
use ::layout;
|
||||
use ::util::vec_remove;
|
||||
use ::vkeyboard;
|
||||
use ::vkeyboard::VirtualKeyboard;
|
||||
|
||||
// traits
|
||||
@ -68,6 +69,8 @@ pub mod c {
|
||||
modifiers_active: Vec::new(),
|
||||
virtual_keyboard: VirtualKeyboard(vk),
|
||||
pressed: Vec::new(),
|
||||
keymap_fds: Vec::new(),
|
||||
keymap_idx: None,
|
||||
}
|
||||
))
|
||||
}
|
||||
@ -91,16 +94,17 @@ pub mod c {
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn submission_set_keyboard(
|
||||
fn submission_use_layout(
|
||||
submission: *mut Submission,
|
||||
keyboard: LevelKeyboard,
|
||||
layout: *const layout::Layout,
|
||||
time: u32,
|
||||
) {
|
||||
if submission.is_null() {
|
||||
panic!("Null submission pointer");
|
||||
}
|
||||
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,
|
||||
modifiers_active: Vec<(KeyStateId, Modifier)>,
|
||||
pressed: Vec<(KeyStateId, SubmittedAction)>,
|
||||
keymap_fds: Vec<vkeyboard::c::KeyMap>,
|
||||
keymap_idx: Option<usize>,
|
||||
}
|
||||
|
||||
pub enum SubmitData<'a> {
|
||||
@ -177,11 +183,34 @@ impl Submission {
|
||||
let submit_action = match was_committed_as_text {
|
||||
true => SubmittedAction::IMService,
|
||||
false => {
|
||||
self.virtual_keyboard.switch(
|
||||
keycodes,
|
||||
PressType::Pressed,
|
||||
time,
|
||||
);
|
||||
let keycodes_count = keycodes.len();
|
||||
for keycode in keycodes.iter() {
|
||||
self.select_keymap(keycode.keymap_idx, 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())
|
||||
},
|
||||
};
|
||||
@ -199,11 +228,21 @@ impl Submission {
|
||||
// no matter if the imservice got activated,
|
||||
// keys must be released
|
||||
SubmittedAction::VirtualKeyboard(keycodes) => {
|
||||
self.virtual_keyboard.switch(
|
||||
&keycodes,
|
||||
PressType::Released,
|
||||
time,
|
||||
)
|
||||
let keycodes_count = keycodes.len();
|
||||
match keycodes_count {
|
||||
1 => {
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
/// That approach might be difficult
|
||||
/// due to modifiers meaning different things in different keymaps.
|
||||
pub fn update_keymap(&mut self, keyboard: LevelKeyboard, time: Timestamp) {
|
||||
self.clear_all_modifiers();
|
||||
self.release_all_virtual_keys(time);
|
||||
self.virtual_keyboard.update_keymap(keyboard);
|
||||
fn select_keymap(&mut self, idx: usize, time: Timestamp) {
|
||||
if self.keymap_idx != Some(idx) {
|
||||
self.keymap_idx = Some(idx);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
99
src/tests.rs
99
src/tests.rs
@ -26,49 +26,104 @@ impl CountAndPrint {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_builtin_layout(name: &str) {
|
||||
check_layout(Layout::from_resource(name).expect("Invalid layout data"))
|
||||
pub fn check_builtin_layout(name: &str, missing_return: bool) {
|
||||
check_layout(
|
||||
Layout::from_resource(name).expect("Invalid layout data"),
|
||||
missing_return,
|
||||
)
|
||||
}
|
||||
|
||||
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 (layout, handler) = layout.build(handler);
|
||||
let (layout, mut handler) = layout.build(handler);
|
||||
|
||||
if handler.0 > 0 {
|
||||
println!("{} problems while parsing layout", handler.0)
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let keymap_str = layout.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");
|
||||
check_sym_presence(&xkb_states, "BackSpace", &mut handler);
|
||||
let mut printer = logging::Print;
|
||||
check_sym_presence(
|
||||
&xkb_states,
|
||||
"Return",
|
||||
if allow_missing_return { &mut printer }
|
||||
else { &mut handler },
|
||||
);
|
||||
|
||||
let state = xkb::State::new(&keymap);
|
||||
|
||||
// "Press" each button with keysyms
|
||||
for (_pos, view) in layout.views.values() {
|
||||
for (_y, row) in &view.get_rows() {
|
||||
for (_x, button) in &row.buttons {
|
||||
let keystate = button.state.borrow();
|
||||
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 => {
|
||||
eprintln!("{}", keymap_str);
|
||||
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
|
||||
eprintln!(
|
||||
"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,
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
25
src/util.rs
25
src/util.rs
@ -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))
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -217,4 +234,12 @@ mod tests {
|
||||
assert_eq!(s.insert(Pointer(Rc::new(2u32))), 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)]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,42 @@
|
||||
/*! Managing the events belonging to virtual-keyboard interface. */
|
||||
|
||||
use ::keyboard::{ KeyCode, Modifiers, PressType };
|
||||
use ::layout::c::LevelKeyboard;
|
||||
use ::keyboard::{ Modifiers, PressType };
|
||||
use ::submission::Timestamp;
|
||||
|
||||
/// Standard xkb keycode
|
||||
type KeyCode = u32;
|
||||
|
||||
/// Gathers stuff defined in C or called by C
|
||||
pub mod c {
|
||||
use super::*;
|
||||
use std::os::raw::c_void;
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::{ c_char, c_void };
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
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]
|
||||
extern "C" {
|
||||
pub fn eek_virtual_keyboard_v1_key(
|
||||
@ -24,13 +48,16 @@ pub mod c {
|
||||
|
||||
pub fn eek_virtual_keyboard_update_keymap(
|
||||
virtual_keyboard: ZwpVirtualKeyboardV1,
|
||||
keyboard: LevelKeyboard,
|
||||
keymap: *const KeyMap,
|
||||
);
|
||||
|
||||
pub fn eek_virtual_keyboard_set_modifiers(
|
||||
virtual_keyboard: ZwpVirtualKeyboardV1,
|
||||
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
|
||||
pub fn switch(
|
||||
&self,
|
||||
keycodes: &Vec<KeyCode>,
|
||||
keycode: KeyCode,
|
||||
action: PressType,
|
||||
timestamp: Timestamp,
|
||||
) {
|
||||
let keycodes_count = keycodes.len();
|
||||
for keycode in keycodes.iter() {
|
||||
let keycode = keycode - 8;
|
||||
match (action, keycodes_count) {
|
||||
// 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, _) => {},
|
||||
}
|
||||
let keycode = keycode - 8;
|
||||
unsafe {
|
||||
c::eek_virtual_keyboard_v1_key(
|
||||
self.0, timestamp.0, keycode, action.clone() as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,9 +87,12 @@ impl VirtualKeyboard {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_keymap(&self, keyboard: LevelKeyboard) {
|
||||
pub fn update_keymap(&self, keymap: &c::KeyMap) {
|
||||
unsafe {
|
||||
c::eek_virtual_keyboard_update_keymap(self.0, keyboard);
|
||||
c::eek_virtual_keyboard_update_keymap(
|
||||
self.0,
|
||||
keymap as *const c::KeyMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
|
||||
keyboard->keymap_fd, keyboard->keymap_len);
|
||||
keymap->fd, keymap->fd_len);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
10
tests/layout_erase.yaml
Normal file
10
tests/layout_erase.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
# Erase only
|
||||
views:
|
||||
base:
|
||||
- "BackSpace"
|
||||
outlines:
|
||||
default: { width: 0, height: 0 }
|
||||
buttons:
|
||||
BackSpace:
|
||||
action: erase
|
||||
@ -69,11 +69,17 @@ foreach layout : [
|
||||
|
||||
'emoji',
|
||||
]
|
||||
extra = []
|
||||
if layout == 'emoji'
|
||||
extra += ['allow_missing_return']
|
||||
endif
|
||||
|
||||
test(
|
||||
'test_layout_' + layout,
|
||||
cargo_script,
|
||||
args: ['run'] + cargo_build_flags
|
||||
+ [ '--example', 'test_layout', '--', layout],
|
||||
+ ['--example', 'test_layout', '--', layout]
|
||||
+ extra,
|
||||
workdir: meson.build_root(),
|
||||
)
|
||||
endforeach
|
||||
|
||||
Reference in New Issue
Block a user