Compare commits

..

10 Commits

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

Compatibility not actually guaranteed, that's purely observational.

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

View File

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

View File

@ -1,89 +0,0 @@
---
outlines:
default: { width: 54, height: 42 }
altline: { width: 81, height: 42 }
wide: { width: 100, height: 42 }
spaceline: { width: 205, height: 42 }
special: { width: 54, height: 42 }
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"

View File

@ -1,89 +0,0 @@
---
outlines:
default: { width: 54, height: 42 }
altline: { width: 81, height: 42 }
wide: { width: 100, height: 42 }
spaceline: { width: 205, height: 42 }
special: { width: 54, height: 42 }
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"

View File

@ -1,211 +0,0 @@
---
outlines:
default: { width: 54, height: 37 }
action: { width: 90, height: 37 }
altline: { width: 81, height: 37 }
wide: { width: 90, height: 37 }
spaceline: { width: 225, height: 37 }
special: { width: 54, height: 37 }
small: { width: 67.4, height: 22 }
views:
base:
- "EscSmall TabSmall Ctrl Alt ↑ ↓ ← →"
- "q w e r t y u i o p"
- "a s d f g h j k l"
- "Shift_L z x c v b n m BackSpace"
- "show_numbers preferences space show_actions Return"
upper:
- "EscSmall TabSmall Ctrl Alt PgUp PgDn Home End"
- "Q W E R T Y U I O P"
- "A S D F G H J K L"
- "Shift_L Z X C V B N M BackSpace"
- "show_numbers preferences space show_actions Return"
numbers:
- "EscSmall TabSmall Ctrl Alt ↑ ↓ ← →"
- "1 2 3 4 5 6 7 8 9 0"
- "* # $ / & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space period Return"
symbols:
- "EscSmall TabSmall Ctrl Alt ↑ ↓ ← →"
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ € ¥ ^ ° @ { }"
- "show_numbers_from_symbols \\ % < > = [ ] BackSpace"
- "show_letters preferences space period Return"
actions:
- "EscSmall TabSmall Ctrl Alt PgUp PgDn Home End"
- "F1 F2 F3 F4 F5 F6"
- "F7 F8 F9 F10 F11 F12"
- "Esc Tab Pause Insert Up Del"
- "show_letters Menu Break Left Down Right"
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_actions:
action:
set_view: "actions"
outline: "altline"
label: ">_"
period:
outline: "altline"
text: "."
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
F1:
outline: "action"
keysym: "F1"
F2:
outline: "action"
keysym: "F2"
F3:
outline: "action"
keysym: "F3"
F4:
outline: "action"
keysym: "F4"
F5:
outline: "action"
keysym: "F5"
F6:
outline: "action"
keysym: "F6"
F7:
outline: "action"
keysym: "F7"
F8:
outline: "action"
keysym: "F8"
F9:
outline: "action"
keysym: "F9"
F10:
outline: "action"
keysym: "F10"
F11:
outline: "action"
keysym: "F11"
F12:
outline: "action"
keysym: "F12"
Esc:
outline: "action"
keysym: "Escape"
EscSmall:
outline: "small"
keysym: "Escape"
label: "Esc"
Tab:
outline: "action"
keysym: "Tab"
TabSmall:
outline: "small"
keysym: "Tab"
label: "Tab"
Del:
outline: "action"
keysym: "Delete"
Insert:
outline: "action"
keysym: "Insert"
Menu:
outline: "action"
keysym: "Menu"
Pause:
outline: "action"
keysym: "Pause"
Menu:
outline: "action"
keysym: "Menu"
Break:
outline: "action"
keysym: "Break"
Home:
outline: "small"
keysym: "Home"
End:
outline: "small"
keysym: "End"
PgUp:
outline: "small"
keysym: "Page_Up"
PgDn:
outline: "small"
keysym: "Page_Down"
"↑":
outline: "small"
keysym: "Up"
"↓":
outline: "small"
keysym: "Down"
"←":
outline: "small"
keysym: "Left"
"→":
outline: "small"
keysym: "Right"
Up:
label: "↑"
outline: "action"
keysym: "Up"
Left:
label: "←"
outline: "action"
keysym: "Left"
Down:
label: "↓"
outline: "action"
keysym: "Down"
Right:
label: "→"
outline: "action"
keysym: "Right"
Ctrl:
modifier: "Control"
outline: "small"
label: "Ctrl"
Alt:
modifier: "Alt"
outline: "small"
label: "Alt"

2
debian/control vendored
View File

@ -38,8 +38,6 @@ Depends:
gnome-themes-extra-data,
${shlibs:Depends},
${misc:Depends},
Breaks:
librem5-base (<< 24),
Description: On-screen keyboard for Wayland
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.

View File

@ -1,87 +1,34 @@
A guide to creating layouts
===========================
This guide is based on the original Kareema's [forum post](https://forums.puri.sm/t/translations-and-virtual-touch-keyboards-tracking-localization/7669/48).
Kareema's guide to creating layouts
===================================
Its long overdue to write a comprehensive guide how to add a keyboard layout from start. But unfortunately, I dont have much time left ATM. A lot of information can be found in [this ](https://forums.puri.sm/t/using-non-latin-language-on-librem-5/7103/5) thread.
So at least I will try to start writing a short how-to here and edit this post as I find the time. Hope this helps a bit - comments and corrections [welcome](https://source.puri.sm/Librem5/squeekboard/-/merge_requests/)
So at least I will try to start writing a short how-to here and edit this post as I find the time. Hope this helps a bit - comments and corrections welcome.
## Creating a new layout
Creating a layout is easy. You don't need to recompile things, just edit and test. It's easiest to start with an existing layout.
### Get one of the existing keyboard layouts
**Get one of the existing keyboard layouts**
* You can get one of the keyboards from the squeekboard git repository : [https://source.puri.sm/Librem5/squeekboard ](https://source.puri.sm/Librem5/squeekboard)
* The keyboard layouts are located in the subdirectory [`data/keyboards/`](https://source.puri.sm/Librem5/squeekboard/-/tree/master/data/keyboards) in the `.yaml` files
* The keyboard layouts are located in the subdirectory `data/keyboard/` in the `.yaml` files
* Take a look and try to understand them :slight_smile:
### Creating the keyboard layout
* To be written: For the time being, take a look at [Using non-latin language on Librem 5](https://forums.puri.sm/t/using-non-latin-language-on-librem-5/7103/5)
* The correct name of the .yaml file can be found with the command
```
gsettings get org.gnome.desktop.input-sources sources
```
The output should be something like this: `[('xkb', 'us'), ('xkb', 'de')]`
So for example “de.yaml” would be the correct name for the German keyboard layout.
If the name of your layout is not translated correctly in the list, you can fix it by adding it and recompiling Squeekboard.
### Testing the layout
Copy your yaml file to `~/.local/share/squeekboard/keyboards/` for testing purposes. From there it should get picked up by squeekboard automatically.
You can also use the `test_layout` tool from the -devel package to check it for errors:
```
# squeekboard_test_layout ./mylayout.yaml
Test result: OK
```
## Contributing your changes
If you want to share your layout with the world, the best way is to submit it to the Squeekboard project. The workflow is similar to any other Gitlab-based project.
Above all, your layout should be working, be tested, not break anything, and make sense.
### Fork your own copy of squeekboard
**Fork your own copy of squeekboard**
* Best way would be to start with a fork of the squeekboard repository: Create a user account at https://source.puri.sm/, go the the squeekboard git repository, press “Fork” in the web interface. You can find further instructions [here](https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html#creating-a-fork).
* Clone your fork locally with `git clone` and use the uri of your forked repo there
### Edit your keyboard and get it merged
**Workflow to edit your keyboard and get it merged**
* It may be useful to check out the [generic guide how the workflow to contribute works](https://developer.puri.sm/Librem5/Contact/Contributing.html)
* A generic guide how the workflow to contribute works, can be found at https://developer.puri.sm/Librem5/Contact/Contributing.html
* Create a branch: Name it “keyboard-layout-mylanguage” or whatever
* Checkout your branch, edit your keyboard layout and commit your changes
* Your layout **must** be correctly named, and in `data/keyboards/`.
* Your layout **must** pass the `test_layout` tool with zero problems.
* Your translation **must** be correctly named, and in `data/langs/`.
* Your layout or translation **must** be added to automatic tests. **Dont forget to add it** to `src/resources.rs` and the layout to `tests/meson.build` (thats for me, because I always forget it).
### Get it merged
It's always recommended to **compile and run** squeekboard before submitting your changes. This serves as a test that all is working. See instructions in the [compiling section](#compiling-and-running-squeekboard).
* Push the local changes (to the branch of your fork of squeekboard)
* Create a merge request for the branch to get your changes merged to the official squeekboard git repository
If your changes pass automated tests (CI), then the merge request will be reviewed by the maintainers, and you might be asked to change a thing or two.
## Compiling and running Squeekboard
If you want your change to become part of official Squeekboard, or if you want to add a translation of your layout name, you will have to recompile Squeekboard and test your changes there.
### Compile squeekboard
**Compile squeekboard**
* Follow the instructions found in “Building” section of the squeekboards README: Running squeekboard: [https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#building ](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#building)
### Run squeekboard
**Running squeekboard**
* Follow these instructions to run squeekboard: [https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#running ](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#running)
* Additionally take a look at the contribution document for [testing info](HACKING.md#testing)
@ -90,3 +37,19 @@ If you want your change to become part of official Squeekboard, or if you want t
`deb [arch=amd64] http://ci.puri.sm/ scratch librem5`
Squeekboard can be installed from there as a Debian package, too (thats what I often do). But beware - there be dragons! You could bork your system with these packages and you should probably disable this repository again after installing what you need - these packages are not meant for production systems (or so I heard :wink: )
**Creating the keyboard layout**
* To be written: For the time being, take a look at [Using non-latin language on Librem 5 ](https://forums.puri.sm/t/using-non-latin-language-on-librem-5/7103/5)
* The correct name of the .yaml file can be found with the command `gsettings get org.gnome.desktop.input-sources sources`
The output should be something like this: `[('xkb', 'us'), ('xkb', 'de')]`
So f.ex. “de.yaml” would be the correct name for the German keyboard layout.
* The translations for the keyboard layout names in the different languages can be found at `data/langs/`
* Dont forget to add your newly created layout or translation to `src/resources.rs` and the layout to `tests/meson.build` (thats for me, because I always forget it)
**Testing the layout**
* Copy your yaml file to `~/.local/share/squeekboard/keyboards/` for testing purposes. From there it should get picked up by squeekboard
* To test the translations in `data/langs/` , you have to compile squeekboard

View File

@ -104,10 +104,10 @@ eek_gtk_keyboard_real_draw (GtkWidget *self,
return FALSE;
}
// Units of virtual pixels size
// Units of pixel size
static enum squeek_arrangement_kind get_type(uint32_t width, uint32_t height) {
(void)height;
if (width < 540) {
if (width < 1080) {
return ARRANGEMENT_KIND_BASE;
}
return ARRANGEMENT_KIND_WIDE;
@ -119,10 +119,11 @@ eek_gtk_keyboard_real_size_allocate (GtkWidget *self,
{
EekGtkKeyboardPrivate *priv =
eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self));
uint32_t scale = (uint32_t)gtk_widget_get_scale_factor(self);
// check if the change would switch types
enum squeek_arrangement_kind new_type = get_type(
(uint32_t)(allocation->width - allocation->x),
(uint32_t)(allocation->height - allocation->y));
(uint32_t)(allocation->width - allocation->x) * scale,
(uint32_t)(allocation->height - allocation->y) * scale);
if (priv->layout->arrangement != new_type) {
priv->layout->arrangement = new_type;
uint32_t time = gdk_event_get_time(NULL);

View File

@ -28,19 +28,17 @@
#include <sys/random.h> // TODO: this is Linux-specific
#include <xkbcommon/xkbcommon.h>
#include "eek-keyboard.h"
static void eek_key_map_deinit(struct keymap *self) {
if (self->fd < 0) {
g_error("Deinit called multiple times on KeyMap");
} else {
/// 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);
}
self->fd = -1;
}
static struct keymap eek_key_map_from_str(const char *keymap_str) {
/// External linkage for Rust.
struct KeyMap eek_key_map_from_str(char *keymap_str) {
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
g_error("No context created");
@ -83,7 +81,7 @@ static struct keymap eek_key_map_from_str(const char *keymap_str) {
munmap(ptr, keymap_len);
free(xkb_keymap_str);
xkb_keymap_unref(keymap);
struct keymap km = {
struct KeyMap km = {
.fd = keymap_fd,
.fd_len = keymap_len,
};
@ -91,7 +89,6 @@ static struct keymap eek_key_map_from_str(const char *keymap_str) {
}
void level_keyboard_free(LevelKeyboard *self) {
eek_key_map_deinit(&self->keymap);
squeek_layout_free(self->layout);
g_free(self);
}
@ -104,7 +101,5 @@ level_keyboard_new (struct squeek_layout *layout)
g_error("Failed to create a keyboard");
}
keyboard->layout = layout;
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
keyboard->keymap = eek_key_map_from_str(keymap_str);
return keyboard;
}

View File

@ -32,19 +32,20 @@
G_BEGIN_DECLS
/// 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
};
/// Keyboard state holder
struct _LevelKeyboard {
struct squeek_layout *layout; // owned
struct keymap keymap; // owned
// FIXME: This no longer needs to exist, keymap was folded into layout.
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);
LevelKeyboard*

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_set_keyboard(context->submission, keyboard, timestamp);
submission_use_layout(context->submission, keyboard->layout, timestamp);
}
// Update UI
@ -345,7 +345,7 @@ void eekboard_context_service_set_submission(EekboardContextService *context, st
context->submission = submission;
if (context->submission) {
uint32_t time = gdk_event_get_time(NULL);
submission_set_keyboard(context->submission, context->keyboard, time);
submission_use_layout(context->submission, context->keyboard->layout, time);
}
}

View File

@ -19,7 +19,6 @@ add_project_arguments(
'-Werror=missing-field-initializers',
'-Werror=incompatible-pointer-types',
'-Werror=int-conversion',
'-Werror=redundant-decls',
'-Wformat-nonliteral',
'-Wformat-security',
'-Winit-self',
@ -84,7 +83,7 @@ cargo_toml_base = configure_file(
cargo_deps = files('Cargo.deps')
if get_option('legacy') == true
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5']
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5,rustc_less_1_36']
cargo_deps = files('Cargo.deps.legacy')
endif

View File

@ -18,7 +18,7 @@ use xkbcommon::xkb;
use ::action;
use ::keyboard::{
KeyState, PressType,
generate_keymap, generate_keycodes, FormattingError
generate_keymaps, generate_keycodes, KeyCode, FormattingError
};
use ::layout;
use ::layout::ArrangementKind;
@ -382,7 +382,7 @@ impl Layout {
)
)}).collect();
let symbolmap: HashMap<String, u32> = generate_keycodes(
let symbolmap: HashMap<String, KeyCode> = generate_keycodes(
extract_symbol_names(&button_actions)
);
@ -391,7 +391,7 @@ impl Layout {
let keycodes = match &action {
::action::Action::Submit { text: _, keys } => {
keys.iter().map(|named_keysym| {
*symbolmap.get(named_keysym.0.as_str())
symbolmap.get(named_keysym.0.as_str())
.expect(
format!(
"keysym {} in key {} missing from symbol map",
@ -399,11 +399,13 @@ impl Layout {
name
).as_str()
)
.clone()
}).collect()
},
action::Action::Erase => vec![
*symbolmap.get("BackSpace")
.expect(&format!("BackSpace missing from symbol map")),
symbolmap.get("BackSpace")
.expect(&format!("BackSpace missing from symbol map"))
.clone(),
],
_ => 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) },
Ok(v) => v,
};
@ -446,14 +448,14 @@ impl Layout {
&mut warning_handler,
))
});
layout::Row::new(
add_offsets(
layout::Row {
buttons: add_offsets(
buttons,
|button| button.size.width,
).collect()
)
}
});
let rows = add_offsets(rows, |row| row.get_size().height)
let rows = add_offsets(rows, |row| row.get_height())
.collect();
(
name.clone(),
@ -471,8 +473,8 @@ impl Layout {
name,
(
layout::c::Point {
x: (total_size.width - view.get_size().width) / 2.0,
y: (total_size.height - view.get_size().height) / 2.0,
x: (total_size.width - view.get_width()) / 2.0,
y: (total_size.height - view.get_height()) / 2.0,
},
view,
),
@ -482,10 +484,10 @@ impl Layout {
(
Ok(::layout::LayoutData {
views: views,
keymap_str: {
keymaps: keymaps.into_iter().map(|keymap_str|
CString::new(keymap_str)
.expect("Invalid keymap string generated")
},
).collect(),
// FIXME: use a dedicated field
margins: layout::Margins {
top: self.margins.top,
@ -722,19 +724,20 @@ fn create_button<H: logging::Handler>(
}
fn extract_symbol_names<'a>(actions: &'a [(&str, action::Action)])
-> impl Iterator<Item=&'a str>
-> impl Iterator<Item=String> + 'a
{
actions.iter()
.filter_map(|(_name, act)| {
match act {
action::Action::Submit {
text: _, keys,
} => Some(keys),
} => Some(keys.clone()),
action::Action::Erase => Some(vec!(action::KeySym("BackSpace".into()))),
_ => None,
}
})
.flatten()
.map(|named_keysym| named_keysym.0.as_str())
.map(|named_keysym| named_keysym.0)
}
#[cfg(test)]
@ -827,7 +830,7 @@ mod tests {
assert_eq!(
out.views["base"].1
.get_rows()[0].1
.get_buttons()[0].1
.buttons[0].1
.label,
::layout::Label::Text(CString::new("test").unwrap())
);
@ -842,7 +845,7 @@ mod tests {
assert_eq!(
out.views["base"].1
.get_rows()[0].1
.get_buttons()[0].1
.buttons[0].1
.label,
::layout::Label::Text(CString::new("test").unwrap())
);
@ -858,7 +861,7 @@ mod tests {
assert_eq!(
out.views["base"].1
.get_rows()[0].1
.get_buttons()[0].1
.buttons[0].1
.state.borrow()
.keycodes.len(),
2
@ -986,7 +989,7 @@ mod tests {
)];
assert_eq!(
extract_symbol_names(&actions[..]).collect::<Vec<_>>(),
Vec::<&str>::new(), //"BackSpace"], // TODO: centralize handling of BackSpace
vec!["BackSpace"],
);
}

View File

@ -54,7 +54,7 @@ pub mod c {
im: *const InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
imservice.preedit_string = String::new(); // I don't think this is ever used. Remove it?
imservice.preedit_string = String::new();
imservice.pending = IMProtocolState {
active: true,
..IMProtocolState::default()
@ -84,7 +84,7 @@ pub mod c {
surrounding_text: into_cstring(text)
.expect("Received invalid string")
.expect("Received null string"),
cursor,
surrounding_cursor: cursor,
..imservice.pending.clone()
};
}
@ -146,6 +146,10 @@ pub mod c {
let active_changed = imservice.current.active ^ imservice.pending.active;
imservice.current = imservice.pending.clone();
imservice.pending = IMProtocolState {
active: imservice.current.active,
..IMProtocolState::default()
};
if active_changed {
if imservice.current.active {
@ -310,7 +314,7 @@ impl TryFrom<u32> for ChangeCause {
#[derive(Clone)]
struct IMProtocolState {
surrounding_text: CString,
cursor: u32,
surrounding_cursor: u32,
content_purpose: ContentPurpose,
content_hint: ContentHint,
text_change_cause: ChangeCause,
@ -321,7 +325,7 @@ impl Default for IMProtocolState {
fn default() -> IMProtocolState {
IMProtocolState {
surrounding_text: CString::default(),
cursor: 0, // TODO: mark that there's no cursor
surrounding_cursor: 0, // TODO: mark that there's no cursor
content_hint: ContentHint::NONE,
content_purpose: ContentPurpose::Normal,
text_change_cause: ChangeCause::InputMethod,
@ -340,7 +344,7 @@ pub struct IMService {
pending: IMProtocolState,
current: IMProtocolState, // turn current into an idiomatic representation?
preedit_string: String, // I don't think this is ever used. Remove it?
preedit_string: String,
serial: Wrapping<u32>,
}
@ -362,7 +366,7 @@ impl IMService {
state_manager,
pending: IMProtocolState::default(),
current: IMProtocolState::default(),
preedit_string: String::new(), // I don't think this is ever used. Remove it?
preedit_string: String::new(),
serial: Wrapping(0u32),
});
unsafe {
@ -374,14 +378,9 @@ impl IMService {
imservice
}
pub fn commit_string(&mut self, text: &CString) -> Result<(), SubmitError> {
pub fn commit_string(&self, text: &CString) -> Result<(), SubmitError> {
match self.current.active {
true => {
let cursor_position = self.pending.cursor.try_into().unwrap(); // Converts u32 of cursor to usize
self.pending
.surrounding_text
.insert_str(cursor_position, text);
self.pending.cursor += text.len() as u32;
unsafe {
c::eek_input_method_commit_string(self.im, text.as_ptr())
}
@ -392,16 +391,11 @@ impl IMService {
}
pub fn delete_surrounding_text(
&mut self,
&self,
before: u32, after: u32,
) -> Result<(), SubmitError> {
match self.current.active {
true => {
let cursor_position: usize = self.pending.cursor.try_into().unwrap(); // Converts u32 of cursor to usize
self.pending.surrounding_text.replace_range(
cursor_position - (before as usize)..cursor_position + (after as usize),
"",
);
unsafe {
c::eek_input_method_delete_surrounding_text(
self.im,
@ -421,7 +415,6 @@ impl IMService {
c::eek_input_method_commit(self.im, self.serial.0)
}
self.serial += Wrapping(1u32);
imservice_handle_done(self);
Ok(())
},
false => Err(SubmitError::NotActive),

View File

@ -5,10 +5,13 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::mem;
use std::ptr;
use std::rc::Rc;
use std::string::FromUtf8Error;
use ::action::Action;
use ::util;
// Traits
use std::io::Write;
@ -20,7 +23,12 @@ pub enum PressType {
Pressed = 1,
}
pub type KeyCode = u32;
/// The extended, unambiguous layout-keycode
#[derive(Debug, Clone)]
pub struct KeyCode {
pub code: u32,
pub keymap_idx: usize,
}
bitflags!{
/// Map to `virtual_keyboard.modifiers` modifiers values
@ -79,10 +87,10 @@ impl KeyState {
}
/// Sorts an iterator by converting it to a Vector and back
fn sorted<'a, I: Iterator<Item=&'a str>>(
fn sorted<'a, I: Iterator<Item=String>>(
iter: I
) -> impl Iterator<Item=&'a str> {
let mut v: Vec<&'a str> = iter.collect();
) -> impl Iterator<Item=String> {
let mut v: Vec<String> = iter.collect();
v.sort();
v.into_iter()
}
@ -90,15 +98,17 @@ fn sorted<'a, I: Iterator<Item=&'a str>>(
/// Generates a mapping where each key gets a keycode, starting from ~~8~~
/// HACK: starting from 9, because 8 results in keycode 0,
/// which the compositor likes to discard
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
pub fn generate_keycodes<'a, C: IntoIterator<Item=String>>(
key_names: C,
) -> HashMap<String, u32> {
let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s);
) -> HashMap<String, KeyCode> {
HashMap::from_iter(
// sort to remove a source of indeterminism in keycode assignment
sorted(key_names.into_iter().chain(special_keysyms))
.map(|name| String::from(name))
.zip(9..)
// Sort to remove a source of indeterminism in keycode assignment.
sorted(key_names.into_iter())
.zip(util::cycle_count(9..255))
.map(|(name, (code, keymap_idx))| (
String::from(name),
KeyCode { code, keymap_idx },
))
)
}
@ -123,10 +133,54 @@ impl From<io::Error> for FormattingError {
}
}
/// Index is the key code, String is the occupant.
/// Starts all empty.
/// https://gitlab.freedesktop.org/xorg/xserver/-/issues/260
type SingleKeyMap = [Option<String>; 256];
fn single_key_map_new() -> SingleKeyMap {
// Why can't we just initialize arrays without tricks -_- ?
unsafe {
// Inspired by
// https://www.reddit.com/r/rust/comments/5n7bh1/how_to_create_an_array_of_a_type_with_clone_but/
#[cfg(feature = "rustc_less_1_36")]
let mut array: SingleKeyMap = mem::uninitialized();
#[cfg(not(feature = "rustc_less_1_36"))]
let mut array: SingleKeyMap = mem::MaybeUninit::uninit().assume_init();
for element in array.iter_mut() {
ptr::write(element, None);
}
array
}
}
pub fn generate_keymaps(symbolmap: HashMap::<String, KeyCode>)
-> Result<Vec<String>, FormattingError>
{
let mut bins: Vec<SingleKeyMap> = Vec::new();
for (name, KeyCode { code, keymap_idx }) in symbolmap.into_iter() {
if keymap_idx >= bins.len() {
bins.resize_with(
keymap_idx + 1,
|| single_key_map_new(),
);
}
bins[keymap_idx][code as usize] = Some(name);
}
let mut out = Vec::new();
for bin in bins {
out.push(generate_keymap(&bin)?);
}
Ok(out)
}
/// Generates a de-facto single level keymap.
/// Key codes must not repeat and should remain between 9 and 255.
pub fn generate_keymap(
symbolmap: HashMap::<String, KeyCode>,
/// Key codes must not repeat and must remain between 9 and 255.
fn generate_keymap(
symbolmap: &SingleKeyMap,
) -> Result<String, FormattingError> {
let mut buf: Vec<u8> = Vec::new();
writeln!(
@ -135,14 +189,21 @@ pub fn generate_keymap(
xkb_keycodes \"squeekboard\" {{
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.
// Two possible solutions:
// - use levels to cram multiple characters into one key
// - swap layouts on key presses
for keycode in symbolmap.values() {
for (_name, keycode) in &pairs {
write!(
buf,
"
@ -161,7 +222,7 @@ pub fn generate_keymap(
"
)?;
for (name, keycode) in symbolmap.iter() {
for (name, keycode) in pairs {
write!(
buf,
"
@ -219,13 +280,14 @@ mod tests {
use xkbcommon::xkb;
#[test]
fn test_keymap_multi() {
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
fn test_keymap_single_resolve() {
let mut key_map = single_key_map_new();
key_map[9] = Some("a".into());
key_map[10] = Some("c".into());
let keymap_str = generate_keymap(hashmap!{
"a".into() => 9,
"c".into() => 10,
}).unwrap();
let keymap_str = generate_keymap(&key_map).unwrap();
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = xkb::Keymap::new_from_string(
&context,
@ -239,4 +301,36 @@ mod tests {
assert_eq!(state.key_get_one_sym(9), xkb::KEY_a);
assert_eq!(state.key_get_one_sym(10), xkb::KEY_c);
}
#[test]
fn test_keymap_second_resolve() {
let keymaps = generate_keymaps(hashmap!(
"a".into() => KeyCode { keymap_idx: 1, code: 9 },
)).unwrap();
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = xkb::Keymap::new_from_string(
&context,
keymaps[1].clone(), // this index is part of the test
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).expect("Failed to create keymap");
let state = xkb::State::new(&keymap);
assert_eq!(state.key_get_one_sym(9), xkb::KEY_a);
}
#[test]
fn test_symbolmap_overflow() {
// The 257th key (U1101) is interesting.
// Use Unicode encoding for being able to use in xkb keymaps.
let keynames = (0..258).map(|num| format!("U{:04X}", 0x1000 + num));
let keycodes = generate_keycodes(keynames);
// test now
let code = keycodes.get("U1101").expect("Did not find the tested keysym");
assert_eq!(code.keymap_idx, 1);
}
}

View File

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

View File

@ -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]
pub extern "C"
fn squeek_layout_get_kind(layout: *const Layout) -> u32 {
@ -493,63 +486,38 @@ pub struct Button {
}
/// The graphical representation of a row of buttons
#[derive(Clone, Debug)]
pub struct Row {
/// Buttons together with their offset from the left relative to the row.
/// ie. the first button always start at 0.
buttons: Vec<(f64, Box<Button>)>,
/// Total size of the row
size: Size,
/// Buttons together with their offset from the left
pub buttons: Vec<(f64, Box<Button>)>,
}
impl Row {
pub fn new(buttons: Vec<(f64, Box<Button>)>) -> Row {
// Make sure buttons are sorted by offset.
debug_assert!({
let mut sorted = buttons.clone();
sorted.sort_by(|(f1, _), (f2, _)| f1.partial_cmp(f2).unwrap());
sorted.iter().map(|(f, _)| *f).collect::<Vec<_>>()
== buttons.iter().map(|(f, _)| *f).collect::<Vec<_>>()
});
let width = buttons.iter().next_back()
.map(|(x_offset, button)| button.size.width + x_offset)
.unwrap_or(0.0);
let height = find_max_double(
buttons.iter(),
pub fn get_height(&self) -> f64 {
find_max_double(
self.buttons.iter(),
|(_offset, button)| button.size.height,
);
Row { buttons, size: Size { width, height } }
)
}
pub fn get_size(&self) -> Size {
self.size.clone()
}
pub fn get_buttons(&self) -> &Vec<(f64, Box<Button>)> {
&self.buttons
fn get_width(&self) -> f64 {
self.buttons.iter().next_back()
.map(|(x_offset, button)| button.size.width + x_offset)
.unwrap_or(0.0)
}
/// Finds the first button that covers the specified point
/// relative to row's position's origin
fn find_button_by_position(&self, x: f64) -> &(f64, Box<Button>)
fn find_button_by_position(&self, point: c::Point)
-> Option<(f64, &Box<Button>)>
{
// Buttons are sorted so we can use a binary search to find the clicked
// button. Note this doesn't check whether the point is actually within
// a button. This is on purpose as we want a click past the left edge of
// the left-most button to register as a click.
let result = self.buttons.binary_search_by(
|&(f, _)| f.partial_cmp(&x).unwrap()
);
let index = result.unwrap_or_else(|r| r);
let index = if index > 0 { index - 1 } else { 0 };
&self.buttons[index]
self.buttons.iter().find(|(x_offset, button)| {
c::Bounds {
x: *x_offset, y: 0.0,
width: button.size.width,
height: button.size.height,
}.contains(&point)
})
.map(|(x_offset, button)| (*x_offset, button))
}
}
@ -560,85 +528,56 @@ pub struct Spacing {
}
pub struct View {
/// Rows together with their offsets from the top left
rows: Vec<(c::Point, Row)>,
/// Total size of the view
size: Size,
/// Rows together with their offsets from the top
rows: Vec<(f64, Row)>,
}
impl View {
pub fn new(rows: Vec<(f64, Row)>) -> View {
// Make sure rows are sorted by offset.
debug_assert!({
let mut sorted = rows.clone();
sorted.sort_by(|(f1, _), (f2, _)| f1.partial_cmp(f2).unwrap());
sorted.iter().map(|(f, _)| *f).collect::<Vec<_>>()
== rows.iter().map(|(f, _)| *f).collect::<Vec<_>>()
});
// No need to call `get_rows()`,
// as the biggest row is the most far reaching in both directions
// because they are all centered.
let width = find_max_double(rows.iter(), |(_offset, row)| row.size.width);
let height = rows.iter().next_back()
.map(|(y_offset, row)| row.size.height + y_offset)
.unwrap_or(0.0);
// Center the rows
let rows = rows.into_iter().map(|(y_offset, row)| {(
c::Point {
x: (width - row.size.width) / 2.0,
y: y_offset,
},
row,
)}).collect::<Vec<_>>();
View { rows, size: Size { width, height } }
View { rows }
}
/// Finds the first button that covers the specified point
/// relative to view's position's origin
fn find_button_by_position(&self, point: c::Point)
-> Option<ButtonPlace>
{
// Only test bounds of the view here, letting rows/column search extend
// to the edges of these bounds.
let bounds = c::Bounds {
x: 0.0,
self.get_rows().iter().find_map(|(row_offset, row)| {
// make point relative to the inside of the row
row.find_button_by_position({
c::Point { x: point.x, y: point.y } - row_offset
}).map(|(button_x_offset, button)| ButtonPlace {
button,
offset: row_offset + c::Point {
x: button_x_offset,
y: 0.0,
width: self.size.width,
height: self.size.height,
};
if !bounds.contains(&point) {
return None;
}
// Rows are sorted so we can use a binary search to find the row.
let result = self.rows.binary_search_by(
|(f, _)| f.y.partial_cmp(&point.y).unwrap()
);
let index = result.unwrap_or_else(|r| r);
let index = if index > 0 { index - 1 } else { 0 };
let row = &self.rows[index];
let button = row.1.find_button_by_position(point.x - row.0.x);
Some(ButtonPlace {
button: &button.1,
offset: &row.0 + c::Point { x: button.0, y: 0.0 },
},
})
})
}
pub fn get_size(&self) -> Size {
self.size.clone()
pub fn get_width(&self) -> f64 {
// No need to call `get_rows()`,
// as the biggest row is the most far reaching in both directions
// because they are all centered.
find_max_double(self.rows.iter(), |(_offset, row)| row.get_width())
}
pub fn get_height(&self) -> f64 {
self.rows.iter().next_back()
.map(|(y_offset, row)| row.get_height() + y_offset)
.unwrap_or(0.0)
}
/// Returns positioned rows, with appropriate x offsets (centered)
pub fn get_rows(&self) -> &Vec<(c::Point, Row)> {
&self.rows
pub fn get_rows(&self) -> Vec<(c::Point, &Row)> {
let available_width = self.get_width();
self.rows.iter().map(|(y_offset, row)| {(
c::Point {
x: (available_width - row.get_width()) / 2.0,
y: *y_offset,
},
row,
)}).collect()
}
/// Returns a size which contains all the views
@ -647,11 +586,11 @@ impl View {
Size {
height: find_max_double(
views.iter(),
|view| view.size.height,
|view| view.get_height(),
),
width: find_max_double(
views.iter(),
|view| view.size.width,
|view| view.get_width(),
),
}
}
@ -686,8 +625,8 @@ pub struct Layout {
pub views: HashMap<String, (c::Point, View)>,
// Non-UI stuff
/// xkb keymap applicable to the contained keys. Unchangeable
pub keymap_str: CString,
/// xkb keymaps applicable to the contained keys. Unchangeable
pub keymaps: Vec<CString>,
// Changeable state
// a Vec would be enough, but who cares, this will be small & fast enough
// TODO: turn those into per-input point *_buttons to track dragging.
@ -703,7 +642,7 @@ pub struct Layout {
pub struct LayoutData {
/// Point is the offset within layout
pub views: HashMap<String, (c::Point, View)>,
pub keymap_str: CString,
pub keymaps: Vec<CString>,
pub margins: Margins,
}
@ -726,7 +665,7 @@ impl Layout {
kind,
current_view: "base".to_owned(),
views: data.views,
keymap_str: data.keymap_str,
keymaps: data.keymaps,
pressed_keys: HashSet::new(),
margins: data.margins,
}
@ -799,7 +738,7 @@ impl Layout {
where F: FnMut(c::Point, &Box<Button>)
{
let (view_offset, view) = self.get_current_view_position();
for (row_offset, row) in view.get_rows() {
for (row_offset, row) in &view.get_rows() {
for (x_offset, button) in &row.buttons {
let offset = view_offset
+ row_offset.clone()
@ -812,7 +751,7 @@ impl Layout {
pub fn get_locked_keys(&self) -> Vec<Rc<RefCell<KeyState>>> {
let mut out = Vec::new();
let view = self.get_current_view();
for (_, row) in view.get_rows() {
for (_, row) in &view.get_rows() {
for (_, button) in &row.buttons {
let locked = {
let state = RefCell::borrow(&button.state).clone();
@ -874,9 +813,13 @@ mod procedures {
let button = make_button_with_state("1".into(), state);
let button_ptr = as_ptr(&button);
let row = Row::new(vec!((0.1, button)));
let row = Row {
buttons: vec!((0.1, button)),
};
let view = View::new(vec!((1.2, row)));
let view = View {
rows: vec!((1.2, row)),
};
assert_eq!(
find_key_places(&view, &state_clone.clone()).into_iter()
@ -887,7 +830,9 @@ mod procedures {
)
);
let view = View::new(vec![]);
let view = View {
rows: Vec::new(),
};
assert_eq!(
find_key_places(&view, &state_clone.clone()).is_empty(),
true
@ -1130,56 +1075,37 @@ mod test {
#[test]
fn check_centering() {
// A B
// foo
// ---bar---
let view = View::new(vec![
(
0.0,
Row::new(vec![
(
Row {
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 5.0, height: 10.0 },
..*make_button_with_state("A".into(), make_state())
size: Size { width: 10.0, height: 10.0 },
..*make_button_with_state("foo".into(), make_state())
}),
),
(
5.0,
Box::new(Button {
size: Size { width: 5.0, height: 10.0 },
..*make_button_with_state("B".into(), make_state())
}),
),
]),
)]
},
),
(
10.0,
Row::new(vec![
(
Row {
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 30.0, height: 10.0 },
..*make_button_with_state("bar".into(), make_state())
}),
),
]),
)]
},
)
]);
assert!(
view.find_button_by_position(c::Point { x: 5.0, y: 5.0 })
.unwrap().button.name.to_str().unwrap() == "A"
);
assert!(
view.find_button_by_position(c::Point { x: 14.99, y: 5.0 })
.unwrap().button.name.to_str().unwrap() == "A"
);
assert!(
view.find_button_by_position(c::Point { x: 15.01, y: 5.0 })
.unwrap().button.name.to_str().unwrap() == "B"
);
assert!(
view.find_button_by_position(c::Point { x: 25.0, y: 5.0 })
.unwrap().button.name.to_str().unwrap() == "B"
.is_none()
);
}
@ -1189,18 +1115,20 @@ mod test {
let view = View::new(vec![
(
0.0,
Row::new(vec![(
Row {
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 1.0, height: 1.0 },
..*make_button_with_state("foo".into(), make_state())
}),
)]),
)]
},
),
]);
let layout = Layout {
current_view: String::new(),
keymap_str: CString::new("").unwrap(),
keymaps: Vec::new(),
kind: ArrangementKind::Base,
pressed_keys: HashSet::new(),
// Lots of bottom margin

View File

@ -14,7 +14,6 @@ sources = [
config_h,
'dbus.c',
'imservice.c',
'popover.c',
'server-context-service.c',
'wayland.c',
'../eek/eek.c',

View File

@ -1,69 +0,0 @@
#include <gio/gio.h>
static void
call_dbus_cb (GDBusProxy *proxy,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (GError) err = NULL;
g_autoptr (GVariant) output = NULL;
output = g_dbus_proxy_call_finish (proxy, res, &err);
if (err) {
g_warning ("Can't open panel %s", err->message);
}
g_object_unref (proxy);
}
static void
create_dbus_proxy_cb (GObject *source_object, GAsyncResult *res, char *panel)
{
GDBusProxy *proxy;
g_autoptr (GError) err = NULL;
GVariantBuilder builder;
GVariant *params[3];
GVariant *array[1];
proxy = g_dbus_proxy_new_for_bus_finish (res, &err);
if (err != NULL) {
g_warning ("Can't open panel %s: %s", panel, err->message);
g_free (panel);
return;
}
g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
g_variant_builder_add (&builder, "v", g_variant_new_string (""));
array[0] = g_variant_new ("v", g_variant_new ("(sav)", panel, &builder));
params[0] = g_variant_new_string ("launch-panel");
params[1] = g_variant_new_array (G_VARIANT_TYPE ("v"), array, 1);
params[2] = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
g_dbus_proxy_call (proxy,
"Activate",
g_variant_new_tuple (params, 3),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) call_dbus_cb,
NULL);
g_free (panel);
}
void
popover_open_settings_panel (char *panel)
{
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.gnome.ControlCenter",
"/org/gnome/ControlCenter",
"org.gtk.Actions",
NULL,
(GAsyncReadyCallback) create_dbus_proxy_cb,
g_strdup (panel));
}

View File

@ -3,7 +3,6 @@
use gio;
use gtk;
use std::ffi::CString;
use std::cmp::Ordering;
use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale;
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
@ -26,15 +25,6 @@ use gtk::WidgetExt;
use std::io::Write;
use ::logging::Warn;
mod c {
use std::os::raw::c_char;
#[no_mangle]
extern "C" {
pub fn popover_open_settings_panel(panel: *const c_char);
}
}
mod variants {
use glib;
use glib::Variant;
@ -138,12 +128,6 @@ fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder {
xml,
"
</section>
<section>
<item>
<attribute name=\"label\" translatable=\"yes\">Keyboard Settings</attribute>
<attribute name=\"action\">settings</attribute>
</item>
</section>
</menu>
</interface>"
).unwrap();
@ -350,13 +334,8 @@ pub fn show(
.zip(all_layouts.clone().into_iter())
.collect();
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
// Sort first by layout then name
match (layout_a, layout_b) {
(LayoutId::Local(_), LayoutId::System { .. }) => Ordering::Greater,
(LayoutId::System { .. }, LayoutId::Local(_)) => Ordering::Less,
_ => compare_current_locale(&tr_a.0, &tr_b.0)
}
human_names.sort_unstable_by(|(tr_a, _), (tr_b, _)| {
compare_current_locale(&tr_a.0, &tr_b.0)
});
// GVariant doesn't natively support `enum`s,
@ -435,14 +414,8 @@ pub fn show(
menu_inner.popdown();
});
let settings_action = gio::SimpleAction::new("settings", None);
settings_action.connect_activate(move |_, _| {
unsafe { c::popover_open_settings_panel(CString::new("region").unwrap().as_ptr()) };
});
let action_group = gio::SimpleActionGroup::new();
action_group.add_action(&layout_action);
action_group.add_action(&settings_action);
menu.insert_action_group("popup", Some(&action_group));
};

View File

@ -18,13 +18,11 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("br", include_str!("../data/keyboards/br.yaml")),
("de", include_str!("../data/keyboards/de.yaml")),
("be", include_str!("../data/keyboards/be.yaml")),
("be_wide", include_str!("../data/keyboards/be_wide.yaml")),
("de_wide", include_str!("../data/keyboards/de_wide.yaml")),
("dk", include_str!("../data/keyboards/dk.yaml")),
("es", include_str!("../data/keyboards/es.yaml")),
("fi", include_str!("../data/keyboards/fi.yaml")),
("fr", include_str!("../data/keyboards/fr.yaml")),
("fr_wide", include_str!("../data/keyboards/fr_wide.yaml")),
("gr", include_str!("../data/keyboards/gr.yaml")),
("it", include_str!("../data/keyboards/it.yaml")),
("jp+kana", include_str!("../data/keyboards/jp+kana.yaml")),
@ -38,7 +36,6 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("ua", include_str!("../data/keyboards/ua.yaml")),
// layout+overlay
("terminal", include_str!("../data/keyboards/terminal.yaml")),
("terminal_wide", include_str!("../data/keyboards/terminal_wide.yaml")),
// Overlays
("emoji", include_str!("../data/keyboards/emoji.yaml")),
];

View File

@ -85,20 +85,13 @@ on_notify_unmap (ServerContextService *self, GtkWidget *widget)
}
static uint32_t
calculate_height(int32_t width, GdkRectangle *geometry)
calculate_height(int32_t width)
{
uint32_t height;
if (geometry->width > geometry->height) {
// 1:5 ratio works fine on lanscape mode, and makes sure there's
// room left for the app window
height = width / 5;
} else {
if (width < 540 && width > 0) {
uint32_t height = 180;
if (width < 360 && width > 0) {
height = ((unsigned)width * 7 / 12); // to match 360×210
} else {
// Here we switch to wide layout, less height needed
height = ((unsigned)width * 7 / 22);
}
} else if (width < 540) {
height = 180 + (540 - (unsigned)width) * 30 / 180; // smooth transition
}
return height;
}
@ -106,10 +99,6 @@ calculate_height(int32_t width, GdkRectangle *geometry)
static void
on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface)
{
GdkDisplay *display = NULL;
GdkWindow *window = NULL;
GdkMonitor *monitor = NULL;
GdkRectangle geometry;
gint width;
gint height;
@ -121,24 +110,11 @@ on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface)
"configured-height", &height,
NULL);
// In order to improve height calculation, we need the monitor geometry so
// we can use different algorithms for portrait and landscape mode.
// Note: this is a temporary fix until the size manager is complete.
display = gdk_display_get_default ();
if (display)
window = gtk_widget_get_window (GTK_WIDGET (surface));
if (window)
monitor = gdk_display_get_monitor_at_window (display, window);
if (monitor)
gdk_monitor_get_geometry (monitor, &geometry);
else
geometry.width = geometry.height = 0;
// When the geometry event comes after surface.configure,
// this entire height calculation does nothing.
// guint desired_height = squeek_uiman_get_perceptual_height(context->manager);
// Temporarily use old method, until the size manager is complete.
guint desired_height = calculate_height(width, &geometry);
guint desired_height = calculate_height(width);
guint configured_height = (guint)height;
// if height was already requested once but a different one was given

View File

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

View File

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

View File

@ -40,21 +40,29 @@ pub fn check_layout_file(path: &str) {
)
}
fn check_sym_presence(
state: &xkb::State,
sym_name: &str,
handler: &mut dyn logging::Handler,
) {
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();
let found = range.flat_map(|code| state.key_get_syms(code))
range.flat_map(|code| state.key_get_syms(code))
.find(|s| **s == sym)
.is_some();
if !found {
.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),
@ -72,25 +80,26 @@ fn check_layout(layout: Layout, allow_missing_return: bool) {
let layout = layout.expect("layout broken");
let xkb_states: Vec<xkb::State> = layout.keymaps.iter()
.map(|keymap_str| {
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap_str = layout.keymap_str
let keymap_str = keymap_str
.clone()
.into_string().expect("Failed to decode keymap string");
let keymap = xkb::Keymap::new_from_string(
&context,
keymap_str.clone(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).expect("Failed to create keymap");
xkb::State::new(&keymap)
})
.collect();
let state = xkb::State::new(&keymap);
check_sym_presence(&state, "BackSpace", &mut handler);
check_sym_presence(&xkb_states, "BackSpace", &mut handler);
let mut printer = logging::Print;
check_sym_presence(
&state,
&xkb_states,
"Return",
if allow_missing_return { &mut printer }
else { &mut handler },
@ -98,14 +107,23 @@ fn check_layout(layout: Layout, allow_missing_return: bool) {
// "Press" each button with keysyms
for (_pos, view) in layout.views.values() {
for (_y, row) in view.get_rows() {
for (_x, button) in row.get_buttons() {
for (_y, row) in &view.get_rows() {
for (_x, button) in &row.buttons {
let keystate = button.state.borrow();
for keycode in &keystate.keycodes {
match state.key_get_one_sym(*keycode) {
match xkb_states[keycode.keymap_idx].key_get_one_sym(keycode.code) {
xkb::KEY_NoSymbol => {
eprintln!("{}", keymap_str);
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
eprintln!(
"keymap {}: {}",
keycode.keymap_idx,
layout.keymaps[keycode.keymap_idx].to_str().unwrap(),
);
panic!(
"Keysym for code {:?} on key {} ({:?}) can't be resolved",
keycode,
button.name.to_string_lossy(),
button.name,
);
},
_ => {},
}

View File

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

View File

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

View File

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

View File

@ -50,12 +50,12 @@ endforeach
foreach layout : [
'us', 'us_wide',
'br',
'be', 'be_wide',
'be',
'de', 'de_wide',
'dk',
'es',
'fi',
'fr', 'fr_wide',
'fr',
'gr',
'it',
'jp+kana','jp+kana_wide',
@ -65,7 +65,7 @@ foreach layout : [
'ru',
'se',
'ua',
'terminal', 'terminal_wide',
'terminal',
'emoji',
]