Compare commits

..

2 Commits

Author SHA1 Message Date
3d2f9f3d9e Document changes and release 1.9.3.0pureos0.1 2020-09-22 12:43:03 +02:00
a20ab70984 debian: Add gbp.conf 2020-09-22 12:42:30 +02:00
29 changed files with 310 additions and 746 deletions

View File

@ -19,7 +19,6 @@ 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]

View File

@ -30,42 +30,29 @@ Building
### Dependencies
See `.gitlab-ci.yml` or run `apt-get build-dep .`
See `.gitlab-ci.yml`.
### Build from git repo
```bash
```
$ git clone https://source.puri.sm/Librem5/squeekboard.git
$ cd squeekboard
$ mkdir _build
$ meson _build/
$ cd _build
$ ninja
$ mkdir ../build
$ meson ../build/
$ cd ../build
$ ninja test
$ ninja install
```
To run tests use `ninja test`. To install squeekboard run `ninja install`.
Running
-------
```bash
```
$ phoc # if no compatible Wayland compositor is running yet
$ cd ../build/
$ src/squeekboard
```
Squeekboard honors the gnome "screen-keyboard-enabled" setting. Either enable this through gnome-settings under accessibility or run:
```bash
$ gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true
```
To make the keyboard show you can use either an application that does so automatically, like a text editor or `python3 ./tests/entry.py`, or you can manually trigger it with:
```bash
busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b true
```
Developing
----------

View File

@ -1,89 +0,0 @@
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 59, height: 52 }
spaceline: { width: 140, height: 52 }
special: { width: 44, height: 52 }
views:
base:
- "a z e r t y u i o p"
- "q s d f g h j k l m"
- "Shift_L w x c v b n . BackSpace"
- "show_numbers preferences space show_eschars Return"
upper:
- "A Z E R T Y U I O P"
- "Q S D F G H J K L M"
- "Shift_L W X C V B N , BackSpace"
- "show_numbers preferences space show_eschars Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # € % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space show_eschars Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ $ ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters preferences space show_eschars Return"
eschars:
- "à â ç é è ê î ô ù û"
- "À Â Ç É È Ê Î Ô Ù Û"
- "show_numbers_from_symbols æ œ ä ë ï ö ü BackSpace"
- "show_letters preferences space show_eschars Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: erase
preferences:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "wide"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
show_eschars:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "âÂ"
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
"\"":
keysym: "quotedbl"

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
squeekboard (1.9.3.0pureos0.1) byzantium; urgency=medium
* Upload to byzantium
-- Guido Günther <agx@sigxcpu.org> Tue, 22 Sep 2020 12:42:41 +0200
squeekboard (1.9.3) amber-phone; urgency=medium
[ Björn Tantau ]

7
debian/gbp.conf vendored Normal file
View File

@ -0,0 +1,7 @@
[DEFAULT]
debian-branch = pureos/byzantium
debian-tag = pureos/%(version)s
debian-tag-msg = %(pkg)s %(version)s
[tag]
sign-tags = true

View File

@ -13,16 +13,16 @@ The overarching principle of *squeekboard* is to empower users.
Software is primarily meant to solve problems of its users. Often in the quest to make software better, a hard distinction is made between the developer, who becomes the creator, and the user, who takes the role of the consumer, without direct influence on the software they use.
This project aims to give users the power to make the software work for them by blurring the lines between users and developers.
Notwithstanding its current state, *squeekboard* must be structured in a way that provides users a gradual way to gain more experience and power to adjust it. It must be easy, in order of importance:
Nonwithstanding its current state, *squeekboard* must be structured in a way that provides users a gradual way to gain more experience and power to adjust it. It must be easy, in order of importance:
- to use the software,
- to modify its resources,
- to change its behavior,
- to change its behaviour,
- to contribute upstream.
To give an idea of what it means in practice, those are some examples of what has been important for *squeekboard* so far:
- being quick and usable,
- being quick and useable,
- allowing local overrides of resources and config,
- storing resources and config as editable, standard files,
- having complete, up to date documentation of interfaces,
@ -33,7 +33,7 @@ To give an idea of what it means in practice, those are some examples of what ha
- having code that is [simple and obvious](https://www.python.org/dev/peps/pep-0020/),
- having an easy process of testing and accepting contributions.
You may notice that they are ordered roughly from "user-focused" to "maintainer-focused". While good properties are desired, sometimes they conflict, and maintainers should give additional weight to those benefiting the user compared to those benefiting regular contributors.
You may notice that they are ordered roughly from "user-focused" to "maintainer-focused". While good properties are desired, sometimes they conflict, and maintainers should give additional weight to those benefitting the user compared to those benefitting regular contributors.
Sending patches
---------------
@ -43,7 +43,7 @@ By submitting a change to this project, you agree to license it under the [GPL l
Development environment
-----------------------
*Squeekboard* is regularly built and tested on [the development environment](https://developer.puri.sm/Librem5/Development_Environment.html).
*Squeekboard* is regularly built and tested on [the develpment environment](https://developer.puri.sm/Librem5/Development_Environment.html).
Recent Fedora releases are likely to be tested as well.
@ -162,7 +162,7 @@ Maintenance
Squeekboard uses Rust & Cargo for some of its dependencies.
Use the `cargo.sh` script for maintaining the Cargo part of the build. The script takes the usual Cargo commands, after the first 2 positional arguments: source directory, and output artifact. So, `cargo test` becomes:
Use the `cargo.sh` script for maintaining the Cargo part of the build. The script takes the usual Cargo commands, after the first 2 positionsl arguments: source directory, and output artifact. So, `cargo test` becomes:
```
cd build_dir

View File

@ -360,10 +360,6 @@ eek_gtk_keyboard_init (EekGtkKeyboard *self)
priv->event = lfb_event_new ("button-pressed");
else
g_warning ("Failed to init libfeedback: %s", err->message);
GtkIconTheme *theme = gtk_icon_theme_get_default ();
gtk_icon_theme_add_resource_path (theme, "/sm/puri/squeekboard/icons");
}
static void

View File

@ -31,19 +31,30 @@
#include "eek-keyboard.h"
/// 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);
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.
struct KeyMap eek_key_map_from_str(char *keymap_str) {
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;
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);
@ -51,9 +62,10 @@ struct KeyMap eek_key_map_from_str(char *keymap_str) {
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);
size_t keymap_len = strlen(xkb_keymap_str) + 1;
keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
keyboard->keymap_len = strlen(keymap_str) + 1;
g_autofree char *path = strdup("/eek_keymap-XXXXXX");
char *r = &path[strlen(path) - 6];
@ -67,39 +79,17 @@ struct KeyMap eek_key_map_from_str(char *keymap_str) {
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)keymap_len)) {
if (ftruncate(keymap_fd, (off_t)keyboard->keymap_len)) {
g_error("Failed to increase keymap fd size");
}
char *ptr = mmap(NULL, keymap_len, PROT_WRITE, MAP_SHARED,
char *ptr = mmap(NULL, keyboard->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, keymap_len);
munmap(ptr, keymap_len);
free(xkb_keymap_str);
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;
strncpy(ptr, keymap_str, keyboard->keymap_len);
munmap(ptr, keyboard->keymap_len);
return keyboard;
}

View File

@ -35,18 +35,16 @@ G_BEGIN_DECLS
/// Keyboard state holder
struct _LevelKeyboard {
struct squeek_layout *layout; // owned
// FIXME: This no longer needs to exist, keymap was folded into layout.
struct xkb_keymap *keymap; // owned
int keymap_fd; // keymap formatted as XKB string
size_t keymap_len; // length of the data inside keymap_fd
guint id; // as a key to layout choices
};
typedef struct _LevelKeyboard LevelKeyboard;
/// 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);
gchar * eek_keyboard_get_keymap
(LevelKeyboard *keyboard);
LevelKeyboard*
level_keyboard_new (struct squeek_layout *layout);

View File

@ -265,6 +265,10 @@ renderer_init (EekRenderer *self)
self->allocation_height = 0.0;
self->scale_factor = 1;
GtkIconTheme *theme = gtk_icon_theme_get_default ();
gtk_icon_theme_add_resource_path (theme, "/sm/puri/squeekboard/icons");
self->css_provider = squeek_load_style();
}

View File

@ -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_use_layout(context->submission, keyboard->layout, timestamp);
submission_set_keyboard(context->submission, keyboard, 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_use_layout(context->submission, context->keyboard->layout, time);
submission_set_keyboard(context->submission, context->keyboard, time);
}
}

View File

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

View File

@ -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,rustc_less_1_36']
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5']
cargo_deps = files('Cargo.deps.legacy')
endif

View File

@ -18,7 +18,7 @@ use xkbcommon::xkb;
use ::action;
use ::keyboard::{
KeyState, PressType,
generate_keymaps, generate_keycodes, KeyCode, FormattingError
generate_keymap, generate_keycodes, FormattingError
};
use ::layout;
use ::layout::ArrangementKind;
@ -382,45 +382,56 @@ impl Layout {
)
)}).collect();
let symbolmap: HashMap<String, KeyCode> = generate_keycodes(
extract_symbol_names(&button_actions)
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()
},
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_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,
}
)
})
button_states
);
let keymaps = match generate_keymaps(symbolmap) {
// TODO: generate from symbols
let keymap_str = match generate_keymap(&button_states) {
Err(e) => { return (Err(e), warning_handler) },
Ok(v) => v,
};
@ -484,10 +495,10 @@ impl Layout {
(
Ok(::layout::LayoutData {
views: views,
keymaps: keymaps.into_iter().map(|keymap_str|
keymap_str: {
CString::new(keymap_str)
.expect("Invalid keymap string generated")
).collect(),
},
// FIXME: use a dedicated field
margins: layout::Margins {
top: self.margins.top,
@ -723,27 +734,11 @@ 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::*;
use std::error::Error as ErrorTrait;
use ::logging::ProblemPanic;
const THIS_FILE: &str = file!();
@ -791,8 +786,7 @@ mod tests {
Err(e) => {
let mut handled = false;
if let Error::Yaml(ye) = &e {
handled = ye.to_string()
.starts_with("missing field `views`");
handled = ye.description() == "missing field `views`";
};
if !handled {
println!("Unexpected error {:?}", e);
@ -810,7 +804,7 @@ mod tests {
Err(e) => {
let mut handled = false;
if let Error::Yaml(ye) = &e {
handled = ye.to_string()
handled = ye.description()
.starts_with("unknown field `bad_field`");
};
if !handled {
@ -868,23 +862,6 @@ 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)
@ -962,35 +939,4 @@ mod tests {
}
);
}
#[test]
fn test_extract_symbols() {
let actions = [(
"ac",
action::Action::Submit {
text: None,
keys: vec![
action::KeySym("a".into()),
action::KeySym("c".into()),
],
},
)];
assert_eq!(
extract_symbol_names(&actions[..]).collect::<Vec<_>>(),
vec!["a", "c"],
);
}
#[test]
fn test_extract_symbols_erase() {
let actions = [(
"Erase",
action::Action::Erase,
)];
assert_eq!(
extract_symbol_names(&actions[..]).collect::<Vec<_>>(),
vec!["BackSpace"],
);
}
}

View File

@ -5,13 +5,11 @@ 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 ::util;
use ::logging;
// Traits
use std::io::Write;
@ -23,12 +21,7 @@ pub enum PressType {
Pressed = 1,
}
/// The extended, unambiguous layout-keycode
#[derive(Debug, Clone)]
pub struct KeyCode {
pub code: u32,
pub keymap_idx: usize,
}
pub type KeyCode = u32;
bitflags!{
/// Map to `virtual_keyboard.modifiers` modifiers values
@ -87,10 +80,10 @@ impl KeyState {
}
/// Sorts an iterator by converting it to a Vector and back
fn sorted<'a, I: Iterator<Item=String>>(
fn sorted<'a, I: Iterator<Item=&'a str>>(
iter: I
) -> impl Iterator<Item=String> {
let mut v: Vec<String> = iter.collect();
) -> impl Iterator<Item=&'a str> {
let mut v: Vec<&'a str> = iter.collect();
v.sort();
v.into_iter()
}
@ -98,17 +91,15 @@ fn sorted<'a, I: Iterator<Item=String>>(
/// 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=String>>(
key_names: C,
) -> HashMap<String, KeyCode> {
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);
HashMap::from_iter(
// 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 },
))
// 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..)
)
}
@ -133,54 +124,12 @@ 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.
/// Key codes must not repeat and must remain between 9 and 255.
fn generate_keymap(
symbolmap: &SingleKeyMap,
// 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>
) -> Result<String, FormattingError> {
let mut buf: Vec<u8> = Vec::new();
writeln!(
@ -191,80 +140,86 @@ 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();
// 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,
)?;
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",
);
}
},
_ => {},
}
}
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, keycode) in pairs {
write!(
buf,
"
key <I{}> {{ [ {} ] }};",
keycode,
name,
)?;
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,
)?;
}
}
}
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);
}};
}};
}};"
)?;
@ -279,16 +234,23 @@ mod tests {
use xkbcommon::xkb;
use ::action::KeySym;
#[test]
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(&key_map).unwrap();
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),
pressed: PressType::Released,
},
}).unwrap();
let keymap = xkb::Keymap::new_from_string(
&context,
keymap_str.clone(),
@ -301,36 +263,4 @@ 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);
}
}

View File

@ -39,6 +39,7 @@ 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*);

View File

@ -236,6 +236,13 @@ 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"
@ -625,8 +632,8 @@ pub struct Layout {
pub views: HashMap<String, (c::Point, View)>,
// Non-UI stuff
/// xkb keymaps applicable to the contained keys. Unchangeable
pub keymaps: Vec<CString>,
/// xkb keymap applicable to the contained keys. Unchangeable
pub keymap_str: 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.
@ -642,7 +649,7 @@ pub struct Layout {
pub struct LayoutData {
/// Point is the offset within layout
pub views: HashMap<String, (c::Point, View)>,
pub keymaps: Vec<CString>,
pub keymap_str: CString,
pub margins: Margins,
}
@ -665,7 +672,7 @@ impl Layout {
kind,
current_view: "base".to_owned(),
views: data.views,
keymaps: data.keymaps,
keymap_str: data.keymap_str,
pressed_keys: HashSet::new(),
margins: data.margins,
}
@ -1128,7 +1135,7 @@ mod test {
]);
let layout = Layout {
current_view: String::new(),
keymaps: Vec::new(),
keymap_str: CString::new("").unwrap(),
kind: ArrangementKind::Base,
pressed_keys: HashSet::new(),
// Lots of bottom margin

View File

@ -31,16 +31,22 @@ pub enum Error {
impl ::std::fmt::Display for Error {
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
out.write_str(match self {
use ::std::error::Error;
out.write_str(self.description())
}
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match self {
&Error::NotWellFormed => "Language tag is not well-formed.",
// this is exception: here we do want exhaustive match so we don't publish version with
// missing descriptions by mistake.
&Error::__NonExhaustive => panic!("Placeholder error must not be instantiated!"),
})
}
}
}
/// Convenience Result alias.
type Result<T> = ::std::result::Result<T, Error>;

View File

@ -17,7 +17,6 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("us_wide", include_str!("../data/keyboards/us_wide.yaml")),
("br", include_str!("../data/keyboards/br.yaml")),
("de", include_str!("../data/keyboards/de.yaml")),
("be", include_str!("../data/keyboards/be.yaml")),
("de_wide", include_str!("../data/keyboards/de_wide.yaml")),
("dk", include_str!("../data/keyboards/dk.yaml")),
("es", include_str!("../data/keyboards/es.yaml")),

View File

@ -31,7 +31,6 @@
enum {
PROP_0,
PROP_VISIBLE,
PROP_ENABLED,
PROP_LAST
};
@ -45,7 +44,6 @@ struct _ServerContextService {
struct ui_manager *manager; // unowned
gboolean visible;
gboolean enabled;
PhoshLayerSurface *window;
GtkWidget *widget; // nullable
guint hiding;
@ -210,9 +208,6 @@ on_hide (ServerContextService *self)
static void
server_context_service_real_show_keyboard (ServerContextService *self)
{
if (!self->enabled)
return;
if (self->hiding) {
g_source_remove (self->hiding);
self->hiding = 0;
@ -268,9 +263,7 @@ server_context_service_set_property (GObject *object,
case PROP_VISIBLE:
self->visible = g_value_get_boolean (value);
break;
case PROP_ENABLED:
server_context_service_set_enabled (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -326,43 +319,11 @@ server_context_service_class_init (ServerContextServiceClass *klass)
g_object_class_install_property (gobject_class,
PROP_VISIBLE,
pspec);
/**
* ServerContextServie:keyboard:
*
* Does the user want the keyboard to show up automatically?
*/
pspec =
g_param_spec_boolean ("enabled",
"Enabled",
"Whether the keyboard is enabled",
TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (gobject_class,
PROP_ENABLED,
pspec);
}
static void
server_context_service_init (ServerContextService *self) {
const char *schema_name = "org.gnome.desktop.a11y.applications";
GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default();
g_autoptr(GSettingsSchema) schema = NULL;
self->enabled = TRUE;
if (!ssrc) {
g_warning("No gsettings schemas installed.");
return;
}
schema = g_settings_schema_source_lookup(ssrc, schema_name, TRUE);
if (schema) {
g_autoptr(GSettings) settings = g_settings_new (schema_name);
g_settings_bind (settings, "screen-keyboard-enabled",
self, "enabled", G_SETTINGS_BIND_GET);
} else {
g_warning("Gsettings schema %s is not installed on the system. "
"Enabling by default.", schema_name);
}
(void)self;
}
ServerContextService *
@ -375,18 +336,3 @@ server_context_service_new (EekboardContextService *self, struct submission *sub
ui->manager = uiman;
return ui;
}
void
server_context_service_set_enabled (ServerContextService *self, gboolean enabled)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
if (enabled == self->enabled)
return;
self->enabled = enabled;
if (self->enabled)
server_context_service_show_keyboard (self);
else
server_context_service_hide_keyboard (self);
}

View File

@ -33,7 +33,6 @@ ServerContextService *server_context_service_new(EekboardContextService *self, s
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
void server_context_service_show_keyboard (ServerContextService *self);
void server_context_service_hide_keyboard (ServerContextService *self);
void server_context_service_set_enabled (ServerContextService *self, gboolean enabled);
G_END_DECLS
#endif /* SERVER_CONTEXT_SERVICE_H */

View File

@ -6,7 +6,6 @@
#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,
@ -16,5 +15,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_use_layout(struct submission *self, struct squeek_layout *layout, uint32_t time);
void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard, uint32_t time);
#endif

View File

@ -23,9 +23,8 @@ use ::action::Modifier;
use ::imservice;
use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
use ::layout;
use ::layout::c::LevelKeyboard;
use ::util::vec_remove;
use ::vkeyboard;
use ::vkeyboard::VirtualKeyboard;
// traits
@ -69,8 +68,6 @@ pub mod c {
modifiers_active: Vec::new(),
virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(),
keymap_fds: Vec::new(),
keymap_idx: None,
}
))
}
@ -94,17 +91,16 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn submission_use_layout(
fn submission_set_keyboard(
submission: *mut Submission,
layout: *const layout::Layout,
keyboard: LevelKeyboard,
time: u32,
) {
if submission.is_null() {
panic!("Null submission pointer");
}
let submission: &mut Submission = unsafe { &mut *submission };
let layout = unsafe { &*layout };
submission.use_layout(layout, Timestamp(time));
submission.update_keymap(keyboard, Timestamp(time));
}
}
@ -123,8 +119,6 @@ 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> {
@ -183,34 +177,11 @@ impl Submission {
let submit_action = match was_committed_as_text {
true => SubmittedAction::IMService,
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(
keycode.clone(),
PressType::Pressed,
time,
);
self.virtual_keyboard.switch(
keycode.clone(),
PressType::Released,
time,
);
},
};
}
self.virtual_keyboard.switch(
keycodes,
PressType::Pressed,
time,
);
SubmittedAction::VirtualKeyboard(keycodes.clone())
},
};
@ -228,21 +199,11 @@ impl Submission {
// no matter if the imservice got activated,
// keys must be released
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(
keycode.code,
PressType::Released,
time,
);
},
// Design choice here: submit multiple all at press time
// and do nothing at release time.
_ => {},
};
self.virtual_keyboard.switch(
&keycodes,
PressType::Released,
time,
)
},
}
};
@ -313,7 +274,6 @@ impl Submission {
}
}
/// Changes keymap and clears pressed keys and modifiers.
///
/// It's not obvious if clearing is the right thing to do,
@ -323,28 +283,9 @@ 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.
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);
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);
}
}

View File

@ -26,104 +26,49 @@ impl CountAndPrint {
}
}
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_builtin_layout(name: &str) {
check_layout(Layout::from_resource(name).expect("Invalid layout data"))
}
pub fn check_layout_file(path: &str) {
check_layout(
Layout::from_file(path.into()).expect("Invalid layout file"),
false,
)
check_layout(Layout::from_file(path.into()).expect("Invalid layout file"))
}
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) {
fn check_layout(layout: Layout) {
let handler = CountAndPrint::new();
let (layout, mut handler) = layout.build(handler);
let (layout, handler) = layout.build(handler);
if handler.0 > 0 {
println!("{} problems while parsing layout", handler.0)
}
let layout = layout.expect("layout broken");
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
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();
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 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");
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 xkb_states[keycode.keymap_idx].key_get_one_sym(keycode.code) {
match state.key_get_one_sym(*keycode) {
xkb::KEY_NoSymbol => {
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,
);
eprintln!("{}", keymap_str);
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
},
_ => {},
}

View File

@ -203,23 +203,6 @@ 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::*;
@ -234,12 +217,4 @@ 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)]
);
}
}

View File

@ -1,42 +1,18 @@
/*! Managing the events belonging to virtual-keyboard interface. */
use ::keyboard::{ Modifiers, PressType };
use ::keyboard::{ KeyCode, Modifiers, PressType };
use ::layout::c::LevelKeyboard;
use ::submission::Timestamp;
/// Standard xkb keycode
type KeyCode = u32;
/// Gathers stuff defined in C or called by C
pub mod c {
use std::ffi::CStr;
use std::os::raw::{ c_char, c_void };
use super::*;
use std::os::raw::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(
@ -48,16 +24,13 @@ pub mod c {
pub fn eek_virtual_keyboard_update_keymap(
virtual_keyboard: ZwpVirtualKeyboardV1,
keymap: *const KeyMap,
keyboard: LevelKeyboard,
);
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);
}
}
@ -68,15 +41,35 @@ impl VirtualKeyboard {
// TODO: error out if keymap not set
pub fn switch(
&self,
keycode: KeyCode,
keycodes: &Vec<KeyCode>,
action: PressType,
timestamp: Timestamp,
) {
let keycode = keycode - 8;
unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, action.clone() as u32
);
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, _) => {},
}
}
}
@ -87,12 +80,9 @@ impl VirtualKeyboard {
}
}
pub fn update_keymap(&self, keymap: &c::KeyMap) {
pub fn update_keymap(&self, keyboard: LevelKeyboard) {
unsafe {
c::eek_virtual_keyboard_update_keymap(
self.0,
keymap as *const c::KeyMap,
);
c::eek_virtual_keyboard_update_keymap(self.0, keyboard);
}
}
}

View File

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

View File

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

View File

@ -50,7 +50,6 @@ endforeach
foreach layout : [
'us', 'us_wide',
'br',
'be',
'de', 'de_wide',
'dk',
'es',
@ -69,17 +68,11 @@ 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]
+ extra,
+ [ '--example', 'test_layout', '--', layout],
workdir: meson.build_root(),
)
endforeach