WIP

WIP: keymap generation test passes

meta: Update features and version

WiP: cargo.lock

WIP: don't crash

WIP: no outlines

parsing: New tests

WIP: base level works

WIP: remove old keyboard

symbols correctly input

WIP: lodaing files

WIP: fallback works

Valid fallback
This commit is contained in:
Dorota Czaplejewicz
2019-09-01 11:38:05 +00:00
parent 3413021d30
commit b84c402c4a
41 changed files with 1319 additions and 2068 deletions

106
Cargo.lock generated Normal file
View File

@ -0,0 +1,106 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "dtoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "linked-hash-map"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rs"
version = "0.1.0"
dependencies = [
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_yaml"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "yaml-rust"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
"checksum proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "175a40b9cf564ce9bf050654633dbf339978706b8ead1a907bb970b63185dd95"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d"

View File

@ -2,6 +2,11 @@
name = "rs"
version = "0.1.0"
[dependencies]
maplit = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
[lib]
name = "rs"
path = "src/lib.rs"

View File

@ -3,14 +3,18 @@
*Squeekboard* is a virtual keyboard supporting Wayland, built primarily for the *Librem 5* phone.
It squeaks because some Rust got inside.
Features
--------
### Present
- GTK3
- Custom xml-defined keyboards
- Custom yaml-defined keyboards
- DBus interface to show and hide
- Use Wayland input method protocol to show and hide
- Use Wayland virtual keyboard protocol
### Temporarily dropped
@ -18,8 +22,7 @@ Features
### TODO
- Use Wayland virtual keyboard protocol
- Use Wayland text input protocol
- Pick up user-defined layouts
- Use Wayland input method protocol
- Pick up DBus interface files from /usr/share
@ -38,6 +41,7 @@ $ cd squeekboard
$ mkdir ../build
$ meson ../build/
$ cd ../build
$ ninja test
$ ninja install
```

View File

@ -1,64 +0,0 @@
<?xml version="1.0"?>
<geometry version="0.90">
<bounds x="10" y="10" width="410.0000" height="229"/>
<outline id="default" corner-radius="1.000000">
<point x="0.000000" y="0.000000"/>
<point x="37.46341" y="0.000000"/>
<point x="37.46341" y="52"/>
<point x="0.000000" y="52"/>
</outline>
<outline id="altline" corner-radius="1.000000">
<point x="0.000000" y="0.000000"/>
<point x="48.39024" y="0.000000"/>
<point x="48.39024" y="52"/>
<point x="0.000000" y="52"/>
</outline>
<outline id="outline7" corner-radius="1.000000">
<point x="0.000000" y="0.000000"/>
<point x="88.97561" y="0.000000"/>
<point x="88.97561" y="52"/>
<point x="0.000000" y="52"/>
</outline>
<outline id="spaceline" corner-radius="1.000000">
<point x="0.000000" y="0.000000"/>
<point x="150.5853" y="0.000000"/>
<point x="150.5853" y="52"/>
<point x="0.000000" y="52"/>
</outline>
<button name="Shift_L" oref="altline" />
<button name="BackSpace" oref="altline" />
<button name="preferences" oref="altline" />
<button name="show_numbers" oref="altline" keycode="0" />
<button name="show_letters" oref="altline" keycode="0" />
<button name="show_symbols" oref="altline" keycode="0" />
<button name="period" oref="altline" />
<button name="space" oref="spaceline" />
<button name="Return" oref="outline7" />
<view>
<section angle="0">q w e r t y u i o p</section>
<section angle="0">a s d f g h j k l</section>
<section angle="0"> Shift_L z x c v b n m BackSpace </section>
<section angle="0"> show_numbers preferences space period Return </section>
</view>
<view>
<section angle="0">Q W E R T Y U I O P</section>
<section angle="0">A S D F G H J K L</section>
<section angle="0"> Shift_L Z X C V B N M BackSpace </section>
<section angle="0"> show_numbers preferences space period Return </section>
</view>
<view>
<section angle="0">1 2 3 4 5 6 7 8 9 0</section>
<section angle="0">at numbersign dollar percent ampersand minus underscore plus parenleft parenright</section>
<section angle="0"> show_symbols comma quotedbl quoteright colon semicolon exclam question BackSpace </section>
<section angle="0"> show_letters preferences space period Return </section>
</view>
<view>
<section angle="0">asciitilde quoteleft bar U00B7 squareroot Greek_pi Greek_tau division multiply paragraph</section>
<section angle="0">copyright U00AE U00A3 EuroSign U00A5 asciicircum degree asterisk braceleft braceright</section>
<section angle="0"> show_numbers backslash slash less greater equal bracketleft bracketright BackSpace </section>
<section angle="0"> show_letters preferences space period Return </section>
</view>
</geometry>

View File

@ -1,118 +0,0 @@
<?xml version='1.0' encoding='ASCII' standalone='yes'?>
<symbols version="0.90">
<symbol label="*">asterisk</symbol>
<symbol label="+/=">show_symbols</symbol>
<symbol label="q">q</symbol>
<symbol label="Q">Q</symbol>
<symbol label="1">1</symbol>
<symbol label="~">asciitilde</symbol>
<symbol label="w">w</symbol>
<symbol label="W">W</symbol>
<symbol label="2">2</symbol>
<symbol label="`">quoteleft</symbol>
<symbol label="e">e</symbol>
<symbol label="E">E</symbol>
<symbol label="3">3</symbol>
<symbol label="|">bar</symbol>
<symbol label="r">r</symbol>
<symbol label="R">R</symbol>
<symbol label="4">4</symbol>
<symbol label="&#183;">U00B7</symbol>
<symbol label="t">t</symbol>
<symbol label="T">T</symbol>
<symbol label="5">5</symbol>
<symbol label="&#8730;">squareroot</symbol>
<symbol label="y">y</symbol>
<symbol label="Y">Y</symbol>
<symbol label="6">6</symbol>
<symbol label="&#960;">Greek_pi</symbol>
<symbol label="u">u</symbol>
<symbol label="U">U</symbol>
<symbol label="7">7</symbol>
<symbol label="&#247;">division</symbol>
<symbol label="i">i</symbol>
<symbol label="I">I</symbol>
<symbol label="8">8</symbol>
<symbol label="&#215;">multiply</symbol>
<symbol label="o">o</symbol>
<symbol label="O">O</symbol>
<symbol label="9">9</symbol>
<symbol label="&#182;">paragraph</symbol>
<symbol label="p">p</symbol>
<symbol label="P">P</symbol>
<symbol label="0">0</symbol>
<symbol label="&#964;">Greek_tau</symbol>
<symbol label="a">a</symbol>
<symbol label="A">A</symbol>
<symbol label="@">at</symbol>
<symbol label="&#169;">copyright</symbol>
<symbol label="s">s</symbol>
<symbol label="S">S</symbol>
<symbol label="#">numbersign</symbol>
<symbol label="&#174;">U00AE</symbol>
<symbol label="d">d</symbol>
<symbol label="D">D</symbol>
<symbol label="$">dollar</symbol>
<symbol label="&#163;">U00A3</symbol>
<symbol label="f">f</symbol>
<symbol label="F">F</symbol>
<symbol label="%">percent</symbol>
<symbol label="&#8364;">EuroSign</symbol>
<symbol label="g">g</symbol>
<symbol label="G">G</symbol>
<symbol label="&amp;">ampersand</symbol>
<symbol label="&#165;">U00A5</symbol>
<symbol label="h">h</symbol>
<symbol label="H">H</symbol>
<symbol label="-">minus</symbol>
<symbol label="^">asciicircum</symbol>
<symbol label="j">j</symbol>
<symbol label="J">J</symbol>
<symbol label="_">underscore</symbol>
<symbol label="&#176;">degree</symbol>
<symbol label="k">k</symbol>
<symbol label="K">K</symbol>
<symbol label="+">plus</symbol>
<symbol label="=">equal</symbol>
<symbol label="l">l</symbol>
<symbol label="L">L</symbol>
<symbol label="(">parenleft</symbol>
<symbol label="{">braceleft</symbol>
<symbol label=")">parenright</symbol>
<symbol label="}">braceright</symbol>
<symbol keyval="65293" icon="key-enter">Return</symbol>
<symbol keyval="65505" icon="key-shift">Shift_L</symbol>
<symbol label="z">z</symbol>
<symbol label="Z">Z</symbol>
<symbol label=",">comma</symbol>
<symbol label="\">backslash</symbol>
<symbol label="x">x</symbol>
<symbol label="X">X</symbol>
<symbol label="&quot;">quotedbl</symbol>
<symbol label="/">slash</symbol>
<symbol label="c">c</symbol>
<symbol label="C">C</symbol>
<symbol label="'">quoteright</symbol>
<symbol label="&lt;">less</symbol>
<symbol label="v">v</symbol>
<symbol label="V">V</symbol>
<symbol label=":">colon</symbol>
<symbol label="&gt;">greater</symbol>
<symbol label="b">b</symbol>
<symbol label="B">B</symbol>
<symbol label=";">semicolon</symbol>
<symbol label="n">n</symbol>
<symbol label="N">N</symbol>
<symbol label="!">exclam</symbol>
<symbol label="[">bracketleft</symbol>
<symbol label="m">m</symbol>
<symbol label="M">M</symbol>
<symbol label="?">question</symbol>
<symbol label="]">bracketright</symbol>
<symbol label=".">period</symbol>
<symbol label="123">show_numbers</symbol>
<symbol label="ABC">show_letters</symbol>
<symbol label="&#9786;" icon="keyboard-mode-symbolic" tooltip="Setup">preferences</symbol>
<symbol label=" ">space</symbol>
<symbol keyval="65288" icon="edit-clear-symbolic">BackSpace</symbol>
</symbols>

165
data/keyboards/us.yaml Normal file
View File

@ -0,0 +1,165 @@
---
bounds: { x: 10, y: 10, width: 410, height: 229 }
outlines:
default:
corner_radius: 1
bounds: { x: 0, y: 0, width: 37.46341, height: 52 }
altline:
corner_radius: 1
bounds: { x: 0, y: 0, width: 48.39024, height: 52 }
outline7:
corner_radius: 1
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
corner_radius: 1
bounds: { x: 0, y: 0, width: 150.5853, height: 52 }
views:
base:
- "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 period Return"
upper:
- "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 period Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "at numbersign dollar percent ampersand minus underscore plus parenleft parenright"
- "show_symbols comma quotedbl quoteright colon semicolon exclam question BackSpace"
- "show_letters preferences space period Return"
symbols:
- "asciitilde quoteleft bar U00B7 squareroot Greek_pi Greek_tau division multiply paragraph"
- "copyright U00AE U00A3 EuroSign U00A5 asciicircum degree asterisk braceleft braceright"
- "show_numbers backslash slash less greater equal bracketleft bracketright BackSpace"
- "show_letters preferences space period Return"
buttons:
Shift_L:
action:
set_view: "upper"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
preferences:
action: "show_prefs"
outline: "altline"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "altline"
label: "ABC"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "+/="
period:
outline: altline
label: "."
space:
outline: spaceline
label: " "
Return:
outline: outline7
icon: "key-enter"
asterisk:
label: "*"
asciitilde:
label: "~"
quoteleft:
label: "`"
bar:
label: "|"
U00B7:
label: "·"
squareroot:
label: "√"
Greek_pi:
label: "π"
division:
label: "÷"
multiply:
label: "×"
paragraph:
label: "¶"
Greek_tau:
label: "τ"
copyright:
label: "©"
numbersign:
label: "#"
U00AE:
label: "®"
at:
label: "@"
dollar:
label: "$"
U00A3:
label: "£"
percent:
label: "%"
EuroSign:
label: "€"
ampersand:
label: "&"
U00A5:
label: "¥"
minus:
label: "-"
asciicircum:
label: "^"
underscore:
label: "_"
degree:
label: "°"
plus:
label: "+"
equal:
label: "="
parenleft:
label: "("
parenright:
label: ")"
braceleft:
label: "{"
braceright:
label: "}"
comma:
label: ","
backslash:
label: "\\"
slash:
label: "/"
quotedbl:
label: "\""
quoteright:
label: "'"
less:
label: "<"
greater:
label: ">"
colon:
label: ":"
semicolon:
label: ";"
exclam:
label: "!"
question:
label: "?"
bracketleft:
label: "["
bracketright:
label: "]"

View File

@ -2,7 +2,6 @@
<gresources>
<gresource prefix="/sm/puri/squeekboard">
<file compressed="true">style.css</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/geometry/compact.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/geometry/extended.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/geometry/number-keypad.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/keyboards.xml</file>
@ -33,7 +32,6 @@
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/th.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/ua.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/ug.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/us.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/zh-bopomofo.xml</file>
<file compressed="true" preprocess="xml-stripblanks">keyboards/symbols/special/number.xml</file>
<file>icons/key-enter.svg</file>

7
debian/changelog vendored
View File

@ -1,3 +1,10 @@
squeekboard (1.1.0) unstable; urgency=medium
* Use new keyboard layout format
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 02 Sep 2019 10:12:02 +0000
squeekboard (1.0.10) unstable; urgency=medium
* Use a shared DBus definition

View File

@ -34,7 +34,6 @@
#include "eek-renderer.h"
#include "eek-keyboard.h"
#include "src/symbol.h"
#include "eek-gtk-keyboard.h"
@ -109,7 +108,7 @@ eek_gtk_keyboard_real_draw (GtkWidget *self,
eek_renderer_render_keyboard (priv->renderer, cr);
struct squeek_view *view = priv->keyboard->views[priv->keyboard->level];
struct squeek_view *view = squeek_layout_get_current_view(priv->keyboard->layout);
/* redraw pressed key */
const GList *list = priv->keyboard->pressed_buttons;

View File

@ -34,7 +34,6 @@
#include "eekboard/key-emitter.h"
#include "keymap.h"
#include "src/keyboard.h"
#include "src/symbol.h"
#include "eek-keyboard.h"
@ -58,20 +57,20 @@ set_key_states (LevelKeyboard *keyboard,
struct squeek_button *button,
guint new_level)
{
struct squeek_key *key = squeek_button_get_key(button);
// Keys locking rules hardcoded for the time being...
const gchar *name = squeek_symbol_get_name(squeek_key_get_symbol(key));
const gchar *name = squeek_button_get_name(button);
// Lock the shift whenever it's pressed on the baselevel
// TODO: need to lock shift on the destination level
if (g_strcmp0(name, "Shift_L") == 0 && keyboard->level == 0) {
if (g_strcmp0(name, "Shift_L") == 0 && squeek_layout_get_level(keyboard->layout) == 0) {
EekModifierKey *modifier_key = g_slice_new (EekModifierKey);
modifier_key->modifiers = 0;
modifier_key->button = button;
keyboard->locked_buttons =
g_list_prepend (keyboard->locked_buttons, modifier_key);
struct squeek_key *key = squeek_button_get_key(button);
squeek_key_set_locked(key, true);
}
if (keyboard->level == 1) {
if (squeek_layout_get_level(keyboard->layout) == 1) {
// Only shift is locked in this state, unlock on any key press
for (GList *head = keyboard->locked_buttons; head; ) {
EekModifierKey *modifier_key = head->data;
@ -92,10 +91,10 @@ static void
set_level_from_press (LevelKeyboard *keyboard, struct squeek_button *button)
{
/* The levels are: 0 Letters, 1 Upper case letters, 2 Numbers, 3 Symbols */
guint level = keyboard->level;
guint level = squeek_layout_get_level(keyboard->layout);
/* Handle non-emitting keys */
if (button) {
const gchar *name = squeek_symbol_get_name(squeek_key_get_symbol(squeek_button_get_key(button)));
const gchar *name = squeek_button_get_name(button);
if (g_strcmp0(name, "show_numbers") == 0) {
level = 2;
} else if (g_strcmp0(name, "show_letters") == 0) {
@ -107,9 +106,7 @@ set_level_from_press (LevelKeyboard *keyboard, struct squeek_button *button)
}
}
keyboard->level = set_key_states(keyboard, button, level);
eek_layout_update_layout(keyboard);
squeek_layout_set_level(keyboard->layout, set_key_states(keyboard, button, level));
}
void eek_keyboard_press_button(LevelKeyboard *keyboard, struct squeek_button *button, guint32 timestamp) {
@ -117,10 +114,6 @@ void eek_keyboard_press_button(LevelKeyboard *keyboard, struct squeek_button *bu
squeek_key_set_pressed(key, TRUE);
keyboard->pressed_buttons = g_list_prepend (keyboard->pressed_buttons, button);
struct squeek_symbol *symbol = squeek_key_get_symbol(key);
if (!symbol)
return;
// Only take action about setting level *after* the key has taken effect, i.e. on release
//set_level_from_press (keyboard, key);
@ -142,10 +135,6 @@ void eek_keyboard_release_button(LevelKeyboard *keyboard,
}
}
struct squeek_symbol *symbol = squeek_button_get_symbol(button);
if (!symbol)
return;
set_level_from_press (keyboard, button);
// "Borrowed" from eek-context-service; doesn't influence the state but forwards the event
@ -156,18 +145,7 @@ void eek_keyboard_release_button(LevelKeyboard *keyboard,
}
void level_keyboard_deinit(LevelKeyboard *self) {
g_hash_table_destroy (self->names);
for (guint i = 0; i < self->outline_array->len; i++) {
EekOutline *outline = &g_array_index (self->outline_array,
EekOutline,
i);
g_slice_free1 (sizeof (EekPoint) * outline->num_points,
outline->points);
}
g_array_free (self->outline_array, TRUE);
for (guint i = 0; i < 4; i++) {
// free self->view[i];
}
squeek_layout_free(self->layout);
}
void level_keyboard_free(LevelKeyboard *self) {
@ -175,115 +153,18 @@ void level_keyboard_free(LevelKeyboard *self) {
g_free(self);
}
void level_keyboard_init(LevelKeyboard *self) {
self->outline_array = g_array_new (FALSE, TRUE, sizeof (EekOutline));
void level_keyboard_init(LevelKeyboard *self, struct squeek_layout *layout) {
self->layout = layout;
}
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_view *views[4], GHashTable *name_button_hash) {
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_layout *layout) {
LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1);
level_keyboard_init(keyboard);
for (uint i = 0; i < 4; i++) {
keyboard->views[i] = views[i];
}
level_keyboard_init(keyboard, layout);
keyboard->manager = manager;
keyboard->names = name_button_hash;
return keyboard;
}
/**
* eek_keyboard_find_key_by_name:
* @keyboard: an #EekKeyboard
* @name: a key name
*
* Find an #EekKey whose name is @name.
* Return value: (transfer none): #EekKey whose name is @name
*/
struct squeek_button*
eek_keyboard_find_button_by_name (LevelKeyboard *keyboard,
const gchar *name)
{
return g_hash_table_lookup (keyboard->names, name);
}
/**
* eek_keyboard_get_outline:
* @keyboard: an #EekKeyboard
* @oref: ID of the outline
*
* Get an outline associated with @oref in @keyboard.
* Returns: an #EekOutline, which should not be released
*/
EekOutline *
level_keyboard_get_outline (LevelKeyboard *keyboard,
guint oref)
{
if (oref > keyboard->outline_array->len)
return NULL;
return &g_array_index (keyboard->outline_array, EekOutline, oref);
}
/**
* eek_keyboard_get_keymap:
* @keyboard: an #EekKeyboard
*
* Get the keymap for the keyboard.
* Returns: a string containing the XKB keymap.
*/
gchar *
eek_keyboard_get_keymap(LevelKeyboard *keyboard)
{
/* Start the keycodes and symbols sections with their respective headers. */
gchar *keycodes = g_strdup(keymap_keycodes_header);
gchar *symbols = g_strdup(keymap_symbols_header);
/* Iterate over the keys in the name-to-key hash table. */
GHashTableIter iter;
gchar *button_name;
gpointer button_ptr;
g_hash_table_iter_init(&iter, keyboard->names);
while (g_hash_table_iter_next(&iter, (gpointer)&button_name, &button_ptr)) {
gchar *current, *line;
struct squeek_button *button = button_ptr;
struct squeek_key *key = squeek_button_get_key(button);
guint keycode = squeek_key_get_keycode(key);
/* Don't include invalid keycodes in the keymap. */
if (keycode == EEK_INVALID_KEYCODE)
continue;
/* Append a key name-to-keycode definition to the keycodes section. */
current = keycodes;
line = g_strdup_printf(" <%s> = %i;\n", (char *)button_name, keycode);
keycodes = g_strconcat(current, line, NULL);
g_free(line);
g_free(current);
// FIXME: free
const char *key_str = squeek_key_to_keymap_entry(
(char*)button_name,
key
);
current = symbols;
symbols = g_strconcat(current, key_str, NULL);
g_free(current);
}
/* Assemble the keymap file from the header, sections and footer. */
gchar *keymap = g_strconcat(keymap_header,
keycodes, " };\n\n",
symbols, " };\n\n",
keymap_footer, NULL);
g_free(keycodes);
g_free(symbols);
return keymap;
}
struct squeek_view *level_keyboard_current(LevelKeyboard *keyboard)
{
return keyboard->views[keyboard->level];
return squeek_layout_get_current_view(keyboard->layout);
}

View File

@ -42,37 +42,26 @@ typedef struct _EekModifierKey EekModifierKey;
/// Keyboard state holder
struct _LevelKeyboard {
struct squeek_view *views[4];
guint level;
struct squeek_layout *layout;
struct xkb_keymap *keymap;
int keymap_fd; // keymap formatted as XKB string
size_t keymap_len; // length of the data inside keymap_fd
GArray *outline_array;
GList *pressed_buttons; // struct squeek_button*
GList *locked_buttons; // struct squeek_button*
/* Map button names to button objects: */
GHashTable *names;
guint id; // as a key to layout choices
EekboardContextService *manager; // unowned reference
};
typedef struct _LevelKeyboard LevelKeyboard;
struct squeek_button *eek_keyboard_find_button_by_name(LevelKeyboard *keyboard,
const gchar *name);
/// Represents the path to the button within a view
struct button_place {
const struct squeek_row *row;
const struct squeek_button *button;
};
EekOutline *level_keyboard_get_outline
(LevelKeyboard *keyboard,
guint oref);
EekModifierKey *eek_modifier_key_copy
(EekModifierKey *modkey);
void eek_modifier_key_free
@ -81,11 +70,8 @@ void eek_modifier_key_free
void eek_keyboard_press_button(LevelKeyboard *keyboard, struct squeek_button *button, guint32 timestamp);
void eek_keyboard_release_button(LevelKeyboard *keyboard, struct squeek_button *button, guint32 timestamp);
gchar * eek_keyboard_get_keymap
(LevelKeyboard *keyboard);
struct squeek_view *level_keyboard_current(LevelKeyboard *keyboard);
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_view *views[], GHashTable *name_button_hash);
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_layout *layout);
void level_keyboard_deinit(LevelKeyboard *self);
void level_keyboard_free(LevelKeyboard *self);

View File

@ -45,9 +45,3 @@ void
eek_layout_init (EekLayout *self)
{
}
void
eek_layout_update_layout(LevelKeyboard *keyboard)
{
squeek_view_place_contents(level_keyboard_current(keyboard), keyboard);
}

View File

@ -56,14 +56,5 @@ struct _EekLayoutClass
GType eek_layout_get_type (void) G_GNUC_CONST;
void eek_layout_place_rows(LevelKeyboard *keyboard, struct squeek_view *level);
void eek_layout_update_layout(LevelKeyboard *keyboard);
LevelKeyboard *
level_keyboard_from_layout (EekLayout *layout,
gdouble initial_width,
gdouble initial_height);
G_END_DECLS
#endif /* EEK_LAYOUT_H */

View File

@ -24,8 +24,6 @@
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "src/symbol.h"
#include "eek-renderer.h"
enum {
@ -188,13 +186,6 @@ render_button_outline (EekRenderer *renderer,
gboolean active)
{
EekRendererPrivate *priv = eek_renderer_get_instance_private (renderer);
EekOutline *outline;
guint oref = squeek_button_get_oref(button);
outline = level_keyboard_get_outline (priv->keyboard, oref);
if (outline == NULL)
return;
EekBounds bounds = squeek_button_get_bounds(button);
gtk_style_context_set_state(priv->key_context,
active ? GTK_STATE_FLAG_ACTIVE : GTK_STATE_FLAG_NORMAL);
@ -214,18 +205,12 @@ render_button (EekRenderer *self,
gboolean active)
{
EekRendererPrivate *priv = eek_renderer_get_instance_private (self);
EekOutline *outline;
cairo_surface_t *outline_surface;
GHashTable *outline_surface_cache;
PangoLayout *layout;
PangoRectangle extents = { 0, };
EekColor foreground;
guint oref = squeek_button_get_oref (place->button);
outline = level_keyboard_get_outline (priv->keyboard, oref);
if (outline == NULL)
return;
/* render outline */
EekBounds bounds = squeek_button_get_bounds(place->button);
@ -234,7 +219,7 @@ render_button (EekRenderer *self,
else
outline_surface_cache = priv->outline_surface_cache;
outline_surface = g_hash_table_lookup (outline_surface_cache, outline);
outline_surface = g_hash_table_lookup (outline_surface_cache, place->button);
if (!outline_surface) {
cairo_t *cr;
@ -258,7 +243,7 @@ render_button (EekRenderer *self,
cairo_destroy (cr);
g_hash_table_insert (outline_surface_cache,
outline,
(gpointer)place->button,
outline_surface);
}
@ -267,16 +252,12 @@ render_button (EekRenderer *self,
eek_renderer_get_foreground_color (self, priv->key_context, &foreground);
/* render icon (if any) */
struct squeek_symbol *symbol = squeek_button_get_symbol(place->button);
if (!symbol)
return;
const char *icon_name = squeek_button_get_icon_name(place->button);
if (squeek_symbol_get_icon_name (symbol)) {
if (icon_name) {
gint scale = priv->scale_factor;
cairo_surface_t *icon_surface =
eek_renderer_get_icon_surface (self,
squeek_symbol_get_icon_name (symbol),
16 / priv->scale,
eek_renderer_get_icon_surface (self, icon_name, 16 / priv->scale,
scale);
if (icon_surface) {
gint width = cairo_image_surface_get_width (icon_surface);
@ -371,18 +352,16 @@ eek_renderer_real_render_button_label (EekRenderer *self,
{
EekRendererPrivate *priv = eek_renderer_get_instance_private (self);
const gchar *label;
const gchar *label = squeek_button_get_label(button);
if (!label) {
return;
}
PangoFontDescription *font;
PangoLayoutLine *line;
gdouble scale;
struct squeek_symbol *symbol = squeek_button_get_symbol(button);
if (!symbol)
return;
label = squeek_symbol_get_label (symbol);
if (!label)
return;
if (!priv->font) {
const PangoFontDescription *base_font;
@ -480,7 +459,7 @@ eek_renderer_real_render_keyboard (EekRenderer *self,
cairo_get_target (cr), 0, 0,
priv->allocation_width, priv->allocation_height);
render_keyboard_surface (self, priv->keyboard->views[priv->keyboard->level]);
render_keyboard_surface (self, squeek_layout_get_current_view(priv->keyboard->layout));
cairo_set_source_surface (cr, priv->keyboard_surface, 0.0, 0.0);
source = cairo_get_source (cr);

View File

@ -1,56 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "config.h"
#include "eek-section.h"
EekBounds eek_get_outline_size(LevelKeyboard *keyboard, uint32_t oref) {
EekOutline *outline = level_keyboard_get_outline (keyboard, oref);
if (outline && outline->num_points > 0) {
double minx = outline->points[0].x;
double maxx = minx;
double miny = outline->points[0].y;
double maxy = miny;
for (uint i = 1; i < outline->num_points; i++) {
EekPoint p = outline->points[i];
if (p.x < minx) {
minx = p.x;
} else if (p.x > maxx) {
maxx = p.x;
}
if (p.y < miny) {
miny = p.y;
} else if (p.y > maxy) {
maxy = p.y;
}
}
EekBounds key_bounds = {
.height = maxy - miny,
.width = maxx - minx,
.x = 0,
.y = 0,
};
return key_bounds;
}
EekBounds bounds = {0, 0, 0, 0};
return bounds;
}

View File

@ -1,34 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#if !defined(__EEK_H_INSIDE__) && !defined(EEK_COMPILATION)
#error "Only <eek/eek.h> can be included directly."
#endif
#ifndef EEK_SECTION_H
#define EEK_SECTION_H 1
/* Contains row-related functions that couldn't be done in Rust easily. */
#include <glib-object.h>
#include "eek-keyboard.h"
#include "src/layout.h"
#endif /* EEK_SECTION_H */

View File

@ -29,7 +29,6 @@
#include "eek-keyboard.h"
#include "src/keyboard.h"
#include "src/symbol.h"
#include "squeekboard-resources.h"
@ -62,21 +61,6 @@ G_DEFINE_BOXED_TYPE(EekXmlKeyboardDesc, eek_xml_keyboard_desc, eek_xml_keyboard_
static GList *parse_keyboards (const gchar *path,
GError **error);
static GList *parse_prerequisites
(const gchar *path,
GError **error);
static gboolean parse_geometry (const gchar *path,
struct squeek_view **views, GArray *outline_array, GHashTable *name_button_hash,
GError **error);
static gboolean parse_symbols_with_prerequisites
(const gchar *keyboards_dir,
const gchar *name,
LevelKeyboard *keyboard,
GList **loaded,
GError **error);
static gboolean parse_symbols (const gchar *path,
LevelKeyboard *keyboard,
GError **error);
static gboolean validate (const gchar **valid_path_list,
gsize valid_path_list_len,
@ -253,380 +237,6 @@ struct _GeometryParseData {
};
typedef struct _GeometryParseData GeometryParseData;
static GeometryParseData *
geometry_parse_data_new (struct squeek_view **views, GHashTable *name_button_hash, GArray *outline_array)
{
GeometryParseData *data = g_slice_new0 (GeometryParseData);
data->views = views;
data->outline_array = outline_array;
data->keyname_oref_hash =
g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
data->outlineid_oref_hash =
g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
data->name_button_hash = name_button_hash;
data->text = g_string_sized_new (BUFSIZE);
data->keycode = 8;
return data;
}
static void
geometry_parse_data_free (GeometryParseData *data)
{
g_hash_table_destroy (data->keyname_oref_hash);
g_hash_table_destroy (data->outlineid_oref_hash);
g_string_free (data->text, TRUE);
g_slice_free (GeometryParseData, data);
}
static const gchar *geometry_valid_path_list[] = {
"geometry",
"button/geometry",
"bounds/geometry",
"view/geometry",
"section/view/geometry",
"outline/geometry",
"point/outline/geometry",
};
static void
geometry_start_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
GeometryParseData *data = user_data;
const gchar *attribute;
if (!validate (geometry_valid_path_list,
G_N_ELEMENTS (geometry_valid_path_list),
element_name,
data->element_stack,
error)) {
return;
}
if (g_strcmp0 (element_name, "bounds") == 0) {
EekBounds bounds;
attribute = get_attribute (attribute_names, attribute_values, "x");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"x\" attribute for \"bounds\"");
return;
}
bounds.x = g_strtod (attribute, NULL);
attribute = get_attribute (attribute_names, attribute_values,
"y");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"y\" attribute for \"bounds\"");
return;
}
bounds.y = g_strtod (attribute, NULL);
attribute = get_attribute (attribute_names, attribute_values,
"width");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"width\" attribute for \"bounds\"");
return;
}
bounds.width = g_strtod (attribute, NULL);
attribute = get_attribute (attribute_names, attribute_values,
"height");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"height\" attribute for \"bounds\"");
return;
}
bounds.height = g_strtod (attribute, NULL);
data->bounds = bounds;
goto out;
}
if (g_strcmp0 (element_name, "view") == 0) {
/* Create an empty keyboard to which geometry and symbols
information are applied. */
struct squeek_view *view = squeek_view_new(data->bounds);
data->views[data->view_idx] = view;
}
if (g_strcmp0 (element_name, "section") == 0) {
gint angle = 0;
attribute = get_attribute (attribute_names, attribute_values,
"angle");
if (attribute != NULL) {
angle = strtol (attribute, NULL, 10);
}
data->row = squeek_view_create_row(data->views[data->view_idx], angle);
goto out;
}
if (g_strcmp0 (element_name, "button") == 0) {
const gchar *base_name = get_attribute (attribute_names, attribute_values,
"name");
if (base_name == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"name\" attribute for \"button\"");
return;
}
const gchar *oref_name = get_attribute (attribute_names, attribute_values,
"oref");
if (oref_name == NULL) {
oref_name = "default";
}
const gchar *keycode_name = get_attribute (attribute_names, attribute_values,
"keycode");
guint oref = GPOINTER_TO_UINT(g_hash_table_lookup(data->outlineid_oref_hash,
oref_name));
const gchar *name = base_name;
g_hash_table_insert (data->keyname_oref_hash,
g_strdup(name),
GUINT_TO_POINTER(oref));
struct squeek_button *button = g_hash_table_lookup(data->name_button_hash, name);
// never gets used! this section gets executed before any buttons get defined
if (button) {
if (keycode_name != NULL) {
// This sets the keycode for all buttons,
// since they share state
// TODO: get rid of this in the parser;
// this belongs after keymap is defined
struct squeek_key *key = squeek_button_get_key(button);
squeek_key_set_keycode(key, strtol (keycode_name, NULL, 10));
}
}
goto out;
}
if (g_strcmp0 (element_name, "outline") == 0) {
attribute = get_attribute (attribute_names, attribute_values, "id");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"id\" attribute for \"outline\"");
return;
}
data->outline_id = g_strdup (attribute);
attribute = get_attribute (attribute_names, attribute_values,
"corner-radius");
if (attribute != NULL)
data->corner_radius = g_strtod (attribute, NULL);
goto out;
}
if (g_strcmp0 (element_name, "point") == 0) {
EekPoint *point;
gdouble x, y;
attribute = get_attribute (attribute_names, attribute_values, "x");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"x\" attribute for \"bounds\"");
return;
}
x = g_strtod (attribute, NULL);
attribute = get_attribute (attribute_names, attribute_values, "y");
if (attribute == NULL) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"no \"y\" attribute for \"bounds\"");
return;
}
y = g_strtod (attribute, NULL);
point = g_slice_new (EekPoint);
point->x = x;
point->y = y;
data->points = g_slist_prepend (data->points, point);
goto out;
}
out:
data->element_stack = g_slist_prepend (data->element_stack,
g_strdup (element_name));
data->text->len = 0;
}
/**
* eek_keyboard_add_outline:
* @keyboard: an #EekKeyboard
* @outline: an #EekOutline
*
* Register an outline of @keyboard.
* Returns: an unsigned integer ID of the registered outline, for
* later reference
*/
static guint
add_outline (GArray *outline_array,
EekOutline *outline)
{
EekOutline *_outline;
_outline = eek_outline_copy (outline);
g_array_append_val (outline_array, *_outline);
/* don't use eek_outline_free here, so as to keep _outline->points */
g_slice_free (EekOutline, _outline);
return outline_array->len - 1;
}
static void
geometry_end_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
gpointer user_data,
GError **error)
{
GeometryParseData *data = user_data;
GSList *head = data->element_stack;
g_free (head->data);
data->element_stack = g_slist_next (data->element_stack);
g_slist_free1 (head);
const gchar *text = g_strndup (data->text->str, data->text->len);
if (g_strcmp0 (element_name, "view") == 0) {
data->view_idx++;
return;
}
if (g_strcmp0 (element_name, "section") == 0) {
// Split text on spaces and process each part
unsigned head = 0;
while (head < strlen(text)) {
// Skip to the first non-space character
for (; head < strlen(text); head++) {
if (text[head] != ' ') {
break;
}
}
unsigned start = head;
// Skip to the first space character
for (; head < strlen(text); head++) {
if (text[head] == ' ') {
break;
}
}
unsigned end = head;
/// Reached the end
if (start == end) {
break;
}
gchar *name = g_strndup (&text[start], end - start);
struct squeek_button *button = g_hash_table_lookup(data->name_button_hash, name);
if (!button) {
// Save button name together with its level,
// to account for buttons with the same name in multiple levels
guint keycode = data->keycode++;
guint oref = GPOINTER_TO_UINT(g_hash_table_lookup(data->keyname_oref_hash, name));
// default value gives idx 0, which is guaranteed to be occupied
button = squeek_row_create_button (data->row, keycode, oref);
g_hash_table_insert (data->name_button_hash,
g_strdup(name),
button);
} else {
struct squeek_button *new_button = squeek_row_create_button_with_state(data->row, button);
if (!new_button) {
g_set_error (error,
G_MARKUP_ERROR,
G_MARKUP_ERROR_MISSING_ATTRIBUTE,
"Couldn't create a shared button");
return;
}
}
}
data->row = NULL;
data->num_rows = 0;
return;
}
if (g_strcmp0 (element_name, "outline") == 0) {
EekOutline *outline = g_slice_new (EekOutline);
outline->corner_radius = data->corner_radius;
data->corner_radius = 0.0;
outline->num_points = g_slist_length (data->points);
outline->points = g_slice_alloc0 (sizeof (EekPoint) *
outline->num_points);
guint i;
for (i = 0, head = data->points = g_slist_reverse (data->points);
head && i < outline->num_points;
head = g_slist_next (head), i++) {
memcpy (&outline->points[i], head->data, sizeof (EekPoint));
g_slice_free1 (sizeof (EekPoint), head->data);
}
g_slist_free (data->points);
data->points = NULL;
guint oref = add_outline (data->outline_array, outline);
g_hash_table_insert (data->outlineid_oref_hash,
data->outline_id,
GUINT_TO_POINTER(oref));
return;
}
}
static void
geometry_text_callback (GMarkupParseContext *pcontext,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
GeometryParseData *data = user_data;
g_string_append_len (data->text, text, (gssize)text_len);
}
static const GMarkupParser geometry_parser = {
geometry_start_element_callback,
geometry_end_element_callback,
geometry_text_callback,
0,
0
};
struct _SymbolsParseData {
GSList *element_stack;
GString *text;
@ -641,139 +251,6 @@ struct _SymbolsParseData {
};
typedef struct _SymbolsParseData SymbolsParseData;
static SymbolsParseData *
symbols_parse_data_new (LevelKeyboard *keyboard)
{
SymbolsParseData *data = g_slice_new0 (SymbolsParseData);
data->keyboard = keyboard;
data->text = g_string_sized_new (BUFSIZE);
return data;
}
static void
symbols_parse_data_free (SymbolsParseData *data)
{
g_string_free (data->text, TRUE);
g_slice_free (SymbolsParseData, data);
}
static const gchar *symbols_valid_path_list[] = {
"symbols",
"symbol/symbols",
"include/symbols",
};
static void
symbols_start_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
SymbolsParseData *data = user_data;
const gchar *attribute;
if (!validate (symbols_valid_path_list,
G_N_ELEMENTS (symbols_valid_path_list),
element_name,
data->element_stack,
error))
return;
if (g_strcmp0 (element_name, "symbol") == 0) {
attribute = get_attribute (attribute_names, attribute_values,
"label");
if (attribute != NULL)
data->label = g_strdup (attribute);
attribute = get_attribute (attribute_names, attribute_values,
"icon");
if (attribute != NULL)
data->icon = g_strdup (attribute);
attribute = get_attribute (attribute_names, attribute_values,
"tooltip");
if (attribute != NULL)
data->tooltip = g_strdup (attribute);
}
data->element_stack = g_slist_prepend (data->element_stack,
g_strdup (element_name));
data->text->len = 0;
}
static void
symbols_end_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
gpointer user_data,
GError **error)
{
SymbolsParseData *data = user_data;
GSList *head = data->element_stack;
gchar *text;
g_free (head->data);
data->element_stack = g_slist_next (data->element_stack);
g_slist_free1 (head);
// TODO: this could all be moved to text handler
text = g_strndup (data->text->str, data->text->len);
if (g_strcmp0 (element_name, "symbol") == 0) {
gchar *name = text;
struct squeek_button *button = eek_keyboard_find_button_by_name (data->keyboard,
name);
if (button) {
squeek_key_add_symbol(
squeek_button_get_key(button),
element_name,
text,
data->keyval,
data->label,
data->icon,
data->tooltip
);
}
data->keyval = 0;
g_free(data->label);
data->label = NULL;
g_free(data->icon);
data->icon = NULL;
g_free(data->tooltip);
data->tooltip = NULL;
goto out;
}
if (g_strcmp0 (element_name, "invalid") == 0) {
goto out;
}
out:
g_free (text);
}
static void
symbols_text_callback (GMarkupParseContext *pcontext,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
SymbolsParseData *data = user_data;
g_string_append_len (data->text, text, text_len);
}
static const GMarkupParser symbols_parser = {
symbols_start_element_callback,
symbols_end_element_callback,
symbols_text_callback,
0,
0
};
struct _PrerequisitesParseData {
GSList *element_stack;
@ -783,157 +260,13 @@ struct _PrerequisitesParseData {
};
typedef struct _PrerequisitesParseData PrerequisitesParseData;
static PrerequisitesParseData *
prerequisites_parse_data_new (void)
{
PrerequisitesParseData *data = g_slice_new0 (PrerequisitesParseData);
data->text = g_string_sized_new (BUFSIZE);
return data;
}
static void
prerequisites_parse_data_free (PrerequisitesParseData *data)
{
g_list_free_full (data->prerequisites, g_free);
g_string_free (data->text, TRUE);
g_slice_free (PrerequisitesParseData, data);
}
static void
prerequisites_start_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
PrerequisitesParseData *data = user_data;
if (!validate (symbols_valid_path_list,
G_N_ELEMENTS (symbols_valid_path_list),
element_name,
data->element_stack,
error))
return;
data->element_stack = g_slist_prepend (data->element_stack,
g_strdup (element_name));
data->text->len = 0;
}
static void
prerequisites_end_element_callback (GMarkupParseContext *pcontext,
const gchar *element_name,
gpointer user_data,
GError **error)
{
PrerequisitesParseData *data = user_data;
GSList *head = data->element_stack;
g_free (head->data);
data->element_stack = g_slist_next (data->element_stack);
g_slist_free1 (head);
if (g_strcmp0 (element_name, "include") == 0) {
data->prerequisites = g_list_append (data->prerequisites,
g_strndup (data->text->str,
data->text->len));
}
}
static void
prerequisites_text_callback (GMarkupParseContext *pcontext,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
PrerequisitesParseData *data = user_data;
g_string_append_len (data->text, text, text_len);
}
static const GMarkupParser prerequisites_parser = {
prerequisites_start_element_callback,
prerequisites_end_element_callback,
prerequisites_text_callback,
0,
0
};
LevelKeyboard *
eek_xml_layout_real_create_keyboard (EekLayout *self,
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
EekboardContextService *manager)
{
EekXmlLayout *layout = EEK_XML_LAYOUT (self);
EekXmlLayoutPrivate *priv = eek_xml_layout_get_instance_private (layout);
gboolean retval;
/* Read geometry information. */
gchar *filename = g_strdup_printf ("%s.xml", priv->desc->geometry);
gchar *path = g_build_filename (priv->keyboards_dir, "geometry", filename, NULL);
g_free (filename);
GArray *outline_array = g_array_new (FALSE, TRUE, sizeof (EekOutline));
// char* -> struct squeek_button*
GHashTable *name_button_hash =
g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
// One view for each level
struct squeek_view *views[4] = {0};
GError *error = NULL;
retval = parse_geometry (path, views, outline_array, name_button_hash, &error);
g_free (path);
if (!retval) {
for (uint i = 0; i < 4; i++) {
g_object_unref (views[i]);
}
g_warning ("can't parse geometry file %s: %s",
priv->desc->geometry,
error->message);
g_error_free (error);
return NULL;
}
LevelKeyboard *keyboard = level_keyboard_new(manager, views, name_button_hash);
keyboard->outline_array = outline_array;
// FIXME: are symbols shared betwen views?
/* Read symbols information. */
GList *loaded = NULL;
retval = parse_symbols_with_prerequisites (priv->keyboards_dir,
priv->desc->symbols,
keyboard,
&loaded,
&error);
g_list_free_full (loaded, g_free);
if (!retval) {
for (uint i = 0; i < 4; i++) {
if (views[i]) {
g_object_unref(views[i]);
views[i] = NULL;
}
}
//g_object_unref (view);
g_warning ("can't parse symbols file %s: %s",
priv->desc->symbols,
error->message);
g_error_free (error);
return NULL;
}
for (uint i = 0; i < 4; i++) {
if (views[i]) {
squeek_view_place_contents(views[i], keyboard);
}
}
return keyboard;
struct squeek_layout *layout = squeek_load_layout(keyboard_type);
squeek_layout_place_contents(layout);
return level_keyboard_new(manager, layout);
}
static void
@ -1116,190 +449,6 @@ eek_xml_keyboard_desc_free (EekXmlKeyboardDesc *desc)
g_slice_free (EekXmlKeyboardDesc, desc);
}
static gboolean
parse_geometry (const gchar *path, struct squeek_view **views, GArray *outline_array, GHashTable *name_button_hash, GError **error)
{
GeometryParseData *data;
GMarkupParseContext *pcontext;
GFile *file;
GFileInputStream *input;
gboolean retval;
file = g_str_has_prefix (path, "resource://")
? g_file_new_for_uri (path)
: g_file_new_for_path (path);
input = g_file_read (file, NULL, error);
g_object_unref (file);
if (input == NULL)
return FALSE;
data = geometry_parse_data_new (views, name_button_hash, outline_array);
pcontext = g_markup_parse_context_new (&geometry_parser,
0,
data,
NULL);
retval = parse (pcontext, G_INPUT_STREAM (input), error);
g_markup_parse_context_free (pcontext);
g_object_unref (input);
if (!retval) {
geometry_parse_data_free (data);
return FALSE;
}
/* Resolve outline references. */
/*
* GHashTable *oref_hash;
GHashTableIter iter;
gpointer k, v;
oref_hash = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_iter_init (&iter, data->oref_outline_hash);
while (g_hash_table_iter_next (&iter, &k, &v)) {
EekOutline *outline = v;
gulong oref;
oref = add_outline (outline_array, outline);
g_hash_table_insert (oref_hash, k, GUINT_TO_POINTER(oref));
}*/
/*
g_hash_table_iter_init (&iter, data->key_oref_hash);
while (g_hash_table_iter_next (&iter, &k, &v)) {
gpointer oref;
if (g_hash_table_lookup_extended (oref_hash, v, NULL, &oref))
eek_key_set_oref (EEK_KEY(k), GPOINTER_TO_UINT(oref));
}*/
// g_hash_table_destroy (oref_hash);
geometry_parse_data_free (data);
return TRUE;
}
static gboolean
parse_symbols_with_prerequisites (const gchar *keyboards_dir,
const gchar *name,
LevelKeyboard *keyboard,
GList **loaded,
GError **error)
{
gchar *filename, *path;
GList *prerequisites, *p;
GError *prerequisites_error;
gboolean retval;
for (p = *loaded; p; p = p->next) {
if (g_strcmp0 (p->data, name) == 0) {
g_set_error (error,
EEK_ERROR,
EEK_ERROR_LAYOUT_ERROR,
"%s already loaded",
name);
return FALSE;
}
}
*loaded = g_list_prepend (*loaded, g_strdup (name));
filename = g_strdup_printf ("%s.xml", name);
path = g_build_filename (keyboards_dir, "symbols", filename, NULL);
g_free (filename);
prerequisites_error = NULL;
prerequisites = parse_prerequisites (path, &prerequisites_error);
if (prerequisites_error != NULL) {
g_propagate_error (error, prerequisites_error);
return FALSE;
}
for (p = prerequisites; p; p = p->next) {
retval = parse_symbols_with_prerequisites (keyboards_dir,
p->data,
keyboard,
loaded,
error);
if (!retval)
return FALSE;
}
g_list_free_full (prerequisites, (GDestroyNotify)g_free);
retval = parse_symbols (path, keyboard, error);
g_free (path);
return retval;
}
static gboolean
parse_symbols (const gchar *path, LevelKeyboard *keyboard, GError **error)
{
SymbolsParseData *data;
GMarkupParseContext *pcontext;
GFile *file;
GFileInputStream *input;
gboolean retval;
file = g_str_has_prefix (path, "resource://")
? g_file_new_for_uri (path)
: g_file_new_for_path (path);
input = g_file_read (file, NULL, error);
g_object_unref (file);
if (input == NULL)
return FALSE;
data = symbols_parse_data_new (keyboard);
pcontext = g_markup_parse_context_new (&symbols_parser,
0,
data,
NULL);
retval = parse (pcontext, G_INPUT_STREAM (input), error);
g_markup_parse_context_free (pcontext);
g_object_unref (input);
if (!retval) {
symbols_parse_data_free (data);
return FALSE;
}
symbols_parse_data_free (data);
return TRUE;
}
static GList *
parse_prerequisites (const gchar *path, GError **error)
{
PrerequisitesParseData *data;
GMarkupParseContext *pcontext;
GFile *file;
GFileInputStream *input;
GList *prerequisites;
gboolean retval;
file = g_str_has_prefix (path, "resource://")
? g_file_new_for_uri (path)
: g_file_new_for_path (path);
input = g_file_read (file, NULL, error);
g_object_unref (file);
if (input == NULL)
return FALSE;
data = prerequisites_parse_data_new ();
pcontext = g_markup_parse_context_new (&prerequisites_parser,
0,
data,
NULL);
retval = parse (pcontext, G_INPUT_STREAM (input), error);
g_markup_parse_context_free (pcontext);
g_object_unref (input);
if (!retval) {
prerequisites_parse_data_free (data);
return NULL;
}
prerequisites = data->prerequisites;
data->prerequisites = NULL;
prerequisites_parse_data_free (data);
return prerequisites;
}
static GList *
parse_keyboards (const gchar *path, GError **error)
{

View File

@ -63,7 +63,7 @@ EekXmlKeyboardDesc *eek_xml_keyboard_desc_copy (EekXmlKeyboardDesc *desc);
void eek_xml_keyboard_desc_free (EekXmlKeyboardDesc *desc);
LevelKeyboard *
eek_xml_layout_real_create_keyboard (EekLayout *self,
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
EekboardContextService *manager);
G_END_DECLS
#endif /* EEK_XML_LAYOUT_H */

View File

@ -6,34 +6,3 @@ squeek_keymap_get_entries_for_keyval (struct xkb_keymap *xkb_keymap,
guint keyval,
GdkKeymapKey **keys,
guint *n_keys);
static const char *keymap_header = "xkb_keymap {\n\
\n";
static const char *keymap_keycodes_header = "\
xkb_keycodes \"squeekboard\" {\n\n\
minimum = 8;\n\
maximum = 255;\n\
\n";
static const char *keymap_symbols_header = "\
xkb_symbols \"squeekboard\" {\n\
\n\
name[Group1] = \"Letters\";\n\
name[Group2] = \"Numbers/Symbols\";\n\
\n";
static const char *keymap_footer = "\
xkb_types \"squeekboard\" {\n\
\n\
type \"TWO_LEVEL\" {\n\
modifiers = Shift;\n\
map[Shift] = Level2;\n\
level_name[Level1] = \"Base\";\n\
level_name[Level2] = \"Shift\";\n\
};\n\
};\n\
\n\
xkb_compatibility \"squeekboard\" {\n\
};\n\
};";

View File

@ -89,62 +89,17 @@ static LevelKeyboard *
eekboard_context_service_real_create_keyboard (EekboardContextService *self,
const gchar *keyboard_type)
{
EekLayout *layout;
GError *error;
if (g_str_has_prefix (keyboard_type, "xkb:")) {
/* TODO: Depends on xklavier
XklConfigRec *rec =
eekboard_xkl_config_rec_from_string (&keyboard_type[4]);
if (display == NULL)
//display = XOpenDisplay (NULL);
return NULL; // FIXME: replace with wl display
error = NULL;
layout = eek_xkl_layout_new (display, &error);
if (layout == NULL) {
g_warning ("can't create keyboard %s: %s",
keyboard_type, error->message);
g_error_free (error);
return NULL;
}
if (!eek_xkl_layout_set_config (EEK_XKL_LAYOUT(layout), rec)) {
g_object_unref (layout);
return NULL;
}
*/
return NULL;
} else {
error = NULL;
layout = eek_xml_layout_new (keyboard_type, &error);
if (layout == NULL) {
g_warning ("can't create keyboard %s: %s",
keyboard_type, error->message);
g_error_free (error);
keyboard_type = "us";
error = NULL;
layout = eek_xml_layout_new (keyboard_type, &error);
if (layout == NULL) {
g_error ("failed to create fallback layout: %s", error->message);
g_error_free (error);
return NULL;
}
}
}
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(layout, self);
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(keyboard_type, self);
if (!keyboard) {
g_error("Failed to create a keyboard");
}
g_object_unref (layout);
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
g_error("No context created");
}
gchar *keymap_str = eek_keyboard_get_keymap(keyboard);
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);
@ -152,8 +107,6 @@ eekboard_context_service_real_create_keyboard (EekboardContextService *self,
if (!keymap)
g_error("Bad keymap:\n%s", keymap_str);
free(keymap_str);
xkb_context_unref(context);
keyboard->keymap = keymap;
@ -183,7 +136,6 @@ eekboard_context_service_real_create_keyboard (EekboardContextService *self,
}
strcpy(ptr, keymap_str);
munmap(ptr, keyboard->keymap_len);
free(keymap_str);
return keyboard;
}

View File

@ -90,7 +90,7 @@ send_fake_key (SeatEmitter *emitter,
gboolean pressed,
uint32_t timestamp)
{
guint level = keyboard->level;
guint level = squeek_layout_get_level(keyboard->layout);
uint32_t group = (level / 2);
zwp_virtual_keyboard_v1_modifiers(emitter->virtual_keyboard, 0, 0, 0, group);

View File

@ -1,7 +1,7 @@
project(
'squeekboard',
'c', 'rust',
version: '1.0.10',
version: '1.1.0',
license: 'GPLv3',
meson_version: '>=0.51.0',
default_options: [

483
src/data.rs Normal file
View File

@ -0,0 +1,483 @@
/**! The parsing of the data files for layouts */
use std::cell::RefCell;
use std::collections::{ HashMap, HashSet };
use std::env;
use std::ffi::CString;
use std::fmt;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::rc::Rc;
use std::vec::Vec;
use ::keyboard::{
KeyState,
generate_keymap, generate_keycodes, FormattingError
};
use ::resources;
use ::util::c::as_str;
use ::xdg;
// traits, derives
use std::io::BufReader;
use std::iter::FromIterator;
use serde::Deserialize;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C"
fn squeek_load_layout(name: *const c_char) -> *mut ::layout::Layout {
let name = as_str(&name)
.expect("Bad layout name")
.expect("Empty layout name");
let layout = load_layout_with_fallback(name);
Box::into_raw(Box::new(layout))
}
}
const FALLBACK_LAYOUT_NAME: &str = "us";
#[derive(Debug)]
enum LoadError {
BadData(Error),
MissingResource,
BadResource(serde_yaml::Error),
BadKeyMap(FormattingError),
}
impl fmt::Display for LoadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::LoadError::*;
match self {
BadData(e) => write!(f, "Bad data: {}", e),
MissingResource => write!(f, "Missing resource"),
BadResource(e) => write!(f, "Bad resource: {}", e),
BadKeyMap(e) => write!(f, "Bad key map: {}", e),
}
}
}
fn load_layout_from_resource(
name: &str
) -> Result<Layout, LoadError> {
let data = resources::get_keyboard(name)
.ok_or(LoadError::MissingResource)?;
serde_yaml::from_str(data)
.map_err(|e| LoadError::BadResource(e))
}
enum DataSource {
File(PathBuf),
Resource(String),
}
impl fmt::Display for DataSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataSource::File(path) => write!(f, "Path: {:?}", path.display()),
DataSource::Resource(name) => write!(f, "Resource: {}", name),
}
}
}
/// Tries to load the layout from the first place where it's present.
/// If the layout exists, but is broken, fallback is activated.
fn load_layout(
name: &str
) -> (Result<::layout::Layout, LoadError>, DataSource) {
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
.map(PathBuf::from)
.or_else(|| xdg::data_path("squeekboard/keyboards"))
.map(|path| path.join(name).with_extension("yaml"));
let (layout, source) = match path {
Some(path) => {(
Layout::from_yaml_stream(path.clone())
.map_err(|e| LoadError::BadData(e)),
DataSource::File(path)
)},
None => {(
load_layout_from_resource(name),
DataSource::Resource(name.into())
)},
};
let layout = layout.and_then(
|layout| layout.build().map_err(LoadError::BadKeyMap)
);
(layout, source)
}
fn load_layout_with_fallback(
name: &str
) -> ::layout::Layout {
let (layout, source) = load_layout(name);
let (layout, source) = match (layout, source) {
(Err(e), source) => {
eprintln!(
"Failed to load layout from {}: {}, using fallback",
source, e
);
load_layout(FALLBACK_LAYOUT_NAME)
},
res => res,
};
let (layout, source) = match (layout, source) {
(Err(e), source) => {
eprintln!(
"Failed to load fallback layout from {}: {}, using hardcoded",
source, e
);
(
load_layout_from_resource(FALLBACK_LAYOUT_NAME)
.and_then(
|layout| layout.build().map_err(LoadError::BadKeyMap)
),
DataSource::Resource(FALLBACK_LAYOUT_NAME.into()),
)
},
res => res,
};
match (layout, source) {
(Err(e), source) => {
panic!(
format!("Failed to load hardcoded layout from {}: {:?}",
source, e
)
);
},
(Ok(layout), source) => {
eprintln!("Loaded layout from {}", source);
layout
}
}
}
/// The root element describing an entire keyboard
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct Layout {
bounds: Bounds,
views: HashMap<String, Vec<ButtonIds>>,
#[serde(default)]
buttons: HashMap<String, ButtonMeta>,
outlines: HashMap<String, Outline>
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct Bounds {
x: f64,
y: f64,
width: f64,
height: f64,
}
/// Buttons are embedded in a single string
type ButtonIds = String;
#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct ButtonMeta {
/// Action other than keysym (conflicts with keysym)
action: Option<Action>,
/// The name of the outline. If not present, will be "default"
outline: Option<String>,
/// FIXME: start using it
keysym: Option<String>,
/// If not present, will be derived from the button ID
label: Option<String>,
/// Conflicts with label
icon: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
enum Action {
/// FIXME: start using it
#[serde(rename="set_view")]
SetView(String),
#[serde(rename="show_prefs")]
ShowPrefs,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct Outline {
corner_radius: f64,
bounds: Bounds,
}
#[derive(Debug)]
enum Error {
Yaml(serde_yaml::Error),
Io(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Yaml(e) => write!(f, "YAML: {}", e),
Error::Io(e) => write!(f, "IO: {}", e),
}
}
}
impl Layout {
fn from_yaml_stream(path: PathBuf) -> Result<Layout, Error> {
let infile = BufReader::new(
fs::OpenOptions::new()
.read(true)
.open(&path)
.map_err(Error::Io)?
);
serde_yaml::from_reader(infile)
.map_err(Error::Yaml)
}
fn build(self) -> Result<::layout::Layout, FormattingError> {
let button_names = self.views.values()
.flat_map(|rows| {
rows.iter()
.flat_map(|row| row.split_ascii_whitespace())
});
let button_names: HashSet<&str>
= HashSet::from_iter(button_names);
let keycodes = generate_keycodes(
button_names.iter()
.map(|name| *name)
.filter(|name| {
match self.buttons.get(*name) {
// buttons with defined action can't emit keysyms
// and so don't need keycodes
Some(ButtonMeta { action: Some(_), .. }) => false,
_ => true,
}
})
);
let button_states = button_names.iter().map(|name| {(
String::from(*name),
Rc::new(RefCell::new(KeyState {
pressed: false,
locked: false,
keycode: keycodes.get(*name).map(|k| *k),
symbol: ::symbol::Symbol {
// FIXME: allow user to switch views
action: ::symbol::Action::Submit {
text: None,
// TODO: derive keysym name & value from button name
keys: vec!(),
},
},
}))
)});
let button_states =
HashMap::<String, Rc<RefCell<KeyState>>>::from_iter(
button_states
);
// TODO: generate from symbols
let keymap_str = generate_keymap(&button_states)?;
let views = HashMap::from_iter(
self.views.iter().map(|(name, view)| {(
String::from(name),
Box::new(::layout::View {
bounds: ::layout::c::Bounds {
x: self.bounds.x,
y: self.bounds.y,
width: self.bounds.width,
height: self.bounds.height,
},
rows: view.iter().map(|row| {
Box::new(::layout::Row {
angle: 0,
bounds: None,
buttons: row.split_ascii_whitespace().map(|name| {
Box::new(create_button(
&self.buttons,
&self.outlines,
name,
button_states.get(name.into())
.expect("Button state not created")
.clone()
))
}).collect(),
})
}).collect(),
})
)})
);
Ok(::layout::Layout {
current_view: "base".into(),
views: views,
keymap_str: {
CString::new(keymap_str)
.expect("Invalid keymap string generated")
},
})
}
}
/// TODO: Since this will receive user-provided data,
/// all .expect() on them should be turned into soft fails
fn create_button(
button_info: &HashMap<String, ButtonMeta>,
outlines: &HashMap<String, Outline>,
name: &str,
state: Rc<RefCell<KeyState>>,
) -> ::layout::Button {
let cname = CString::new(name.clone())
.expect("Bad name");
// don't remove, because multiple buttons with the same name are allowed
let default_meta = ButtonMeta::default();
let button_meta = button_info.get(name)
.unwrap_or(&default_meta);
// TODO: move conversion to the C/Rust boundary
let label = if let Some(label) = &button_meta.label {
::layout::Label::Text(CString::new(label.as_str())
.expect("Bad label"))
} else if let Some(icon) = &button_meta.icon {
::layout::Label::IconName(CString::new(icon.as_str())
.expect("Bad icon"))
} else {
::layout::Label::Text(cname.clone())
};
let outline_name = match &button_meta.outline {
Some(outline) => {
if outlines.contains_key(outline) {
outline.clone()
} else {
eprintln!("Outline named {} does not exist! Using default for button {}", outline, name);
"default".into()
}
}
None => "default".into(),
};
let outline = outlines.get(&outline_name)
.map(|outline| (*outline).clone())
.unwrap_or_else(|| {
eprintln!("No default outline defied Using 1x1!");
Outline {
corner_radius: 0f64,
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
}
});
::layout::Button {
name: cname,
// TODO: do layout before creating buttons
bounds: ::layout::c::Bounds {
x: outline.bounds.x,
y: outline.bounds.y,
width: outline.bounds.width,
height: outline.bounds.height,
},
corner_radius: outline.corner_radius,
label: label,
state: state,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error as ErrorTrait;
#[test]
fn test_parse_path() {
assert_eq!(
Layout::from_yaml_stream(
PathBuf::from("tests/layout.yaml")
).unwrap(),
Layout {
bounds: Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 },
views: hashmap!(
"base".into() => vec!("test".into()),
),
buttons: hashmap!{
"test".into() => ButtonMeta {
icon: None,
keysym: None,
action: None,
label: Some("test".into()),
outline: None,
}
},
outlines: hashmap!{
"default".into() => Outline {
corner_radius: 1f64,
bounds: Bounds {
x: 0f64, y: 0f64, width: 0f64, height: 0f64
},
}
},
}
);
}
/// Check if the default protection works
#[test]
fn test_empty_views() {
let out = Layout::from_yaml_stream(PathBuf::from("tests/layout2.yaml"));
match out {
Ok(_) => assert!(false, "Data mistakenly accepted"),
Err(e) => {
let mut handled = false;
if let Error::Yaml(ye) = &e {
handled = ye.description() == "missing field `views`";
};
if !handled {
println!("Unexpected error {:?}", e);
assert!(false)
}
}
}
}
#[test]
fn test_extra_field() {
let out = Layout::from_yaml_stream(PathBuf::from("tests/layout3.yaml"));
match out {
Ok(_) => assert!(false, "Data mistakenly accepted"),
Err(e) => {
let mut handled = false;
if let Error::Yaml(ye) = &e {
handled = ye.description()
.starts_with("unknown field `bad_field`");
};
if !handled {
println!("Unexpected error {:?}", e);
assert!(false)
}
}
}
}
#[test]
fn parsing_fallback() {
assert!(load_layout_from_resource(FALLBACK_LAYOUT_NAME)
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
.is_ok()
);
}
}

View File

@ -4,6 +4,7 @@ use std::num::Wrapping;
use std::string::String;
use super::bitflags;
use ::util::c::into_cstring;
// Traits
use std::convert::TryFrom;
@ -13,15 +14,8 @@ use std::convert::TryFrom;
pub mod c {
use super::*;
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
fn into_cstring(s: *const c_char) -> Result<CString, std::ffi::NulError> {
CString::new(
unsafe {CStr::from_ptr(s)}.to_bytes()
)
}
// The following defined in C
/// struct zwp_input_method_v2*
@ -91,7 +85,9 @@ pub mod c {
{
let imservice = check_imservice(imservice, im).unwrap();
imservice.pending = IMProtocolState {
surrounding_text: into_cstring(text).expect("Received invalid string"),
surrounding_text: into_cstring(text)
.expect("Received invalid string")
.expect("Received null string"),
surrounding_cursor: cursor,
..imservice.pending.clone()
};

View File

@ -1,29 +1,29 @@
use std::vec::Vec;
/*! State of the emulated keyboard and keys */
use super::symbol;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::rc::Rc;
use std::string::FromUtf8Error;
use ::symbol::{ Symbol, Action };
use std::io::Write;
use std::iter::{ FromIterator, IntoIterator };
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use ::util::c::{ as_cstr, into_cstring };
use ::util::c::as_cstr;
use std::cell::RefCell;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use std::rc::Rc;
// traits
use std::borrow::ToOwned;
// The following defined in C
#[no_mangle]
extern "C" {
fn eek_keysym_from_name(name: *const c_char) -> u32;
}
/// The wrapped structure for KeyState suitable for handling in C
/// Since C doesn't respect borrowing rules,
/// RefCell will enforce them dynamically (only 1 writer/many readers)
@ -64,23 +64,6 @@ pub mod c {
// TODO: unwrapping
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
// TODO: this will receive data from the filesystem,
// so it should handle garbled strings in the future
#[no_mangle]
pub extern "C"
fn squeek_key_new(keycode: u32) -> CKeyState {
let state: Rc<RefCell<KeyState>> = Rc::new(RefCell::new(
KeyState {
pressed: false,
locked: false,
keycode: keycode,
symbol: None,
}
));
CKeyState::wrap(state)
}
#[no_mangle]
pub extern "C"
fn squeek_key_free(key: CKeyState) {
@ -115,103 +98,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn squeek_key_get_keycode(key: CKeyState) -> u32 {
return key.to_owned().keycode as u32;
}
#[no_mangle]
pub extern "C"
fn squeek_key_set_keycode(key: CKeyState, code: u32) {
key.borrow_mut(|key| key.keycode = code);
}
// TODO: this will receive data from the filesystem,
// so it should handle garbled strings in the future
#[no_mangle]
pub extern "C"
fn squeek_key_add_symbol(
key: CKeyState,
element: *const c_char,
text_raw: *const c_char, keyval: u32,
label: *const c_char, icon: *const c_char,
tooltip: *const c_char,
) {
let element = as_cstr(&element)
.expect("Missing element name");
let text = into_cstring(text_raw)
.unwrap_or_else(|e| {
eprintln!("Text unreadable: {}", e);
None
})
.and_then(|text| {
if text.as_bytes() == b"" {
None
} else {
Some(text)
}
});
let icon = into_cstring(icon)
.unwrap_or_else(|e| {
eprintln!("Icon name unreadable: {}", e);
None
});
use symbol::*;
// Only read label if there's no icon
let label = match icon {
Some(icon) => Label::IconName(icon),
None => Label::Text(
into_cstring(label)
.unwrap_or_else(|e| {
eprintln!("Label unreadable: {}", e);
Some(CString::new(" ").unwrap())
})
.unwrap_or_else(|| {
eprintln!("Label missing");
CString::new(" ").unwrap()
})
),
};
let tooltip = into_cstring(tooltip)
.unwrap_or_else(|e| {
eprintln!("Tooltip unreadable: {}", e);
None
});
key.borrow_mut(|key| {
if let Some(_) = key.symbol {
eprintln!("Key {:?} already has a symbol defined", text);
return;
}
key.symbol = Some(match element.to_bytes() {
b"symbol" => Symbol {
action: Action::Submit {
text: text,
keys: Vec::new(),
},
label: label,
tooltip: tooltip,
},
_ => panic!("unsupported element type {:?}", element),
});
});
}
#[no_mangle]
pub extern "C"
fn squeek_key_get_symbol(key: CKeyState) -> *const symbol::Symbol {
key.borrow_mut(|key| {
match key.symbol {
// This pointer stays after the function exits,
// so it must reference borrowed data and not any copy
Some(ref symbol) => symbol as *const symbol::Symbol,
None => ptr::null(),
}
})
return key.to_owned().keycode.unwrap_or(0u32);
}
#[no_mangle]
@ -225,20 +112,14 @@ pub mod c {
.to_str()
.expect("Bad key name");
let symbol_name = match key.to_owned().symbol {
Some(ref symbol) => match &symbol.action {
symbol::Action::Submit { text: Some(text), .. } => {
Some(
text.clone()
.into_string().expect("Bad symbol")
)
},
_ => None
},
None => {
eprintln!("Key {} has no symbol", key_name);
None
let symbol_name = match key.to_owned().symbol.action {
Action::Submit { text: Some(text), .. } => {
Some(
text.clone()
.into_string().expect("Bad symbol text")
)
},
_ => None
};
let inner = match symbol_name {
@ -251,7 +132,7 @@ pub mod c {
.into_raw()
}
#[no_mangle]
#[no_mangle]
pub extern "C"
fn squeek_key_get_action_name(
key_name: *const c_char,
@ -262,20 +143,14 @@ pub mod c {
.to_str()
.expect("Bad key name");
let symbol_name = match key.to_owned().symbol {
Some(ref symbol) => match &symbol.action {
symbol::Action::Submit { text: Some(text), .. } => {
Some(
text.clone()
.into_string().expect("Bad symbol")
)
},
_ => None
},
None => {
eprintln!("Key {} has no symbol", key_name);
None
let symbol_name = match key.to_owned().symbol.action {
Action::Submit { text: Some(text), .. } => {
Some(
text.clone()
.into_string().expect("Bad symbol text")
)
},
_ => None
};
let inner = match symbol_name {
@ -287,14 +162,115 @@ pub mod c {
.expect("Couldn't convert string")
.into_raw()
}
}
#[derive(Debug, Clone)]
pub struct KeyState {
pub pressed: bool,
pub locked: bool,
pub keycode: u32,
// TODO: remove the optionality of a symbol
pub symbol: Option<symbol::Symbol>,
pub keycode: Option<u32>,
pub symbol: Symbol,
}
/// Generates a mapping where each key gets a keycode, starting from 8
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
key_names: C
) -> HashMap<String, u32> {
HashMap::from_iter(
key_names.into_iter()
.map(|name| String::from(name))
.zip(8..)
)
}
#[derive(Debug)]
pub enum FormattingError {
Utf(FromUtf8Error),
Format(io::Error),
}
impl fmt::Display for FormattingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FormattingError::Utf(e) => write!(f, "UTF: {}", e),
FormattingError::Format(e) => write!(f, "Format: {}", e),
}
}
}
impl From<io::Error> for FormattingError {
fn from(e: io::Error) -> Self {
FormattingError::Format(e)
}
}
/// Generates a de-facto single level keymap. TODO: actually drop second level
/// TODO: take in keysym->keycode mapping
pub fn generate_keymap(
keystates: &HashMap::<String, Rc<RefCell<KeyState>>>
) -> Result<String, FormattingError> {
let mut buf: Vec<u8> = Vec::new();
writeln!(
buf,
"xkb_keymap {{
xkb_keycodes \"squeekboard\" {{
minimum = 8;
maximum = 255;"
)?;
for (name, state) in keystates.iter() {
if let Some(keycode) = state.borrow().keycode {
write!(
buf,
"
<{}> = {};",
name,
keycode
)?;
}
}
writeln!(
buf,
"
}};
xkb_symbols \"squeekboard\" {{
name[Group1] = \"Letters\";
name[Group2] = \"Numbers/Symbols\";"
)?;
for (name, state) in keystates.iter() {
if let Some(_) = state.borrow().keycode {
write!(
buf,
"
key <{}> {{ [ {0} ] }};",
name,
)?;
}
}
writeln!(
buf,
"
}};
xkb_types \"squeekboard\" {{
type \"TWO_LEVEL\" {{
modifiers = Shift;
map[Shift] = Level2;
level_name[Level1] = \"Base\";
level_name[Level2] = \"Shift\";
}};
}};
xkb_compatibility \"squeekboard\" {{
}};
}};"
)?;
String::from_utf8(buf).map_err(FormattingError::Utf)
}

View File

@ -9,17 +9,10 @@
struct squeek_button;
struct squeek_row;
struct squeek_view;
struct squeek_layout;
struct squeek_row *squeek_row_new(int32_t angle);
struct squeek_button *squeek_row_create_button (struct squeek_row *row,
guint keycode, guint oref);
struct squeek_button *squeek_row_create_button_with_state(struct squeek_row *row,
struct squeek_button *source);
void squeek_row_set_angle(struct squeek_row *row, int32_t angle);
int32_t squeek_row_get_angle(const struct squeek_row*);
EekBounds squeek_row_get_bounds(const struct squeek_row*);
void squeek_row_set_bounds(struct squeek_row* row, EekBounds bounds);
uint32_t squeek_row_contains(struct squeek_row*, struct squeek_button *button);
@ -33,34 +26,20 @@ void squeek_row_foreach(struct squeek_row*,
void squeek_row_free(struct squeek_row*);
/*
struct squeek_button *squeek_buttons_find_by_position(
const struct squeek_buttons *buttons,
double x, double y,
double origin_x, double origin_y,
double angle);
void squeek_buttons_add(struct squeek_buttons*, const struct squeek_button* button);
void squeek_buttons_remove_key(struct squeek_buttons*, const struct squeek_key* key);
*/
struct squeek_button *squeek_button_new(uint32_t keycode, uint32_t oref);
struct squeek_button *squeek_button_new_with_state(const struct squeek_button* source);
uint32_t squeek_button_get_oref(const struct squeek_button*);
EekBounds squeek_button_get_bounds(const struct squeek_button*);
void squeek_button_set_bounds(struct squeek_button* button, EekBounds bounds);
const char *squeek_button_get_label(const struct squeek_button*);
const char *squeek_button_get_icon_name(const struct squeek_button*);
const char *squeek_button_get_name(struct squeek_button*);
struct squeek_symbol *squeek_button_get_symbol (
const struct squeek_button *button);
struct squeek_key *squeek_button_get_key(const struct squeek_button*);
uint32_t *squeek_button_has_key(const struct squeek_button* button,
const struct squeek_key *key);
void squeek_button_print(const struct squeek_button* button);
struct squeek_view *squeek_view_new(EekBounds bounds);
struct squeek_row *squeek_view_create_row(struct squeek_view *, int32_t angle);
EekBounds squeek_view_get_bounds(const struct squeek_view*);
void squeek_view_set_bounds(const struct squeek_view*, EekBounds bounds);
typedef void (*RowCallback) (struct squeek_row *row, gpointer user_data);
void squeek_view_foreach(struct squeek_view*,
@ -70,9 +49,16 @@ void squeek_view_foreach(struct squeek_view*,
struct squeek_row *squeek_view_get_row(struct squeek_view *view,
struct squeek_button *button);
struct squeek_button *squeek_view_find_button_by_position(struct squeek_view *view, EekPoint point);
void
squeek_view_place_contents(struct squeek_view *view, LevelKeyboard *keyboard);
squeek_layout_place_contents(struct squeek_layout*);
struct squeek_view *squeek_layout_get_current_view(struct squeek_layout*);
uint32_t squeek_layout_get_level(struct squeek_layout*);
void squeek_layout_set_level(struct squeek_layout* layout, uint32_t level);
struct squeek_button *squeek_view_find_button_by_position(struct squeek_view *view, EekPoint point);
struct squeek_layout *squeek_load_layout(const char *type);
const char *squeek_layout_get_keymap(const struct squeek_layout*);
void squeek_layout_free(struct squeek_layout*);
#endif

View File

@ -1,4 +1,4 @@
/**
/*!
* Layout-related data.
*
* The `View` contains `Row`s and each `Row` contains `Button`s.
@ -18,6 +18,8 @@
*/
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CString;
use std::rc::Rc;
use std::vec::Vec;
@ -29,14 +31,15 @@ use ::symbol::*;
pub mod c {
use super::*;
use std::os::raw::c_void;
use std::ffi::CStr;
use std::os::raw::{ c_char, c_void };
use std::ptr;
// The following defined in C
#[repr(transparent)]
pub struct UserData(*const c_void);
/// The index in the relevant outline table
#[repr(C)]
#[derive(Clone, Debug)]
@ -60,57 +63,18 @@ pub mod c {
pub height: f64
}
impl Bounds {
pub fn zero() -> Bounds {
Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
}
}
type ButtonCallback = unsafe extern "C" fn(button: *mut ::layout::Button, data: *mut UserData);
type RowCallback = unsafe extern "C" fn(row: *mut ::layout::Row, data: *mut UserData);
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
#[no_mangle]
pub extern "C"
fn squeek_view_new(bounds: Bounds) -> *mut ::layout::View {
Box::into_raw(Box::new(::layout::View {
rows: Vec::new(),
bounds: bounds,
}))
}
#[no_mangle]
pub extern "C"
fn squeek_view_get_bounds(view: *const ::layout::View) -> Bounds {
unsafe { &*view }.bounds.clone()
}
#[no_mangle]
pub extern "C"
fn squeek_view_set_bounds(view: *mut ::layout::View, bounds: Bounds) {
unsafe { &mut *view }.bounds = bounds;
}
/// Places a row into the view and returns a reference to it
#[no_mangle]
pub extern "C"
fn squeek_view_create_row(
view: *mut ::layout::View,
angle: i32,
) -> *mut ::layout::Row {
let view = unsafe { &mut *view };
view.rows.push(Box::new(::layout::Row::new(angle)));
// Return the reference directly instead of a Box, it's not on the stack
// It will live as long as the Vec
let last_idx = view.rows.len() - 1;
// Caution: Box can't be returned directly,
// so returning a reference to its innards
view.rows[last_idx].as_mut() as *mut ::layout::Row
}
#[no_mangle]
#[no_mangle]
pub extern "C"
fn squeek_view_foreach(
view: *mut ::layout::View,
@ -123,68 +87,6 @@ pub mod c {
unsafe { callback(row, data) };
}
}
#[no_mangle]
pub extern "C"
fn squeek_row_new(angle: i32) -> *mut ::layout::Row {
Box::into_raw(Box::new(::layout::Row::new(angle)))
}
/// Places a button into the row and returns a reference to it
#[no_mangle]
pub extern "C"
fn squeek_row_create_button(
row: *mut ::layout::Row,
keycode: u32, oref: u32
) -> *mut ::layout::Button {
let row = unsafe { &mut *row };
let state: Rc<RefCell<::keyboard::KeyState>> = Rc::new(RefCell::new(
::keyboard::KeyState {
pressed: false,
locked: false,
keycode: keycode,
symbol: None,
}
));
row.buttons.push(Box::new(::layout::Button {
oref: OutlineRef(oref),
bounds: None,
state: state,
}));
// Return the reference directly instead of a Box, it's not on the stack
// It will live as long as the Vec
let last_idx = row.buttons.len() - 1;
// Caution: Box can't be returned directly,
// so returning a reference to its innards
row.buttons[last_idx].as_mut() as *mut ::layout::Button
}
/// Places a button into the row, copying its state,
/// and returns a reference to it
#[no_mangle]
pub extern "C"
fn squeek_row_create_button_with_state(
row: *mut ::layout::Row,
button: *const ::layout::Button,
) -> *mut ::layout::Button {
let row = unsafe { &mut *row };
let source = unsafe { &*button };
row.buttons.push(Box::new(source.clone()));
// Return the reference directly instead of a Box, it's not on the stack
// It will live as long as the Vec
let last_idx = row.buttons.len() - 1;
// Caution: Box can't be returned directly,
// so returning a reference to its innards directly
row.buttons[last_idx].as_mut() as *mut ::layout::Button
}
#[no_mangle]
pub extern "C"
fn squeek_row_set_angle(row: *mut ::layout::Row, angle: i32) {
let row = unsafe { &mut *row };
row.angle = angle;
}
#[no_mangle]
pub extern "C"
@ -203,14 +105,6 @@ pub mod c {
}
}
/// Set bounds by consuming the value
#[no_mangle]
pub extern "C"
fn squeek_row_set_bounds(row: *mut ::layout::Row, bounds: Bounds) {
let row = unsafe { &mut *row };
row.bounds = Some(bounds);
}
#[no_mangle]
pub extern "C"
fn squeek_row_foreach(
@ -231,59 +125,13 @@ pub mod c {
unsafe { Box::from_raw(row) }; // gets dropped
}
#[no_mangle]
pub extern "C"
fn squeek_button_new(keycode: u32, oref: u32) -> *mut ::layout::Button {
let state: Rc<RefCell<::keyboard::KeyState>> = Rc::new(RefCell::new(
::keyboard::KeyState {
pressed: false,
locked: false,
keycode: keycode,
symbol: None,
}
));
Box::into_raw(Box::new(::layout::Button {
oref: OutlineRef(oref),
bounds: None,
state: state,
}))
}
#[no_mangle]
pub extern "C"
fn squeek_button_new_with_state(source: *mut ::layout::Button) -> *mut ::layout::Button {
let source = unsafe { &*source };
let button = Box::new(source.clone());
Box::into_raw(button)
}
#[no_mangle]
pub extern "C"
fn squeek_button_get_oref(button: *const ::layout::Button) -> u32 {
let button = unsafe { &*button };
button.oref.0
}
// Bounds transparently mapped to C, therefore no pointer needed
#[no_mangle]
pub extern "C"
fn squeek_button_get_bounds(button: *const ::layout::Button) -> Bounds {
let button = unsafe { &*button };
match &button.bounds {
Some(bounds) => bounds.clone(),
None => panic!("Button doesn't have any bounds yet"),
}
button.bounds.clone()
}
/// Set bounds by consuming the value
#[no_mangle]
pub extern "C"
fn squeek_button_set_bounds(button: *mut ::layout::Button, bounds: Bounds) {
let button = unsafe { &mut *button };
button.bounds = Some(bounds);
}
/// Borrow a new reference to key state. Doesn't need freeing
#[no_mangle]
pub extern "C"
@ -302,12 +150,43 @@ pub mod c {
) -> *const Symbol {
let button = unsafe { &*button };
let state = button.state.borrow();
match state.symbol {
Some(ref symbol) => symbol as *const Symbol,
None => ptr::null(),
&state.symbol as *const Symbol
}
#[no_mangle]
pub extern "C"
fn squeek_button_get_label(
button: *const ::layout::Button
) -> *const c_char {
let button = unsafe { &*button };
match &button.label {
Label::Text(text) => text.as_ptr(),
// returning static strings to C is a bit cumbersome
Label::IconName(_) => unsafe {
// CStr doesn't allocate anything, so it only points to
// the 'static str, avoiding a memory leak
CStr::from_bytes_with_nul_unchecked(b"icon\0")
}.as_ptr(),
}
}
#[no_mangle]
pub extern "C"
fn squeek_button_get_icon_name(button: *const Button) -> *const c_char {
let button = unsafe { &*button };
match &button.label {
Label::Text(_) => ptr::null(),
Label::IconName(name) => name.as_ptr(),
}
}
#[no_mangle]
pub extern "C"
fn squeek_button_get_name(button: *const Button) -> *const c_char {
let button = unsafe { &*button };
button.name.as_ptr()
}
#[no_mangle]
pub extern "C"
fn squeek_button_has_key(
@ -328,13 +207,61 @@ pub mod c {
println!("{:?}", button);
}
#[no_mangle]
pub extern "C"
fn squeek_layout_get_current_view(layout: *const Layout) -> *const View {
let layout = unsafe { &*layout };
let view_name = layout.current_view.clone();
layout.views.get(&view_name)
.expect("Current view doesn't exist")
.as_ref() as *const View
}
/// FIXME: very temporary way to minimize level impact
#[no_mangle]
pub extern "C"
fn squeek_layout_get_level(layout: *const Layout) -> u32 {
let layout = unsafe { &*layout };
match layout.current_view.as_str() {
"base" => 0,
"upper" => 1,
"numbers" => 2,
"symbols" => 3,
_ => 0
}
}
#[no_mangle]
pub extern "C"
fn squeek_layout_set_level(layout: *mut Layout, level: u32) {
let mut layout = unsafe { &mut*layout };
layout.current_view = String::from(match level {
0 => "base",
1 => "upper",
2 => "numbers",
3 => "symbols",
_ => "base",
})
}
#[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_free(layout: *mut Layout) {
unsafe { Box::from_raw(layout) };
}
/// Entry points for more complex procedures and algoithms which span multiple modules
pub mod procedures {
use super::*;
#[repr(transparent)]
pub struct LevelKeyboard(*const c_void);
#[repr(C)]
#[derive(PartialEq, Debug)]
pub struct ButtonPlace {
@ -344,11 +271,6 @@ pub mod c {
#[no_mangle]
extern "C" {
fn eek_get_outline_size(
keyboard: *const LevelKeyboard,
outline: u32
) -> Bounds;
/// Checks if point falls within bounds,
/// which are relative to origin and rotated by angle (I think)
pub fn eek_are_bounds_inside (bounds: Bounds,
@ -357,15 +279,6 @@ pub mod c {
angle: i32
) -> u32;
}
fn squeek_buttons_get_outlines(
buttons: &Vec<Box<Button>>,
keyboard: *const LevelKeyboard,
) -> Vec<Bounds> {
buttons.iter().map(|button| {
unsafe { eek_get_outline_size(keyboard, button.oref.0) }
}).collect()
}
/// Places each button in order, starting from 0 on the left,
/// keeping the spacing.
@ -375,17 +288,19 @@ pub mod c {
/// Sets button and row sizes according to their contents.
#[no_mangle]
pub extern "C"
fn squeek_view_place_contents(
view: *mut ::layout::View,
keyboard: *const LevelKeyboard, // source of outlines
fn squeek_layout_place_contents(
layout: *mut Layout,
) {
let view = unsafe { &mut *view };
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row|
squeek_buttons_get_outlines(&row.buttons, keyboard)
).collect();
let layout = unsafe { &mut *layout };
for view in layout.views.values_mut() {
let sizes: Vec<Vec<Bounds>> = view.rows.iter().map(|row| {
row.buttons.iter()
.map(|button| button.bounds.clone())
.collect()
}).collect();
view.place_buttons_with_sizes(sizes);
view.place_buttons_with_sizes(sizes);
}
}
fn squeek_row_contains(row: &Row, needle: *const Button) -> bool {
@ -454,35 +369,36 @@ pub mod c {
mod test {
use super::*;
use super::super::test::*;
#[test]
fn row_has_button() {
let mut row = Row::new(0);
let button = squeek_row_create_button(&mut row as *mut Row, 0, 0);
assert_eq!(squeek_row_contains(&row, button), true);
let shared_button = squeek_row_create_button_with_state(
&mut row as *mut Row,
button
let state = make_state();
let button = make_button_with_state(
"test".into(),
state.clone()
);
assert_eq!(squeek_row_contains(&row, shared_button), true);
let button_ptr = button_as_raw(&button);
let mut row = Row::new(0);
row.buttons.push(button);
assert_eq!(squeek_row_contains(&row, button_ptr), true);
let shared_button = make_button_with_state(
"test2".into(),
state
);
let shared_button_ptr = button_as_raw(&shared_button);
row.buttons.push(shared_button);
assert_eq!(squeek_row_contains(&row, shared_button_ptr), true);
let row = Row::new(0);
assert_eq!(squeek_row_contains(&row, button), false);
assert_eq!(squeek_row_contains(&row, button_ptr), false);
}
#[test]
fn view_has_button() {
let state = Rc::new(RefCell::new(::keyboard::KeyState {
pressed: false,
locked: false,
keycode: 0,
symbol: None,
}));
let state = make_state();
let state_clone = ::keyboard::c::CKeyState::wrap(state.clone());
let button = Box::new(Button {
oref: OutlineRef(0),
bounds: None,
state: state,
});
let button = make_button_with_state("1".into(), state);
let button_ptr = button.as_ref() as *const Button;
let row = Box::new(Row {
@ -535,18 +451,64 @@ pub mod c {
#[cfg(test)]
mod test {
use super::*;
use ::keyboard::c::CKeyState;
pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
Rc::new(RefCell::new(::keyboard::KeyState {
pressed: false,
locked: false,
keycode: None,
symbol: Symbol {
action: Action::SetLevel(0),
}
}))
}
pub fn make_button_with_state(
name: String,
state: Rc<RefCell<::keyboard::KeyState>>,
) -> Box<Button> {
Box::new(Button {
name: CString::new(name.clone()).unwrap(),
corner_radius: 0f64,
bounds: c::Bounds {
x: 0f64, y: 0f64, width: 0f64, height: 0f64
},
label: Label::Text(CString::new(name).unwrap()),
state: state,
})
}
pub fn button_as_raw(button: &Box<Button>) -> *const Button {
button.as_ref() as *const Button
}
#[test]
fn button_has_key() {
let button = squeek_button_new(0, 0);
let state = squeek_button_get_key(button);
assert_eq!(squeek_button_has_key(button, state.clone()), 1);
let other_button = squeek_button_new(0, 0);
assert_eq!(squeek_button_has_key(other_button, state.clone()), 0);
let other_state = ::keyboard::c::squeek_key_new(0);
assert_eq!(squeek_button_has_key(button, other_state), 0);
let shared_button = squeek_button_new_with_state(button);
assert_eq!(squeek_button_has_key(shared_button, state), 1);
let state = make_state();
let button = make_button_with_state("1".into(), state.clone());
assert_eq!(
squeek_button_has_key(
button_as_raw(&button),
CKeyState::wrap(state.clone())
),
1
);
let other_state = make_state();
let other_button = make_button_with_state("1".into(), other_state);
assert_eq!(
squeek_button_has_key(
button_as_raw(&other_button),
CKeyState::wrap(state.clone())
),
0
);
let orphan_state = CKeyState::wrap(make_state());
assert_eq!(
squeek_button_has_key(button_as_raw(&button), orphan_state),
0
);
}
}
}
@ -557,13 +519,25 @@ pub struct Size {
pub height: f64,
}
#[derive(Debug, Clone)]
pub enum Label {
/// Text used to display the symbol
Text(CString),
/// Icon name used to render the symbol
IconName(CString),
}
/// The graphical representation of a button
#[derive(Clone, Debug)]
pub struct Button {
oref: c::OutlineRef,
/// TODO: abolish Option, buttons should be created with bounds fully formed
/// ID string, e.g. for CSS
pub name: CString,
/// Label to display to the user
pub label: Label,
pub corner_radius: f64,
/// TODO: position the buttons before they get initial bounds
/// Position relative to some origin (i.e. parent/row)
bounds: Option<c::Bounds>,
pub bounds: c::Bounds,
/// current state, shared with other buttons
pub state: Rc<RefCell<KeyState>>,
}
@ -574,11 +548,11 @@ const ROW_SPACING: f64 = 7.0;
/// The graphical representation of a row of buttons
pub struct Row {
buttons: Vec<Box<Button>>,
pub buttons: Vec<Box<Button>>,
/// Angle is not really used anywhere...
angle: i32,
pub angle: i32,
/// Position relative to some origin (i.e. parent/view origin)
bounds: Option<c::Bounds>,
pub bounds: Option<c::Bounds>,
}
impl Row {
@ -639,9 +613,7 @@ impl Row {
};
let angle = self.angle;
self.buttons.iter_mut().find(|button| {
let bounds = button.bounds
.as_ref().expect("Missing bounds on button")
.clone();
let bounds = button.bounds.clone();
let point = point.clone();
let origin = origin.clone();
procedures::is_point_inside(bounds, point, origin, angle)
@ -651,8 +623,8 @@ impl Row {
pub struct View {
/// Position relative to keyboard origin
bounds: c::Bounds,
rows: Vec<Box<Row>>,
pub bounds: c::Bounds,
pub rows: Vec<Box<Row>>,
}
impl View {
@ -704,7 +676,7 @@ impl View {
for (mut button, button_position)
in row.buttons.iter_mut()
.zip(button_positions) {
button.bounds = Some(button_position);
button.bounds = button_position;
}
}
}
@ -727,6 +699,13 @@ impl View {
}
}
pub struct Layout {
pub current_view: String,
pub views: HashMap<String, Box<View>>,
// TODO: move to ::keyboard::Keyboard
pub keymap_str: CString,
}
mod procedures {
use super::*;

View File

@ -1,9 +1,16 @@
#[macro_use]
extern crate maplit;
extern crate serde;
#[macro_use]
mod bitflags;
mod data;
mod float_ord;
mod imservice;
mod keyboard;
mod layout;
mod resources;
mod symbol;
mod util;
mod xdg;

View File

@ -23,7 +23,6 @@ sources = [
'../eek/eek-keysym.c',
'../eek/eek-layout.c',
'../eek/eek-renderer.c',
'../eek/eek-section.c',
'../eek/eek-types.c',
'../eek/eek-xml-layout.c',
'../eek/layersurface.c',
@ -65,6 +64,7 @@ rslibs = custom_target(
build_always_stale: true,
output: ['librs.a'],
install: false,
console: true,
command: [cargo_script, '@CURRENT_SOURCE_DIR@', '@OUTPUT@', 'build']
)

22
src/resources.rs Normal file
View File

@ -0,0 +1,22 @@
/*! Statically linked resources.
* This could be done using GResource, but that would need additional work.
*/
const KEYBOARDS: &[(*const str, *const str)] = &[
("us", include_str!("../data/keyboards/us.yaml"))
];
pub fn get_keyboard(needle: &str) -> Option<&'static str> {
// Need to dereference in unsafe code
// comparing *const str to &str will compare pointers
KEYBOARDS.iter()
.find(|(name, _)| {
let name: *const str = *name;
(unsafe { &*name }) == needle
})
.map(|(_, value)| {
let value: *const str = *value;
unsafe { &*value }
})
}

View File

@ -1,24 +0,0 @@
#ifndef __SYMBOL_H
#define __SYMBOL_H
#include "stdbool.h"
#include "inttypes.h"
// Defined in Rust
struct squeek_symbol;
struct squeek_symbols;
void squeek_symbols_add(struct squeek_symbols*,
const char *element_name,
const char *text, uint32_t keyval,
const char *label, const char *icon,
const char *tooltip);
const char *squeek_symbol_get_name(struct squeek_symbol* symbol);
const char *squeek_symbol_get_label(struct squeek_symbol* symbol);
const char *squeek_symbol_get_icon_name(struct squeek_symbol* symbol);
uint32_t squeek_symbol_get_modifier_mask(struct squeek_symbol* symbol);
void squeek_symbol_print(struct squeek_symbol* symbol);
#endif

View File

@ -1,78 +1,7 @@
/*! The symbol object, defining actions that the key can do when activated */
use std::ffi::CString;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;
// The following defined in C
// Legacy; Will never be used in Rust as a bit field
enum ModifierMask {
Nothing = 0,
Shift = 1,
}
// The following defined in Rust.
// TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
// Symbols are owned by Rust and will move towards no C manipulation, so it may make sense not to wrap them
#[no_mangle]
pub extern "C"
fn squeek_symbol_get_name(symbol: *const Symbol) -> *const c_char {
let symbol = unsafe { &*symbol };
match &symbol.action {
Action::Submit { text: Some(text), .. } => text.as_ptr(),
_ => ptr::null(),
}
}
#[no_mangle]
pub extern "C"
fn squeek_symbol_get_label(symbol: *const Symbol) -> *const c_char {
let symbol = unsafe { &*symbol };
match &symbol.label {
Label::Text(text) => text.as_ptr(),
// returning static strings to C is a bit cumbersome
Label::IconName(_) => unsafe {
CStr::from_bytes_with_nul_unchecked(b"icon\0")
}.as_ptr(),
}
}
#[no_mangle]
pub extern "C"
fn squeek_symbol_get_icon_name(symbol: *const Symbol) -> *const c_char {
let symbol = unsafe { &*symbol };
match &symbol.label {
Label::Text(_) => ptr::null(),
Label::IconName(name) => name.as_ptr(),
}
}
// Legacy; throw away
#[no_mangle]
pub extern "C"
fn squeek_symbol_get_modifier_mask(symbol: *const Symbol) -> u32 {
let symbol = unsafe { &*symbol };
(match &symbol.action {
Action::SetLevel(1) => ModifierMask::Shift,
_ => ModifierMask::Nothing,
}) as u32
}
#[no_mangle]
pub extern "C"
fn squeek_symbol_print(symbol: *const Symbol) {
let symbol = unsafe { &*symbol };
println!("{:?}", symbol);
}
}
/// Just defines some int->identifier mappings for convenience
#[derive(Debug, Clone)]
pub enum KeySym {
@ -92,14 +21,6 @@ impl KeySym {
#[derive(Debug, Clone)]
pub struct XKeySym(pub u32);
#[derive(Debug, Clone)]
pub enum Label {
/// Text used to display the symbol
Text(CString),
/// Icon name used to render the symbol
IconName(CString),
}
/// Use to switch layouts
type Level = u8;
@ -119,7 +40,7 @@ pub enum Action {
SetModifier(Modifier),
/// Submit some text
Submit {
/// orig: Canonical name of the symbol
/// Text to submit with input-method
text: Option<CString>,
/// The key events this symbol submits when submitting text is not possible
keys: Vec<XKeySym>,
@ -131,8 +52,4 @@ pub enum Action {
pub struct Symbol {
/// The action that this key performs
pub action: Action,
/// Label to display to the user
pub label: Label,
// FIXME: is it used?
pub tooltip: Option<CString>,
}

View File

@ -3,7 +3,6 @@ pub mod c {
use std::os::raw::c_char;
use std::str::Utf8Error;
#[allow(dead_code)]
pub fn as_str(s: &*const c_char) -> Result<Option<&str>, Utf8Error> {
if s.is_null() {
Ok(None)
@ -13,7 +12,7 @@ pub mod c {
.map(Some)
}
}
pub fn as_cstr(s: &*const c_char) -> Option<&CStr> {
if s.is_null() {
None

63
src/xdg.rs Normal file
View File

@ -0,0 +1,63 @@
/*! XDG directory handling. */
/* Based on dirs-sys https://github.com/soc/dirs-sys-rs
* by "Simon Ochsenreither <simon@ochsenreither.de>",
* Licensed under either of
Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
*
* Based on dirs https://github.com/soc/dirs-rs
* by "Simon Ochsenreither <simon@ochsenreither.de>",
* Licensed under either of
Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
*
* Based on xdg https://github.com/whitequark/rust-xdg
* by "Ben Longbons <b.r.longbons@gmail.com>",
* "whitequark <whitequark@whitequark.org>",
rust-xdg is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
*
* The above crates were used to get
* a version without all the excessive dependencies.
*/
use std::env;
use std::ffi::OsString;
use std::path::{ Path, PathBuf };
fn is_absolute_path(path: OsString) -> Option<PathBuf> {
let path = PathBuf::from(path);
if path.is_absolute() {
Some(path)
} else {
None
}
}
fn home_dir() -> Option<PathBuf> {
return env::var_os("HOME")
.and_then(|h| if h.is_empty() { None } else { Some(h) })
.map(PathBuf::from);
}
fn data_dir() -> Option<PathBuf> {
env::var_os("XDG_DATA_HOME")
.and_then(is_absolute_path)
.or_else(|| home_dir().map(|h| h.join(".local/share")))
}
/// Returns the path to the directory within the data dir
pub fn data_path<P>(path: P) -> Option<PathBuf>
where P: AsRef<Path>
{
data_dir().map(|dir| {
dir.join(path.as_ref())
})
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "eek/eek.h"
#include <gtk/gtk.h>
static void
test_create (void)
{
EekBounds bounds = {0};
struct squeek_view *view = squeek_view_new(bounds);
struct squeek_button *button0, *button1;
struct squeek_row *row = squeek_view_create_row (view, 0);
g_assert (row);
button0 = squeek_row_create_button (row, 1, 0);
g_assert (button0);
button1 = squeek_row_create_button (row, 2, 0);
g_assert (button1);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/eek-simple-test/create", test_create);
return g_test_run ();
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
/* For gdk_x11_display_get_xdisplay(). See main(). */
#include <gtk/gtk.h>
#include "config.h"
#include "eek/eek.h"
#include "eek/eek-xml-layout.h"
static void
test_output_parse (void)
{
EekLayout *layout;
LevelKeyboard *keyboard;
GError *error;
error = NULL;
layout = eek_xml_layout_new ("us", &error);
g_assert_no_error (error);
/* We don't need the context service to parse an XML file, so we can pass
NULL when creating a keyboard. */
keyboard = eek_xml_layout_real_create_keyboard(layout, NULL);
g_object_unref (layout);
level_keyboard_free(keyboard);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/eek-xml-test/output-parse", test_output_parse);
return g_test_run ();
}

17
tests/layout.yaml Normal file
View File

@ -0,0 +1,17 @@
---
bounds:
x: 0
y: 0
width: 0
height: 0
views:
base:
- "test"
outlines:
default:
corner_radius: 1
bounds: { x: 0, y: 0, width: 0, height: 0 }
buttons:
test:
label: "test"

12
tests/layout2.yaml Normal file
View File

@ -0,0 +1,12 @@
---
# missing views
bounds:
x: 0
y: 0
width: 0
height: 0
outlines:
default:
corner_radius: 1
bounds: { x: 0, y: 0, width: 0, height: 0 }

16
tests/layout3.yaml Normal file
View File

@ -0,0 +1,16 @@
---
# extra field
bounds:
x: 0
y: 0
width: 0
height: 0
views:
base:
- "test"
outlines:
default:
corner_radius: 1
bounds: { x: 0, y: 0, width: 0, height: 0 }
bad_field: false

View File

@ -20,8 +20,6 @@ test_link_args = [
]
tests = [
'eek-simple-test',
'eek-xml-test',
'test-keymap-generation'
]

View File

@ -25,22 +25,16 @@
#include "config.h"
#include "eek/eek.h"
#include "eek/eek-xml-layout.h"
#include "eek/eek-keyboard.h"
#include "src/layout.h"
static void
test_check_xkb (void)
{
EekLayout *layout;
LevelKeyboard *keyboard;
GError *error;
error = NULL;
layout = eek_xml_layout_new ("us", &error);
g_assert_no_error (error);
keyboard = eek_xml_layout_real_create_keyboard(layout, NULL);
gchar *keymap_str = eek_keyboard_get_keymap(keyboard);
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard("us", NULL);
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
@ -50,14 +44,11 @@ test_check_xkb (void)
struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
free(keymap_str);
xkb_context_unref(context);
if (!keymap) {
g_error("Bad keymap");
}
g_object_unref (layout);
level_keyboard_free(keyboard);
}