Merge branch 'multicodepoint' into 'master'
Support submitting multi-codepoint sequences Closes #96 See merge request Librem5/squeekboard!207
This commit is contained in:
@ -31,6 +31,7 @@
|
||||
#include <glib/gprintf.h>
|
||||
|
||||
#include "eek-enumtypes.h"
|
||||
#include "eekboard/eekboard-context-service.h"
|
||||
#include "eekboard/key-emitter.h"
|
||||
#include "keymap.h"
|
||||
#include "src/keyboard.h"
|
||||
@ -93,17 +94,8 @@ set_level_from_press (LevelKeyboard *keyboard, struct squeek_key *key)
|
||||
}
|
||||
|
||||
void eek_keyboard_press_key(LevelKeyboard *keyboard, struct squeek_key *key, guint32 timestamp) {
|
||||
squeek_key_set_pressed(key, TRUE);
|
||||
keyboard->pressed_keys = g_list_prepend (keyboard->pressed_keys, key);
|
||||
|
||||
// Only take action about setting level *after* the key has taken effect, i.e. on release
|
||||
//set_level_from_press (keyboard, key);
|
||||
|
||||
// "Borrowed" from eek-context-service; doesn't influence the state but forwards the event
|
||||
|
||||
guint keycode = squeek_key_get_keycode (key);
|
||||
|
||||
emit_key_activated(keyboard->manager, keyboard, keycode, TRUE, timestamp);
|
||||
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_PRESS, timestamp);
|
||||
}
|
||||
|
||||
void eek_keyboard_release_key(LevelKeyboard *keyboard,
|
||||
@ -118,12 +110,7 @@ void eek_keyboard_release_key(LevelKeyboard *keyboard,
|
||||
}
|
||||
|
||||
set_level_from_press (keyboard, key);
|
||||
|
||||
// "Borrowed" from eek-context-service; doesn't influence the state but forwards the event
|
||||
|
||||
guint keycode = squeek_key_get_keycode (key);
|
||||
|
||||
emit_key_activated(keyboard->manager, keyboard, keycode, FALSE, timestamp);
|
||||
squeek_key_press(key, keyboard->manager->virtual_keyboard, KEY_RELEASE, timestamp);
|
||||
}
|
||||
|
||||
void level_keyboard_deinit(LevelKeyboard *self) {
|
||||
|
||||
@ -193,12 +193,7 @@ static void render_button_in_context(EekRenderer *self,
|
||||
/* render outline */
|
||||
EekBounds bounds = squeek_button_get_bounds(place->button);
|
||||
|
||||
if (active)
|
||||
outline_surface_cache = priv->active_outline_surface_cache;
|
||||
else
|
||||
outline_surface_cache = priv->outline_surface_cache;
|
||||
|
||||
outline_surface = g_hash_table_lookup (outline_surface_cache, place->button);
|
||||
outline_surface = NULL;
|
||||
|
||||
if (!outline_surface) {
|
||||
cairo_t *cr;
|
||||
@ -221,10 +216,6 @@ static void render_button_in_context(EekRenderer *self,
|
||||
cairo_restore (cr);
|
||||
|
||||
cairo_destroy (cr);
|
||||
|
||||
g_hash_table_insert (outline_surface_cache,
|
||||
(gpointer)place->button,
|
||||
outline_surface);
|
||||
}
|
||||
cairo_set_source_surface (cr, outline_surface, 0.0, 0.0);
|
||||
cairo_paint (cr);
|
||||
|
||||
@ -131,7 +131,6 @@ emit_key_activated (EekboardContextService *manager,
|
||||
*/
|
||||
SeatEmitter emitter = {0};
|
||||
emitter.virtual_keyboard = manager->virtual_keyboard;
|
||||
emitter.keymap = keyboard->keymap;
|
||||
update_modifier_info (&emitter);
|
||||
send_fake_key (&emitter, keyboard, keycode, pressed, timestamp);
|
||||
}
|
||||
|
||||
@ -14,14 +14,37 @@ fn check_layout(name: &str) {
|
||||
.expect("layout broken");
|
||||
|
||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
xkb::Keymap::new_from_string(
|
||||
|
||||
let keymap_str = layout.keymap_str
|
||||
.clone()
|
||||
.into_string().expect("Failed to decode keymap string");
|
||||
|
||||
let keymap = xkb::Keymap::new_from_string(
|
||||
&context,
|
||||
layout.keymap_str
|
||||
.clone()
|
||||
.into_string().expect("Failed to decode keymap string"),
|
||||
keymap_str.clone(),
|
||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
).expect("Failed to create keymap");
|
||||
|
||||
let state = xkb::State::new(&keymap);
|
||||
|
||||
// "Press" each button with keysyms
|
||||
for view in layout.views.values() {
|
||||
for row in &view.rows {
|
||||
for button in &row.buttons {
|
||||
let keystate = button.state.borrow();
|
||||
for keycode in &keystate.keycodes {
|
||||
match state.key_get_one_sym(*keycode) {
|
||||
xkb::KEY_NoSymbol => {
|
||||
eprintln!("{}", keymap_str);
|
||||
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> () {
|
||||
|
||||
@ -37,10 +37,3 @@ pub enum Action {
|
||||
keys: Vec<KeySym>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Contains a static description of a particular key's actions
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Symbol {
|
||||
/// The action that this key performs
|
||||
pub action: Action,
|
||||
}
|
||||
220
src/data.rs
220
src/data.rs
@ -19,6 +19,7 @@ use ::keyboard::{
|
||||
};
|
||||
use ::resources;
|
||||
use ::util::c::as_str;
|
||||
use ::util::hash_map_map;
|
||||
use ::xdg;
|
||||
|
||||
// traits, derives
|
||||
@ -131,11 +132,15 @@ fn load_layout(
|
||||
fn log_attempt_info(attempt: Option<(LoadError, DataSource)>) {
|
||||
match attempt {
|
||||
Some((
|
||||
LoadError::BadData(Error::Missing(_e)),
|
||||
DataSource::File(_file)
|
||||
LoadError::BadData(Error::Missing(e)),
|
||||
DataSource::File(file)
|
||||
)) => {
|
||||
eprintln!(
|
||||
"Tried file {:?}, but it's missing: {}",
|
||||
file, e
|
||||
);
|
||||
// Missing file, not to worry. TODO: print in debug logging level
|
||||
}
|
||||
},
|
||||
Some((e, source)) => {
|
||||
eprintln!(
|
||||
"Failed to load layout from {}: {}, trying builtin",
|
||||
@ -300,41 +305,73 @@ impl Layout {
|
||||
let button_names: HashSet<&str>
|
||||
= HashSet::from_iter(button_names);
|
||||
|
||||
let keycodes = generate_keycodes(
|
||||
button_names.iter()
|
||||
.map(|name| *name)
|
||||
.filter(|name| {
|
||||
match self.buttons.get(*name) {
|
||||
// buttons with defined action can't emit keysyms
|
||||
// and so don't need keycodes
|
||||
Some(ButtonMeta { action: Some(_), .. }) => false,
|
||||
_ => true,
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let button_states = button_names.iter().map(|name| {(
|
||||
String::from(*name),
|
||||
Rc::new(RefCell::new(KeyState {
|
||||
pressed: false,
|
||||
locked: false,
|
||||
keycode: keycodes.get(*name).map(|k| *k),
|
||||
symbol: create_symbol(
|
||||
let button_actions: Vec<(&str, ::action::Action)>
|
||||
= button_names.iter().map(|name| {(
|
||||
*name,
|
||||
create_action(
|
||||
&self.buttons,
|
||||
name,
|
||||
self.views.keys().collect()
|
||||
),
|
||||
}))
|
||||
)});
|
||||
)
|
||||
)}).collect();
|
||||
|
||||
let button_states =
|
||||
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
|
||||
let keymap: HashMap<String, u32> = generate_keycodes(
|
||||
button_actions.iter()
|
||||
.filter_map(|(_name, action)| {
|
||||
match action {
|
||||
::action::Action::Submit {
|
||||
text: _, keys,
|
||||
} => Some(keys),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|named_keysym| named_keysym.0.as_str())
|
||||
);
|
||||
|
||||
let button_states = button_actions.into_iter().map(|(name, action)| {
|
||||
let keycodes = match &action {
|
||||
::action::Action::Submit { text: _, keys } => {
|
||||
keys.iter().map(|named_keycode| {
|
||||
*keymap.get(named_keycode.0.as_str())
|
||||
.expect(
|
||||
format!(
|
||||
"keycode {} in key {} missing from keymap",
|
||||
named_keycode.0,
|
||||
name
|
||||
).as_str()
|
||||
)
|
||||
}).collect()
|
||||
},
|
||||
_ => Vec::new(),
|
||||
};
|
||||
(
|
||||
name.into(),
|
||||
KeyState {
|
||||
pressed: false,
|
||||
locked: false,
|
||||
keycodes,
|
||||
action,
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
let button_states
|
||||
= HashMap::<String, KeyState>::from_iter(
|
||||
button_states
|
||||
);
|
||||
|
||||
// TODO: generate from symbols
|
||||
let keymap_str = generate_keymap(&button_states)?;
|
||||
|
||||
let button_states_cache = hash_map_map(
|
||||
button_states,
|
||||
|name, state| {(
|
||||
name,
|
||||
Rc::new(RefCell::new(state))
|
||||
)}
|
||||
);
|
||||
|
||||
let views = HashMap::from_iter(
|
||||
self.views.iter().map(|(name, view)| {(
|
||||
name.clone(),
|
||||
@ -358,7 +395,7 @@ impl Layout {
|
||||
&self.buttons,
|
||||
&self.outlines,
|
||||
name,
|
||||
button_states.get(name.into())
|
||||
button_states_cache.get(name.into())
|
||||
.expect("Button state not created")
|
||||
.clone()
|
||||
))
|
||||
@ -380,11 +417,11 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_symbol(
|
||||
fn create_action(
|
||||
button_info: &HashMap<String, ButtonMeta>,
|
||||
name: &str,
|
||||
view_names: Vec<&String>,
|
||||
) -> ::symbol::Symbol {
|
||||
) -> ::action::Action {
|
||||
let default_meta = ButtonMeta::default();
|
||||
let symbol_meta = button_info.get(name)
|
||||
.unwrap_or(&default_meta);
|
||||
@ -410,64 +447,68 @@ fn create_symbol(
|
||||
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
|
||||
}
|
||||
|
||||
let keysym = match &symbol_meta.action {
|
||||
Some(_) => None,
|
||||
None => Some(match &symbol_meta.keysym {
|
||||
Some(keysym) => match keysym_valid(keysym.as_str()) {
|
||||
let keysyms = match &symbol_meta.action {
|
||||
// Non-submit action
|
||||
Some(_) => Vec::new(),
|
||||
// Submit action
|
||||
None => match &symbol_meta.keysym {
|
||||
// Keysym given explicitly
|
||||
Some(keysym) => vec!(match keysym_valid(keysym.as_str()) {
|
||||
true => keysym.clone(),
|
||||
false => {
|
||||
eprintln!("Keysym name invalid: {}", keysym);
|
||||
"space".into() // placeholder
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Keysyms left open to derive
|
||||
// TODO: when button name is meant diretly as xkb keysym name,
|
||||
// mark it so, e.g. with a "#"
|
||||
None => match keysym_valid(name) {
|
||||
true => String::from(name),
|
||||
false => match name.chars().count() {
|
||||
1 => format!("U{:04X}", name.chars().next().unwrap() as u32),
|
||||
// If the name is longer than 1 char,
|
||||
// then it's not a single Unicode char,
|
||||
// but was trying to be an identifier
|
||||
_ => {
|
||||
eprintln!(
|
||||
"Could not derive a valid keysym for key {}",
|
||||
name
|
||||
);
|
||||
"space".into() // placeholder
|
||||
// Button name is actually a valid xkb name
|
||||
true => vec!(String::from(name)),
|
||||
// Button name is not a valid xkb name,
|
||||
// so assume it's a literal string to be submitted
|
||||
false => {
|
||||
if name.chars().count() == 0 {
|
||||
// A name read from yaml with no valid Unicode.
|
||||
// Highly improbable, but let's be safe.
|
||||
eprintln!("Key {} doesn't have any characters", name);
|
||||
vec!("space".into()) // placeholder
|
||||
} else {
|
||||
name.chars().map(|codepoint| {
|
||||
let codepoint_string = codepoint.to_string();
|
||||
match keysym_valid(codepoint_string.as_str()) {
|
||||
true => codepoint_string,
|
||||
false => format!("U{:04X}", codepoint as u32),
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
match &symbol_meta.action {
|
||||
Some(Action::SetView(view_name)) => ::symbol::Symbol {
|
||||
action: ::symbol::Action::SetLevel(
|
||||
filter_view_name(name, view_name.clone(), &view_names)
|
||||
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
|
||||
filter_view_name(name, view_name.clone(), &view_names)
|
||||
),
|
||||
Some(Action::Locking {
|
||||
lock_view, unlock_view
|
||||
}) => ::action::Action::LockLevel {
|
||||
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
||||
unlock: filter_view_name(
|
||||
name,
|
||||
unlock_view.clone(),
|
||||
&view_names
|
||||
),
|
||||
},
|
||||
Some(Action::Locking { lock_view, unlock_view }) => ::symbol::Symbol {
|
||||
action: ::symbol::Action::LockLevel {
|
||||
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
||||
unlock: filter_view_name(
|
||||
name,
|
||||
unlock_view.clone(),
|
||||
&view_names
|
||||
),
|
||||
},
|
||||
Some(Action::ShowPrefs) => ::action::Action::Submit {
|
||||
text: None,
|
||||
keys: Vec::new(),
|
||||
},
|
||||
Some(Action::ShowPrefs) => ::symbol::Symbol {
|
||||
action: ::symbol::Action::Submit {
|
||||
text: None,
|
||||
keys: Vec::new(),
|
||||
},
|
||||
},
|
||||
None => ::symbol::Symbol {
|
||||
action: ::symbol::Action::Submit {
|
||||
text: None,
|
||||
keys: vec!(
|
||||
::symbol::KeySym(keysym.unwrap()),
|
||||
),
|
||||
},
|
||||
None => ::action::Action::Submit {
|
||||
text: None,
|
||||
keys: keysyms.into_iter().map(::action::KeySym).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -638,7 +679,24 @@ mod tests {
|
||||
::layout::Label::Text(CString::new("test").unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// Test multiple codepoints
|
||||
#[test]
|
||||
fn test_layout_unicode_multi() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
.rows[0]
|
||||
.buttons[0]
|
||||
.state.borrow()
|
||||
.keycodes.len(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_fallback() {
|
||||
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
||||
@ -650,7 +708,7 @@ mod tests {
|
||||
/// First fallback should be to builtin, not to FALLBACK_LAYOUT_NAME
|
||||
#[test]
|
||||
fn fallbacks_order() {
|
||||
let (layout, source, _failure) = load_layout(
|
||||
let (_layout, source, _failure) = load_layout(
|
||||
"nb",
|
||||
Some(PathBuf::from("tests"))
|
||||
);
|
||||
@ -674,7 +732,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_key_unicode() {
|
||||
assert_eq!(
|
||||
create_symbol(
|
||||
create_action(
|
||||
&hashmap!{
|
||||
".".into() => ButtonMeta {
|
||||
icon: None,
|
||||
@ -687,11 +745,9 @@ mod tests {
|
||||
".",
|
||||
Vec::new()
|
||||
),
|
||||
::symbol::Symbol {
|
||||
action: ::symbol::Action::Submit {
|
||||
text: None,
|
||||
keys: vec!(::symbol::KeySym("U002E".into())),
|
||||
},
|
||||
::action::Action::Submit {
|
||||
text: None,
|
||||
keys: vec!(::action::KeySym("U002E".into())),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,26 +1,21 @@
|
||||
#ifndef __KEYBOARD_H
|
||||
#define __KEYBOARD_H
|
||||
|
||||
#include "stdbool.h"
|
||||
#include "inttypes.h"
|
||||
#include "stdbool.h"
|
||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||
|
||||
struct squeek_key;
|
||||
|
||||
struct squeek_key *squeek_key_new(uint32_t keycode);
|
||||
void squeek_key_free(struct squeek_key *key);
|
||||
void squeek_key_add_symbol(struct squeek_key* key,
|
||||
const char *element_name,
|
||||
const char *text, uint32_t keyval,
|
||||
const char *label, const char *icon,
|
||||
const char *tooltip);
|
||||
uint32_t squeek_key_is_pressed(struct squeek_key *key);
|
||||
void squeek_key_set_pressed(struct squeek_key *key, uint32_t pressed);
|
||||
uint32_t squeek_key_is_locked(struct squeek_key *key);
|
||||
void squeek_key_set_locked(struct squeek_key *key, uint32_t pressed);
|
||||
uint32_t squeek_key_get_keycode(struct squeek_key *key);
|
||||
void squeek_key_set_keycode(struct squeek_key *key, uint32_t keycode);
|
||||
uint32_t squeek_key_equal(struct squeek_key* key, struct squeek_key* key1);
|
||||
|
||||
struct squeek_symbol *squeek_key_get_symbol(struct squeek_key* key);
|
||||
const char* squeek_key_to_keymap_entry(const char *key_name, struct squeek_key *key);
|
||||
enum key_press {
|
||||
KEY_RELEASE = 0,
|
||||
KEY_PRESS = 1,
|
||||
};
|
||||
|
||||
void squeek_key_press(struct squeek_key *key, struct zwp_virtual_keyboard_v1*, enum key_press press, uint32_t timestamp);
|
||||
#endif
|
||||
|
||||
214
src/keyboard.rs
214
src/keyboard.rs
@ -1,13 +1,12 @@
|
||||
/*! State of the emulated keyboard and keys */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use ::symbol::{ Symbol, Action };
|
||||
|
||||
use ::action::Action;
|
||||
|
||||
use std::io::Write;
|
||||
use std::iter::{ FromIterator, IntoIterator };
|
||||
@ -16,21 +15,28 @@ use std::iter::{ FromIterator, IntoIterator };
|
||||
pub mod c {
|
||||
use super::*;
|
||||
use ::util::c;
|
||||
use ::util::c::as_cstr;
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
pub type CKeyState = c::Wrapped<KeyState>;
|
||||
|
||||
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
|
||||
#[repr(transparent)]
|
||||
pub struct ZwpVirtualKeyboardV1(*const c_void);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_free(key: CKeyState) {
|
||||
unsafe { key.unwrap() }; // reference dropped
|
||||
extern "C" {
|
||||
/// Checks if point falls within bounds,
|
||||
/// which are relative to origin and rotated by angle (I think)
|
||||
pub fn eek_virtual_keyboard_v1_key(
|
||||
virtual_keyboard: *mut ZwpVirtualKeyboardV1,
|
||||
timestamp: u32,
|
||||
keycode: u32,
|
||||
press: u32,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
|
||||
|
||||
/// Compares pointers to the data
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
@ -44,15 +50,7 @@ pub mod c {
|
||||
//let key = unsafe { Rc::from_raw(key.0) };
|
||||
return key.to_owned().pressed as u32;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_set_pressed(key: CKeyState, pressed: u32) {
|
||||
let key = key.clone_ref();
|
||||
let mut key = key.borrow_mut();
|
||||
key.pressed = pressed != 0;
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_is_locked(key: CKeyState) -> u32 {
|
||||
@ -66,73 +64,45 @@ pub mod c {
|
||||
let mut key = key.borrow_mut();
|
||||
key.locked = locked != 0;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_get_keycode(key: CKeyState) -> u32 {
|
||||
return key.to_owned().keycode.unwrap_or(0u32);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_to_keymap_entry(
|
||||
key_name: *const c_char,
|
||||
fn squeek_key_press(
|
||||
key: CKeyState,
|
||||
) -> *const c_char {
|
||||
let key_name = as_cstr(&key_name)
|
||||
.expect("Missing key name")
|
||||
.to_str()
|
||||
.expect("Bad key name");
|
||||
virtual_keyboard: *mut ZwpVirtualKeyboardV1,
|
||||
press: u32,
|
||||
timestamp: u32,
|
||||
) {
|
||||
let key = key.clone_ref();
|
||||
let mut key = key.borrow_mut();
|
||||
key.pressed = press != 0;
|
||||
|
||||
let symbol_name = match key.to_owned().symbol.action {
|
||||
Action::Submit { text: Some(text), .. } => {
|
||||
Some(
|
||||
text.clone()
|
||||
.into_string().expect("Bad symbol")
|
||||
)
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let inner = match symbol_name {
|
||||
Some(name) => format!("[ {} ]", name),
|
||||
_ => format!("[ ]"),
|
||||
};
|
||||
|
||||
CString::new(format!(" key <{}> {{ {} }};\n", key_name, inner))
|
||||
.expect("Couldn't convert string")
|
||||
.into_raw()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_key_get_action_name(
|
||||
key_name: *const c_char,
|
||||
key: CKeyState,
|
||||
) -> *const c_char {
|
||||
let key_name = as_cstr(&key_name)
|
||||
.expect("Missing key name")
|
||||
.to_str()
|
||||
.expect("Bad key name");
|
||||
|
||||
let symbol_name = match key.to_owned().symbol.action {
|
||||
Action::Submit { text: Some(text), .. } => {
|
||||
Some(
|
||||
text.clone()
|
||||
.into_string().expect("Bad symbol text")
|
||||
)
|
||||
},
|
||||
_ => None
|
||||
};
|
||||
|
||||
let inner = match symbol_name {
|
||||
Some(name) => format!("[ {} ]", name),
|
||||
_ => format!("[ ]"),
|
||||
};
|
||||
|
||||
CString::new(format!(" key <{}> {{ {} }};\n", key_name, inner))
|
||||
.expect("Couldn't convert string")
|
||||
.into_raw()
|
||||
let keycodes_count = key.keycodes.len();
|
||||
for keycode in key.keycodes.iter() {
|
||||
let keycode = keycode - 8;
|
||||
match (key.pressed, keycodes_count) {
|
||||
// Pressing a key made out of a single keycode is simple:
|
||||
// press on press, release on release.
|
||||
(_, 1) => unsafe {
|
||||
eek_virtual_keyboard_v1_key(
|
||||
virtual_keyboard, timestamp, keycode, press
|
||||
);
|
||||
},
|
||||
// A key made of multiple keycodes
|
||||
// has to submit them one after the other
|
||||
(true, _) => unsafe {
|
||||
eek_virtual_keyboard_v1_key(
|
||||
virtual_keyboard, timestamp, keycode, 1
|
||||
);
|
||||
eek_virtual_keyboard_v1_key(
|
||||
virtual_keyboard, timestamp, keycode, 0
|
||||
);
|
||||
},
|
||||
// Design choice here: submit multiple all at press time
|
||||
// and do nothing at release time
|
||||
(false, _) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,8 +110,10 @@ pub mod c {
|
||||
pub struct KeyState {
|
||||
pub pressed: bool,
|
||||
pub locked: bool,
|
||||
pub keycode: Option<u32>,
|
||||
pub symbol: Symbol,
|
||||
/// A cache of raw keycodes derived from Action::Sumbit given a keymap
|
||||
pub keycodes: Vec<u32>,
|
||||
/// Static description of what the key does when pressed or released
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
/// Generates a mapping where each key gets a keycode, starting from 8
|
||||
@ -178,7 +150,7 @@ impl From<io::Error> for FormattingError {
|
||||
|
||||
/// Generates a de-facto single level keymap. TODO: actually drop second level
|
||||
pub fn generate_keymap(
|
||||
keystates: &HashMap::<String, Rc<RefCell<KeyState>>>
|
||||
keystates: &HashMap::<String, KeyState>
|
||||
) -> Result<String, FormattingError> {
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
writeln!(
|
||||
@ -191,24 +163,17 @@ pub fn generate_keymap(
|
||||
)?;
|
||||
|
||||
for (name, state) in keystates.iter() {
|
||||
let state = state.borrow();
|
||||
if let ::symbol::Action::Submit { text: _, keys } = &state.symbol.action {
|
||||
match keys.len() {
|
||||
0 => eprintln!("Key {} has no keysyms", name),
|
||||
a => {
|
||||
// TODO: don't ignore any keysyms
|
||||
if a > 1 {
|
||||
eprintln!("Key {} multiple keysyms", name);
|
||||
}
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
if let Action::Submit { text: _, keys } = &state.action {
|
||||
if let 0 = keys.len() { eprintln!("Key {} has no keysyms", name); };
|
||||
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
<{}> = {};",
|
||||
keys[0].0,
|
||||
state.keycode.unwrap()
|
||||
)?;
|
||||
},
|
||||
};
|
||||
named_keysym.0,
|
||||
keycode,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,8 +189,8 @@ pub fn generate_keymap(
|
||||
)?;
|
||||
|
||||
for (name, state) in keystates.iter() {
|
||||
if let ::symbol::Action::Submit { text: _, keys } = &state.borrow().symbol.action {
|
||||
if let Some(keysym) = keys.iter().next() {
|
||||
if let Action::Submit { text: _, keys } = &state.action {
|
||||
for keysym in keys.iter() {
|
||||
write!(
|
||||
buf,
|
||||
"
|
||||
@ -255,5 +220,44 @@ pub fn generate_keymap(
|
||||
}};"
|
||||
)?;
|
||||
|
||||
//println!("{}", String::from_utf8(buf.clone()).unwrap());
|
||||
String::from_utf8(buf).map_err(FormattingError::Utf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use ::action::KeySym;
|
||||
|
||||
#[test]
|
||||
fn test_keymap_multi() {
|
||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
|
||||
let keymap_str = generate_keymap(&hashmap!{
|
||||
"ac".into() => KeyState {
|
||||
action: Action::Submit {
|
||||
text: None,
|
||||
keys: vec!(KeySym("a".into()), KeySym("c".into())),
|
||||
},
|
||||
keycodes: vec!(9, 10),
|
||||
locked: false,
|
||||
pressed: false,
|
||||
},
|
||||
}).unwrap();
|
||||
|
||||
let keymap = xkb::Keymap::new_from_string(
|
||||
&context,
|
||||
keymap_str.clone(),
|
||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
).expect("Failed to create keymap");
|
||||
|
||||
let state = xkb::State::new(&keymap);
|
||||
|
||||
assert_eq!(state.key_get_one_sym(9), xkb::KEY_a);
|
||||
assert_eq!(state.key_get_one_sym(10), xkb::KEY_c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,9 +23,9 @@ use std::ffi::CString;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
use ::keyboard::*;
|
||||
use ::action::Action;
|
||||
use ::float_ord::FloatOrd;
|
||||
use ::symbol::*;
|
||||
use ::keyboard::*;
|
||||
|
||||
/// Gathers stuff defined in C or called by C
|
||||
pub mod c {
|
||||
@ -144,18 +144,7 @@ pub mod c {
|
||||
let button = unsafe { &*button };
|
||||
::keyboard::c::CKeyState::wrap(button.state.clone())
|
||||
}
|
||||
|
||||
/// Really should just return the label
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_button_get_symbol(
|
||||
button: *const ::layout::Button,
|
||||
) -> *const Symbol {
|
||||
let button = unsafe { &*button };
|
||||
let state = button.state.borrow();
|
||||
&state.symbol as *const Symbol
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_button_get_label(
|
||||
@ -264,6 +253,8 @@ pub mod c {
|
||||
angle: i32
|
||||
) -> u32;
|
||||
|
||||
// CKeyState is safe to pass to C as long as nothing dereferences it
|
||||
#[allow(improper_ctypes)]
|
||||
pub fn eek_keyboard_set_key_locked(
|
||||
keyboard: *mut LevelKeyboard,
|
||||
key: ::keyboard::c::CKeyState,
|
||||
@ -279,11 +270,11 @@ pub mod c {
|
||||
) {
|
||||
let layout = unsafe { &mut *layout };
|
||||
|
||||
let view_name = match key.to_owned().symbol.action {
|
||||
::symbol::Action::SetLevel(name) => {
|
||||
let view_name = match key.to_owned().action {
|
||||
Action::SetLevel(name) => {
|
||||
Some(name.clone())
|
||||
},
|
||||
::symbol::Action::LockLevel { lock, unlock } => {
|
||||
Action::LockLevel { lock, unlock } => {
|
||||
let locked = {
|
||||
let key = key.clone_ref();
|
||||
let mut key = key.borrow_mut();
|
||||
@ -320,9 +311,7 @@ pub mod c {
|
||||
/// Sets button and row sizes according to their contents.
|
||||
#[no_mangle]
|
||||
pub extern "C"
|
||||
fn squeek_layout_place_contents(
|
||||
layout: *mut Layout,
|
||||
) {
|
||||
fn squeek_layout_place_contents(layout: *mut Layout) {
|
||||
let layout = unsafe { &mut *layout };
|
||||
for view in layout.views.values_mut() {
|
||||
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
|
||||
@ -495,10 +484,8 @@ pub mod c {
|
||||
Rc::new(RefCell::new(::keyboard::KeyState {
|
||||
pressed: false,
|
||||
locked: false,
|
||||
keycode: None,
|
||||
symbol: Symbol {
|
||||
action: Action::SetLevel("default".into()),
|
||||
}
|
||||
keycodes: Vec::new(),
|
||||
action: Action::SetLevel("default".into()),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use] // only for tests
|
||||
extern crate maplit;
|
||||
extern crate serde;
|
||||
extern crate xkbcommon;
|
||||
@ -11,6 +12,6 @@ pub mod imservice;
|
||||
mod keyboard;
|
||||
mod layout;
|
||||
mod resources;
|
||||
mod symbol;
|
||||
mod action;
|
||||
mod util;
|
||||
mod xdg;
|
||||
|
||||
15
src/util.rs
15
src/util.rs
@ -1,3 +1,8 @@
|
||||
/*! Assorted helpers */
|
||||
use std::collections::HashMap;
|
||||
|
||||
use std::iter::FromIterator;
|
||||
|
||||
pub mod c {
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::{ CStr, CString };
|
||||
@ -101,3 +106,13 @@ pub mod c {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_map_map<K, V, F, K1, V1>(map: HashMap<K, V>, mut f: F)
|
||||
-> HashMap<K1, V1>
|
||||
where F: FnMut(K, V) -> (K1, V1),
|
||||
K1: std::cmp::Eq + std::hash::Hash
|
||||
{
|
||||
HashMap::from_iter(
|
||||
map.into_iter().map(|(key, value)| f(key, value))
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
#include "wayland.h"
|
||||
|
||||
struct squeek_wayland *squeek_wayland = NULL;
|
||||
|
||||
// The following functions only exist
|
||||
// to create linkable symbols out of inline functions,
|
||||
// because those are not directly callable from Rust
|
||||
|
||||
void
|
||||
eek_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t time, uint32_t key, uint32_t state) {
|
||||
zwp_virtual_keyboard_v1_key(zwp_virtual_keyboard_v1, time, key, state);
|
||||
}
|
||||
|
||||
17
tests/layout_key3.yaml
Normal file
17
tests/layout_key3.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
# punctuation
|
||||
row_spacing: 0
|
||||
button_spacing: 0
|
||||
|
||||
bounds:
|
||||
x: 0
|
||||
y: 0
|
||||
width: 0
|
||||
height: 0
|
||||
views:
|
||||
base:
|
||||
- "か゚" # 2 codepoints
|
||||
outlines:
|
||||
default:
|
||||
bounds: { x: 0, y: 0, width: 0, height: 0 }
|
||||
|
||||
Reference in New Issue
Block a user