keymaps: Use multiple key maps, each within the limit of what Xorg can accept.
Key maps are switched on key press whenever needed.
This commit is contained in:
@ -28,19 +28,12 @@
|
|||||||
#include <sys/random.h> // TODO: this is Linux-specific
|
#include <sys/random.h> // TODO: this is Linux-specific
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
|
||||||
#include "eek-keyboard.h"
|
#include "eek-keyboard.h"
|
||||||
|
|
||||||
|
/// External linkage for Rust.
|
||||||
static void eek_key_map_deinit(struct keymap *self) {
|
/// The corresponding deinit is implemented in vkeyboard::KeyMap::drop
|
||||||
if (self->fd < 0) {
|
struct keymap squeek_key_map_from_str(const char *keymap_str) {
|
||||||
g_error("Deinit called multiple times on KeyMap");
|
|
||||||
} else {
|
|
||||||
close(self->fd);
|
|
||||||
}
|
|
||||||
self->fd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct keymap eek_key_map_from_str(const char *keymap_str) {
|
|
||||||
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");
|
||||||
@ -91,7 +84,6 @@ static struct keymap eek_key_map_from_str(const char *keymap_str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void level_keyboard_free(LevelKeyboard *self) {
|
void level_keyboard_free(LevelKeyboard *self) {
|
||||||
eek_key_map_deinit(&self->keymap);
|
|
||||||
squeek_layout_free(self->layout);
|
squeek_layout_free(self->layout);
|
||||||
g_free(self);
|
g_free(self);
|
||||||
}
|
}
|
||||||
@ -104,7 +96,5 @@ level_keyboard_new (struct squeek_layout *layout)
|
|||||||
g_error("Failed to create a keyboard");
|
g_error("Failed to create a keyboard");
|
||||||
}
|
}
|
||||||
keyboard->layout = layout;
|
keyboard->layout = layout;
|
||||||
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
|
|
||||||
keyboard->keymap = eek_key_map_from_str(keymap_str);
|
|
||||||
return keyboard;
|
return keyboard;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,7 +41,7 @@ struct keymap {
|
|||||||
/// Keyboard state holder
|
/// Keyboard state holder
|
||||||
struct _LevelKeyboard {
|
struct _LevelKeyboard {
|
||||||
struct squeek_layout *layout; // owned
|
struct squeek_layout *layout; // owned
|
||||||
struct keymap keymap; // owned
|
// FIXME: This no longer needs to exist, keymap was folded into layout.
|
||||||
};
|
};
|
||||||
typedef struct _LevelKeyboard LevelKeyboard;
|
typedef struct _LevelKeyboard LevelKeyboard;
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/data.rs
20
src/data.rs
@ -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,7 +382,7 @@ impl Layout {
|
|||||||
)
|
)
|
||||||
)}).collect();
|
)}).collect();
|
||||||
|
|
||||||
let symbolmap: HashMap<String, u32> = generate_keycodes(
|
let symbolmap: HashMap<String, KeyCode> = generate_keycodes(
|
||||||
extract_symbol_names(&button_actions)
|
extract_symbol_names(&button_actions)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -391,7 +391,7 @@ impl Layout {
|
|||||||
let keycodes = match &action {
|
let keycodes = match &action {
|
||||||
::action::Action::Submit { text: _, keys } => {
|
::action::Action::Submit { text: _, keys } => {
|
||||||
keys.iter().map(|named_keysym| {
|
keys.iter().map(|named_keysym| {
|
||||||
*symbolmap.get(named_keysym.0.as_str())
|
symbolmap.get(named_keysym.0.as_str())
|
||||||
.expect(
|
.expect(
|
||||||
format!(
|
format!(
|
||||||
"keysym {} in key {} missing from symbol map",
|
"keysym {} in key {} missing from symbol map",
|
||||||
@ -399,11 +399,13 @@ impl Layout {
|
|||||||
name
|
name
|
||||||
).as_str()
|
).as_str()
|
||||||
)
|
)
|
||||||
|
.clone()
|
||||||
}).collect()
|
}).collect()
|
||||||
},
|
},
|
||||||
action::Action::Erase => vec![
|
action::Action::Erase => vec![
|
||||||
*symbolmap.get("BackSpace")
|
symbolmap.get("BackSpace")
|
||||||
.expect(&format!("BackSpace missing from symbol map")),
|
.expect(&format!("BackSpace missing from symbol map"))
|
||||||
|
.clone(),
|
||||||
],
|
],
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
@ -418,7 +420,7 @@ impl Layout {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let keymap_str = match generate_keymap(symbolmap) {
|
let keymaps = match generate_keymaps(symbolmap) {
|
||||||
Err(e) => { return (Err(e), warning_handler) },
|
Err(e) => { return (Err(e), warning_handler) },
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
};
|
};
|
||||||
@ -482,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,
|
||||||
@ -876,7 +878,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out.views["base"].1
|
out.views["base"].1
|
||||||
.get_rows()[0].1
|
.get_rows()[0].1
|
||||||
.buttons[0].1
|
.get_buttons()[0].1
|
||||||
.state.borrow()
|
.state.borrow()
|
||||||
.keycodes.len(),
|
.keycodes.len(),
|
||||||
1
|
1
|
||||||
|
|||||||
123
src/keyboard.rs
123
src/keyboard.rs
@ -5,10 +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 ::util;
|
||||||
|
|
||||||
// Traits
|
// Traits
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
@ -20,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
|
||||||
@ -92,12 +100,15 @@ fn sorted<'a, I: Iterator<Item=String>>(
|
|||||||
/// which the compositor likes to discard
|
/// which the compositor likes to discard
|
||||||
pub fn generate_keycodes<'a, C: IntoIterator<Item=String>>(
|
pub fn generate_keycodes<'a, C: IntoIterator<Item=String>>(
|
||||||
key_names: C,
|
key_names: C,
|
||||||
) -> HashMap<String, u32> {
|
) -> HashMap<String, KeyCode> {
|
||||||
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())
|
sorted(key_names.into_iter())
|
||||||
.map(|name| name)
|
.zip(util::cycle_count(9..255))
|
||||||
.zip(9..)
|
.map(|(name, (code, keymap_idx))| (
|
||||||
|
String::from(name),
|
||||||
|
KeyCode { code, keymap_idx },
|
||||||
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,10 +133,50 @@ 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/
|
||||||
|
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.
|
||||||
/// Key codes must not repeat and should remain between 9 and 255.
|
/// Key codes must not repeat and must remain between 9 and 255.
|
||||||
pub fn generate_keymap(
|
fn generate_keymap(
|
||||||
symbolmap: HashMap::<String, KeyCode>,
|
symbolmap: &SingleKeyMap,
|
||||||
) -> Result<String, FormattingError> {
|
) -> Result<String, FormattingError> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
writeln!(
|
writeln!(
|
||||||
@ -134,14 +185,21 @@ pub fn generate_keymap(
|
|||||||
|
|
||||||
xkb_keycodes \"squeekboard\" {{
|
xkb_keycodes \"squeekboard\" {{
|
||||||
minimum = 8;
|
minimum = 8;
|
||||||
maximum = 999;"
|
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();
|
||||||
|
|
||||||
// Xorg can only consume up to 255 keys, so this may not work in Xwayland.
|
// Xorg can only consume up to 255 keys, so this may not work in Xwayland.
|
||||||
// Two possible solutions:
|
// Two possible solutions:
|
||||||
// - use levels to cram multiple characters into one key
|
// - use levels to cram multiple characters into one key
|
||||||
// - swap layouts on key presses
|
// - swap layouts on key presses
|
||||||
for keycode in symbolmap.values() {
|
for (_name, keycode) in &pairs {
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"
|
"
|
||||||
@ -160,7 +218,7 @@ pub fn generate_keymap(
|
|||||||
"
|
"
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for (name, keycode) in symbolmap.iter() {
|
for (name, keycode) in pairs {
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"
|
"
|
||||||
@ -218,13 +276,14 @@ mod tests {
|
|||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
#[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();
|
||||||
"a".into() => 9,
|
|
||||||
"c".into() => 10,
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
let keymap = xkb::Keymap::new_from_string(
|
let keymap = xkb::Keymap::new_from_string(
|
||||||
&context,
|
&context,
|
||||||
@ -238,4 +297,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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*);
|
||||||
|
|
||||||
|
|||||||
@ -237,13 +237,6 @@ pub mod c {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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"
|
||||||
fn squeek_layout_get_kind(layout: *const Layout) -> u32 {
|
fn squeek_layout_get_kind(layout: *const Layout) -> u32 {
|
||||||
@ -686,8 +679,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.
|
||||||
@ -703,7 +696,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,7 +719,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,
|
||||||
}
|
}
|
||||||
@ -1200,7 +1193,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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 => {
|
||||||
|
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(
|
self.virtual_keyboard.switch(
|
||||||
keycodes,
|
keycode.clone(),
|
||||||
PressType::Pressed,
|
PressType::Pressed,
|
||||||
time,
|
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) => {
|
||||||
|
let keycodes_count = keycodes.len();
|
||||||
|
match keycodes_count {
|
||||||
|
1 => {
|
||||||
|
let keycode = &keycodes[0];
|
||||||
|
self.select_keymap(keycode.keymap_idx, time);
|
||||||
self.virtual_keyboard.switch(
|
self.virtual_keyboard.switch(
|
||||||
&keycodes,
|
keycode.code,
|
||||||
PressType::Released,
|
PressType::Released,
|
||||||
time,
|
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) {
|
||||||
|
if self.keymap_idx != Some(idx) {
|
||||||
|
self.keymap_idx = Some(idx);
|
||||||
self.clear_all_modifiers();
|
self.clear_all_modifiers();
|
||||||
self.release_all_virtual_keys(time);
|
self.release_all_virtual_keys(time);
|
||||||
self.virtual_keyboard.update_keymap(keyboard);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/tests.rs
54
src/tests.rs
@ -40,21 +40,29 @@ pub fn check_layout_file(path: &str) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_sym_presence(
|
fn check_sym_in_keymap(state: &xkb::State, sym_name: &str) -> bool {
|
||||||
state: &xkb::State,
|
|
||||||
sym_name: &str,
|
|
||||||
handler: &mut dyn logging::Handler,
|
|
||||||
) {
|
|
||||||
let sym = xkb::keysym_from_name(sym_name, xkb::KEYSYM_NO_FLAGS);
|
let sym = xkb::keysym_from_name(sym_name, xkb::KEYSYM_NO_FLAGS);
|
||||||
if sym == xkb::KEY_NoSymbol {
|
if sym == xkb::KEY_NoSymbol {
|
||||||
panic!(format!("Entered invalid keysym: {}", sym_name));
|
panic!(format!("Entered invalid keysym: {}", sym_name));
|
||||||
}
|
}
|
||||||
let map = state.get_keymap();
|
let map = state.get_keymap();
|
||||||
let range = map.min_keycode()..=map.max_keycode();
|
let range = map.min_keycode()..=map.max_keycode();
|
||||||
let found = range.flat_map(|code| state.key_get_syms(code))
|
range.flat_map(|code| state.key_get_syms(code))
|
||||||
.find(|s| **s == sym)
|
.find(|s| **s == sym)
|
||||||
.is_some();
|
.is_some()
|
||||||
if !found {
|
}
|
||||||
|
|
||||||
|
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(
|
handler.handle(
|
||||||
logging::Level::Surprise,
|
logging::Level::Surprise,
|
||||||
&format!("There's no way to input the keysym {} on this layout", sym_name),
|
&format!("There's no way to input the keysym {} on this layout", sym_name),
|
||||||
@ -72,25 +80,26 @@ fn check_layout(layout: Layout, allow_missing_return: bool) {
|
|||||||
|
|
||||||
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 context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
let keymap_str = keymap_str
|
||||||
let keymap_str = layout.keymap_str
|
|
||||||
.clone()
|
.clone()
|
||||||
.into_string().expect("Failed to decode keymap string");
|
.into_string().expect("Failed to decode keymap string");
|
||||||
|
|
||||||
let keymap = xkb::Keymap::new_from_string(
|
let keymap = xkb::Keymap::new_from_string(
|
||||||
&context,
|
&context,
|
||||||
keymap_str.clone(),
|
keymap_str.clone(),
|
||||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
).expect("Failed to create keymap");
|
).expect("Failed to create keymap");
|
||||||
|
xkb::State::new(&keymap)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let state = xkb::State::new(&keymap);
|
check_sym_presence(&xkb_states, "BackSpace", &mut handler);
|
||||||
|
|
||||||
check_sym_presence(&state, "BackSpace", &mut handler);
|
|
||||||
let mut printer = logging::Print;
|
let mut printer = logging::Print;
|
||||||
check_sym_presence(
|
check_sym_presence(
|
||||||
&state,
|
&xkb_states,
|
||||||
"Return",
|
"Return",
|
||||||
if allow_missing_return { &mut printer }
|
if allow_missing_return { &mut printer }
|
||||||
else { &mut handler },
|
else { &mut handler },
|
||||||
@ -102,10 +111,19 @@ fn check_layout(layout: Layout, allow_missing_return: bool) {
|
|||||||
for (_x, button) in row.get_buttons() {
|
for (_x, button) in row.get_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,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|||||||
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))
|
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)]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,47 @@
|
|||||||
/*! 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 {
|
||||||
|
squeek_key_map_from_str(s.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for KeyMap {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
close(self.fd as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
// From libc, to let KeyMap get deallocated.
|
||||||
|
fn close(fd: u32);
|
||||||
|
|
||||||
pub fn eek_virtual_keyboard_v1_key(
|
pub fn eek_virtual_keyboard_v1_key(
|
||||||
virtual_keyboard: ZwpVirtualKeyboardV1,
|
virtual_keyboard: ZwpVirtualKeyboardV1,
|
||||||
timestamp: u32,
|
timestamp: u32,
|
||||||
@ -24,13 +51,15 @@ 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 squeek_key_map_from_str(keymap_str: *const c_char) -> KeyMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,35 +70,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: &[KeyCode],
|
keycode: KeyCode,
|
||||||
action: PressType,
|
action: PressType,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
) {
|
) {
|
||||||
let keycodes_count = keycodes.len();
|
|
||||||
for keycode in keycodes.iter() {
|
|
||||||
let keycode = keycode - 8;
|
let keycode = keycode - 8;
|
||||||
match (action, keycodes_count) {
|
unsafe {
|
||||||
// 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(
|
c::eek_virtual_keyboard_v1_key(
|
||||||
self.0, timestamp.0, keycode, action.clone() as u32
|
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 +89,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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.fd_len);
|
keymap->fd, keymap->fd_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
Reference in New Issue
Block a user