Compare commits

..

30 Commits

Author SHA1 Message Date
7e57df3434 Release 1.6.0 "Specific impulse"
New emoji layout, `entry.py` becomes part of the -devel package under the name `squeekboard-entry`.
2020-01-02 12:06:19 +00:00
24cacf988b Merge branch 'type_fixes' into 'master'
Fix g_ and stdlib allocation/free mismatches

See merge request Librem5/squeekboard!294
2019-12-24 22:09:16 +00:00
8e55e9ee56 Fix g_ and stdlib allocation/free mismatches 2019-12-24 14:28:50 +00:00
e1a012cf50 Merge branch 'enoji' into 'master'
Enoji: implemented mechanism

See merge request Librem5/squeekboard!276
2019-12-24 14:21:51 +00:00
50dab14754 emoji: Add a passable layout 2019-12-24 10:41:02 +00:00
733cbe94ee Fix old Rust woes 2019-12-24 10:41:02 +00:00
01a8afad0d popover: Show overlays as selected 2019-12-24 10:41:02 +00:00
bafd1e6eb3 popover: Install emoji layout
In order to do that, an additional piece of state (layout switcher) was exposed to the event handlers, a separation between squeekboard-only and system layouts was introduced, along with a Translation structure to prevent mixing up strings.
2019-12-24 10:41:02 +00:00
a799178b6a Merge branch 'logging' into 'master'
Logging

See merge request Librem5/squeekboard!288
2019-12-24 10:10:30 +00:00
12be3e980f Merge branch 'cleanup' into 'master'
Build cleanups

See merge request Librem5/squeekboard!287
2019-12-23 21:34:18 +00:00
ca8a2baefa Merge branch 'devel' into 'master'
Entry test added to -devel package

See merge request Librem5/squeekboard!286
2019-12-23 21:33:41 +00:00
6b72d09fb8 Merge branch '1.5' into 'master'
Release 1.5.0 "The bends"

See merge request Librem5/squeekboard!293
2019-12-23 15:09:28 +00:00
d76deb0655 Release 1.5.0 "The bends"
This release introduces some renderer improvements, including relating to speed.

Mire importantly, layout files have an incompatible change in format: bounds on the layout are replaced by margins. This removes the possibility that buttons don't fit in the layout and frees layout makers from having to calculate the size of the layout manually.
2019-12-23 12:08:45 +00:00
3b0456b5cd Merge branch 'margins' into 'master'
Use margins, remove bounds

See merge request Librem5/squeekboard!283
2019-12-23 11:56:30 +00:00
77a6ca1900 Merge branch 'arm64' into 'master'
CI: Build arm64 .deb

See merge request Librem5/squeekboard!292
2019-12-20 08:32:54 +00:00
c0331434d7 CI: Build arm64 .deb 2019-12-19 19:38:34 +00:00
f335ea92d4 Merge branch 'readme' into 'master'
Readme updates

See merge request Librem5/squeekboard!281
2019-12-18 19:49:59 +00:00
c8dd34c829 logging: Add described log levels
Not used yet due to conflicts with other MRs.
2019-12-16 19:56:16 +00:00
4d656ff54d logging: Described the design 2019-12-16 19:36:02 +00:00
e2b89e85f7 logging: Move all facilities to one file 2019-12-16 19:15:40 +00:00
b6f3d9c1ea Remove unused header generator 2019-12-16 18:52:50 +00:00
9ff11dc511 Remove unused build dependencies 2019-12-16 18:22:23 +00:00
1cffdda09d packaging: Install entty.py as squeekboard-entry 2019-12-16 18:13:24 +00:00
d9f284b19a build: Move building of squeekboard-test-layout to tools 2019-12-16 17:47:00 +00:00
ed602faeaf tools: Move entry.py 2019-12-16 17:46:42 +00:00
257b97e9ca layout: Respect margins 2019-12-15 18:43:56 +00:00
5e700219f9 parsing: Remove bounds which weren't used anyway 2019-12-15 18:43:56 +00:00
d5c8f32088 Fix internal .md link 2019-12-11 15:50:28 +00:00
7641b4da6f Create a library/UI module separation 2019-12-11 15:33:29 +00:00
a84730198d readme: Add note about Cargo dependencies 2019-12-11 15:26:27 +00:00
49 changed files with 763 additions and 403 deletions

View File

@ -28,7 +28,22 @@ build_meson:
- ninja -C _build install - ninja -C _build install
build_deb: build_deb:
<<: *tags tags:
- librem5
stage: build
artifacts:
paths:
- "*.deb"
script:
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
build_deb_aarch64:
image: multiarch/debian-debootstrap:arm64-buster
tags:
- ARM64
allow_failure: true
stage: build stage: build
artifacts: artifacts:
paths: paths:

View File

@ -25,7 +25,7 @@ sudo apt-get -y build-dep .
``` ```
For an explicit list of dependencies check the `Build-Depends` entry in the For an explicit list of dependencies check the `Build-Depends` entry in the
[debian/control][] file. [`debian/control`](./debian/control) file.
Testing Testing
------- -------
@ -40,7 +40,7 @@ Most common testing is done in CI. Occasionally, and for each release, do perfor
Testing with an application: Testing with an application:
``` ```
python3 tests/entry.py python3 tools/entry.py
``` ```
Testing visibility: Testing visibility:
@ -62,6 +62,24 @@ $ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb',
Coding Coding
------ ------
### Project structure
Rust modules should be split into 2 categories: libraries, and user interface. They differ in the way they do error handling.
Libraries should:
- not panic due to external surprises, only due to internal inconsistencies
- pass errors and surprises they can't handle to the callers instead
- not silence errors and surprises
User interface modules should:
- try to provide safe values whenever they encounter an error
- do the logging
- give libraries the ability to report errors and surprises (e.g. via giving them loggers)
### Style
Code submitted should roughly match the style of surrounding code. Things that will *not* be accepted are ones that often lead to errors: Code submitted should roughly match the style of surrounding code. Things that will *not* be accepted are ones that often lead to errors:
- skipping brackets `{}` after every `if()`, `else`, and similar - skipping brackets `{}` after every `if()`, `else`, and similar
@ -122,6 +140,8 @@ sh /source_path/cargo.sh test
### Cargo dependencies ### Cargo dependencies
All Cargo dependencies must be selected in the version available in PureOS, and added to the file `debian/control`. Please check with https://software.pureos.net/search_pkg?term=librust .
Dependencies must be specified in `Cargo.toml` with 2 numbers: "major.minor". Since bugfix version number is meant to not affect the interface, this allows for safe updates. Dependencies must be specified in `Cargo.toml` with 2 numbers: "major.minor". Since bugfix version number is meant to not affect the interface, this allows for safe updates.
`Cargo.lock` is used for remembering the revisions of all Rust dependencies. It should be updated often, preferably with each bugfix revision, and in a commit on its own: `Cargo.lock` is used for remembering the revisions of all Rust dependencies. It should be updated often, preferably with each bugfix revision, and in a commit on its own:

View File

@ -1,18 +1,11 @@
# Maintained by: Mark Müller <markmueller86@gmail.com> # Maintained by: Mark Müller <markmueller86@gmail.com>
--- ---
bounds: { x: 0, y: 1, width: 360, height: 208 }
outlines: outlines:
default: default: { width: 35.33, height: 52 }
bounds: { x: 0, y: 0, width: 35.33, height: 52 } altline: { width: 52.67, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 52.67, height: 52 } spaceline: { width: 99.67, height: 52 }
wide: special: { width: 35.33, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 99.67, height: 52 }
special:
bounds: { x: 0, y: 0, width: 35.33, height: 52 }
views: views:
base: base:

View File

@ -1,18 +1,11 @@
# Maintained by: Mark Müller <markmueller86@gmail.com> # Maintained by: Mark Müller <markmueller86@gmail.com>
--- ---
bounds: { x: 0, y: 1, width: 540, height: 168 }
outlines: outlines:
default: default: { width: 48, height: 42 }
bounds: { x: 0, y: 0, width: 48, height: 42 } altline: { width: 81, height: 42 }
altline: wide: { width: 108, height: 42 }
bounds: { x: 0, y: 0, width: 81, height: 42 } spaceline: { width: 216, height: 42 }
wide: special: { width: 48, height: 42 }
bounds: { x: 0, y: 0, width: 108, height: 42 }
spaceline:
bounds: { x: 0, y: 0, width: 216, height: 42 }
special:
bounds: { x: 0, y: 0, width: 48, height: 42 }
views: views:
base: base:

View File

@ -2,19 +2,12 @@
# University of the Aegean, Department of Mathematics, atsol@aegean.gr # University of the Aegean, Department of Mathematics, atsol@aegean.gr
# Sep 2019 # Sep 2019
--- ---
bounds: { x: 0, y: 0.33, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 32, height: 52 }
bounds: { x: 0, y: 0, width: 32, height: 52 } altline: { width: 48.39024, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 48.39024, height: 52 } outline7: { width: 88.97561, height: 52 }
wide: spaceline: { width: 150.5853, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
outline7:
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 150.5853, height: 52 }
views: views:
base: base:

16
data/keyboards/emoji.yaml Normal file
View File

@ -0,0 +1,16 @@
---
outlines:
default: { width: 52, height: 52 }
altline: { width: 52, height: 52 }
views:
base:
- "😀 😁 😅 😂 😊 😇 🙃"
- "😍 😘 😋 😜 😎 🥳 😔"
- "😢 😭 😡 😱 🤔 😬 🙄"
- "preferences 🤨 🤓 😴 🤢 🤮 😈"
buttons:
preferences:
action: "show_prefs"
outline: "altline"
icon: "keyboard-mode-symbolic"

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 1, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 35.33, height: 52 }
bounds: { x: 0, y: 0, width: 35.33, height: 52 } altline: { width: 52.67, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 52.67, height: 52 } spaceline: { width: 99.67, height: 52 }
wide: special: { width: 44, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 99.67, height: 52 }
special:
bounds: { x: 0, y: 0, width: 44, height: 52 }
views: views:
base: base:

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 0.33, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 32, height: 52 }
bounds: { x: 0, y: 0, width: 32, height: 52 } altline: { width: 48.39024, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 48.39024, height: 52 } outline7: { width: 88.97561, height: 52 }
wide: spaceline: { width: 150.5853, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
outline7:
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 150.5853, height: 52 }
views: views:
base: base:

View File

@ -1,19 +1,12 @@
# Italian layout created by Antonio Pandolfo # Italian layout created by Antonio Pandolfo
# 03 october 2019 # 03 october 2019
--- ---
bounds: { x: 0, y: 1, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 35.33, height: 52 }
bounds: { x: 0, y: 0, width: 35.33, height: 52 } altline: { width: 52.67, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 52.67, height: 52 } spaceline: { width: 99.67, height: 52 }
wide: special: { width: 44, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 99.67, height: 52 }
special:
bounds: { x: 0, y: 0, width: 44, height: 52 }
views: views:
base: base:

View File

@ -1,18 +1,11 @@
# Maintained by: Mark Müller <markmueller86@gmail.com> # Maintained by: Mark Müller <markmueller86@gmail.com>
--- ---
bounds: { x: 0, y: 1, width: 360, height: 208 }
outlines: outlines:
default: default: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 } default-wide: { width: 62, height: 52 }
default-wide: altline: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 } wide: { width: 62, height: 52 }
altline: special: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
wide:
bounds: { x: 0, y: 0, width: 62, height: 52 }
special:
bounds: { x: 0, y: 0, width: 62, height: 52 }
views: views:
base: # hiragana base: # hiragana

View File

@ -1,18 +1,11 @@
# Maintained by: Mark Müller <markmueller86@gmail.com> # Maintained by: Mark Müller <markmueller86@gmail.com>
--- ---
bounds: { x: 0, y: 1, width: 540, height: 168 }
outlines: outlines:
default: default: { width: 62, height: 42 }
bounds: { x: 0, y: 0, width: 62, height: 42 } default-wide: { width: 62, height: 42 }
default-wide: altline: { width: 62, height: 42 }
bounds: { x: 0, y: 0, width: 62, height: 42 } wide: { width: 62, height: 42 }
altline: special: { width: 62, height: 42 }
bounds: { x: 0, y: 0, width: 62, height: 42 }
wide:
bounds: { x: 0, y: 0, width: 62, height: 42 }
special:
bounds: { x: 0, y: 0, width: 62, height: 42 }
views: views:
base: # hiragana base: # hiragana

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 0.33, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 32, height: 52 }
bounds: { x: 0, y: 0, width: 32, height: 52 } altline: { width: 48.39024, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 48.39024, height: 52 } outline7: { width: 88.97561, height: 52 }
wide: spaceline: { width: 150.5853, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
outline7:
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 150.5853, height: 52 }
views: views:
base: base:

View File

@ -1,15 +1,9 @@
--- ---
bounds: { x: 0, y: 0.33, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 37.46341, height: 52 }
bounds: { x: 0, y: 0, width: 37.46341, height: 52 } altline: { width: 48.39024, height: 52 }
altline: outline7: { width: 88.97561, height: 52 }
bounds: { x: 0, y: 0, width: 48.39024, height: 52 } spaceline: { width: 120.5853, height: 52 }
outline7:
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 120.5853, height: 52 }
views: views:
base: base:

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 0.33, width: 360, height: 210 }
outlines: outlines:
default: default: { width: 32, height: 52 }
bounds: { x: 0, y: 0, width: 32, height: 52 } altline: { width: 48.39024, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 48.39024, height: 52 } outline7: { width: 88.97561, height: 52 }
wide: spaceline: { width: 150.5853, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
outline7:
bounds: { x: 0, y: 0, width: 88.97561, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 150.5853, height: 52 }
views: views:
base: base:

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 1, width: 360, height: 208 }
outlines: outlines:
default: default: { width: 35.33, height: 52 }
bounds: { x: 0, y: 0, width: 35.33, height: 52 } altline: { width: 52.67, height: 52 }
altline: wide: { width: 62, height: 52 }
bounds: { x: 0, y: 0, width: 52.67, height: 52 } spaceline: { width: 142, height: 52 }
wide: special: { width: 44, height: 52 }
bounds: { x: 0, y: 0, width: 62, height: 52 }
spaceline:
bounds: { x: 0, y: 0, width: 142, height: 52 }
special:
bounds: { x: 0, y: 0, width: 44, height: 52 }
views: views:
base: base:

View File

@ -1,17 +1,10 @@
--- ---
bounds: { x: 0, y: 1, width: 540, height: 168 }
outlines: outlines:
default: default: { width: 54, height: 42 }
bounds: { x: 0, y: 0, width: 54, height: 42 } altline: { width: 81, height: 42 }
altline: wide: { width: 108, height: 42 }
bounds: { x: 0, y: 0, width: 81, height: 42 } spaceline: { width: 216, height: 42 }
wide: special: { width: 54, height: 42 }
bounds: { x: 0, y: 0, width: 108, height: 42 }
spaceline:
bounds: { x: 0, y: 0, width: 216, height: 42 }
special:
bounds: { x: 0, y: 0, width: 54, height: 42 }
views: views:
base: base:

69
debian/changelog vendored
View File

@ -1,3 +1,72 @@
squeekboard (1.6.0) UNRELEASED; urgency=medium
[ Dorota Czaplejewicz ]
* tools: Move entry.py
* build: Move building of squeekboard-test-layout to tools
* packaging: Install entty.py as squeekboard-entry
* Remove unused build dependencies
* Remove unused header generator
* logging: Move all facilities to one file
* logging: Described the design
* logging: Add described log levels
* popover: Install emoji layout
* popover: Show overlays as selected
* Fix old Rust woes
* emoji: Add a passable layout
* Fix g_ and stdlib allocation/free mismatches
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Thu, 02 Jan 2020 12:02:50 +0000
squeekboard (1.5.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* keycodes: Sort to eliminate runtime indeterminism
* switcher: Switch layout on menu item click
* Drop squeek_key
* renderer: Remove some unneeded vars
* renderer: Simplified outline rendering
* renderer: Drop row from button rendering
* renderer: Drop unused params
* renderer: Simplify surface rendering
* rendering: Simplify Cairo context usage, remove unneeded calls.
* rendering: Remove unneeded redraw after button release
* renderer: Remove unused locked key render function
* renderer: Simply cut off when painting outside bounds
* renderer: Render whole keyboard the same way as pressed buttons
[ Mark Müller ]
* layout: add German wide layout
[ Dorota Czaplejewicz ]
* renderer: Remove unused functions
* cleanup: Remove references to squeek_view
* cleanup: Unbox View and Row
* cleanup: Remove unused single frame draw
* positioning: Calculate sizes instead of storing, move position out of widgets
* positioning: Clean up unused code
* Fix old Rust woes
[ Mark Müller ]
* layout: add Japanese Kana wide layout
[ Dorota Czaplejewicz ]
* Entry test: Add Terminal input purpose
* readme: Add note about Cargo dependencies
* Create a library/UI module separation
* hacking: Add DCO and licensing requirement
* Fix internal .md link
[ Mark Müller ]
* squeekboard-test-layout: add argument parsing and some more output
[ Dorota Czaplejewicz ]
* Use clap in the lockfile
* parsing: Remove bounds which weren't used anyway
* layout: Respect margins
* CI: Build arm64 .deb
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 23 Dec 2019 11:58:57 +0000
squeekboard (1.4.0) amber-phone; urgency=medium squeekboard (1.4.0) amber-phone; urgency=medium
* "text" property in layouts * "text" property in layouts

8
debian/control vendored
View File

@ -26,9 +26,6 @@ Build-Depends:
libwayland-dev (>= 1.16), libwayland-dev (>= 1.16),
rustc, rustc,
wayland-protocols (>= 1.14), wayland-protocols (>= 1.14),
# for running the tests
xvfb,
xauth,
Standards-Version: 4.1.3 Standards-Version: 4.1.3
Homepage: https://source.puri.sm/Librem5/squeekboard Homepage: https://source.puri.sm/Librem5/squeekboard
@ -45,9 +42,12 @@ Description: On-screen keyboard for Wayland
Package: squeekboard-devel Package: squeekboard-devel
Architecture: linux-any Architecture: linux-any
Depends: Depends:
python3,
python3-gi,
${shlibs:Depends} ${shlibs:Depends}
${misc:Depends} ${misc:Depends}
Description: Resources for making Squeekboard layouts Description: Resources for making Squeekboard layouts
Tools for creating Squeekboard layouts: Tools for creating and testing Squeekboard layouts:
. .
* squeekboard-entry
* squeekboard-test-layout * squeekboard-test-layout

View File

@ -1 +1,2 @@
usr/bin/squeekboard-test-layout /usr/bin usr/bin/squeekboard-test-layout /usr/bin
usr/bin/squeekboard-entry /usr/bin

View File

@ -131,14 +131,17 @@ static void drag(EekGtkKeyboard *self,
{ {
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_drag(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, squeek_layout_drag(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
x, y, eek_renderer_get_transformation(priv->renderer), time, self); x, y, eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
} }
static void release(EekGtkKeyboard *self, guint32 time) static void release(EekGtkKeyboard *self, guint32 time)
{ {
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, eek_renderer_get_transformation(priv->renderer), time, self); squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
} }
static gboolean static gboolean

View File

@ -30,7 +30,6 @@
#include "config.h" #include "config.h"
#include <glib/gprintf.h> #include <glib/gprintf.h>
#include "eek-enumtypes.h"
#include "eekboard/eekboard-context-service.h" #include "eekboard/eekboard-context-service.h"
#include "eekboard/key-emitter.h" #include "eekboard/key-emitter.h"
#include "keymap.h" #include "keymap.h"

View File

@ -1,8 +0,0 @@
gnome = import('gnome')
enum_headers = [
'eek-types.h',
]
enums = gnome.mkenums_simple('eek-enumtypes', sources: enum_headers)

View File

@ -71,6 +71,8 @@ struct _EekboardContextServicePrivate {
LevelKeyboard *keyboard; // currently used keyboard LevelKeyboard *keyboard; // currently used keyboard
GHashTable *keyboard_hash; // a table of available keyboards, per layout GHashTable *keyboard_hash; // a table of available keyboards, per layout
char *overlay;
GSettings *settings; GSettings *settings;
uint32_t hint; uint32_t hint;
uint32_t purpose; uint32_t purpose;
@ -214,15 +216,17 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
void void
eekboard_context_service_update_layout(EekboardContextService *context, enum squeek_arrangement_kind t) eekboard_context_service_update_layout(EekboardContextService *context, enum squeek_arrangement_kind t)
{ {
g_autofree gchar *keyboard_type = NULL;
g_autofree gchar *keyboard_layout = NULL; g_autofree gchar *keyboard_layout = NULL;
settings_get_layout(context->priv->settings, &keyboard_type, &keyboard_layout); if (context->priv->overlay) {
keyboard_layout = g_strdup(context->priv->overlay);
if (!keyboard_type) { } else {
keyboard_type = g_strdup("us"); g_autofree gchar *keyboard_type = NULL;
settings_get_layout(context->priv->settings,
&keyboard_type, &keyboard_layout);
} }
if (!keyboard_layout) { if (!keyboard_layout) {
keyboard_layout = g_strdup("undefined"); keyboard_layout = g_strdup("us");
} }
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context); EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
@ -262,6 +266,8 @@ settings_handle_layout_changed(GSettings *s,
(void)keys; (void)keys;
(void)n_keys; (void)n_keys;
EekboardContextService *context = user_data; EekboardContextService *context = user_data;
g_free(context->priv->overlay);
context->priv->overlay = NULL;
update_layout_and_type(context); update_layout_and_type(context);
return TRUE; return TRUE;
} }
@ -391,6 +397,8 @@ eekboard_context_service_init (EekboardContextService *self)
g_warning ("Could not connect to gsettings updates, layout" g_warning ("Could not connect to gsettings updates, layout"
" changing unavailable"); " changing unavailable");
} }
self->priv->overlay = NULL;
} }
/** /**
@ -463,6 +471,7 @@ eekboard_context_service_destroy (EekboardContextService *context)
if (context->priv->enabled) { if (context->priv->enabled) {
eekboard_context_service_disable (context); eekboard_context_service_disable (context);
} }
g_free(context->priv->overlay);
g_signal_emit (context, signals[DESTROYED], 0); g_signal_emit (context, signals[DESTROYED], 0);
} }
@ -498,3 +507,14 @@ void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
update_layout_and_type(context); update_layout_and_type(context);
} }
} }
void
eekboard_context_service_set_overlay(EekboardContextService *context, const char* name) {
context->priv->overlay = g_strdup(name);
update_layout_and_type(context);
}
const char*
eekboard_context_service_get_overlay(EekboardContextService *context) {
return context->priv->overlay;
}

View File

@ -1,7 +1,7 @@
project( project(
'squeekboard', 'squeekboard',
'c', 'rust', 'c', 'rust',
version: '1.4.0', version: '1.6.0',
license: 'GPLv3', license: 'GPLv3',
meson_version: '>=0.51.0', meson_version: '>=0.51.0',
default_options: [ default_options: [
@ -40,6 +40,7 @@ else
endif endif
prefix = get_option('prefix') prefix = get_option('prefix')
bindir = join_paths(prefix, get_option('bindir'))
datadir = join_paths(prefix, get_option('datadir')) datadir = join_paths(prefix, get_option('datadir'))
pkgdatadir = join_paths(datadir, meson.project_name()) pkgdatadir = join_paths(datadir, meson.project_name())
if get_option('depdatadir') == '' if get_option('depdatadir') == ''
@ -65,6 +66,6 @@ cargo_build = find_program('cargo_build.sh')
subdir('data') subdir('data')
subdir('protocols') subdir('protocols')
subdir('eek')
subdir('src') subdir('src')
subdir('tools')
subdir('tests') subdir('tests')

View File

@ -1,5 +1,7 @@
/**! The parsing of the data files for layouts */ /**! The parsing of the data files for layouts */
// TODO: find a nice way to make sure non-positive sizes don't break layouts
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{ HashMap, HashSet }; use std::collections::{ HashMap, HashSet };
use std::env; use std::env;
@ -19,17 +21,17 @@ use ::keyboard::{
}; };
use ::layout; use ::layout;
use ::layout::ArrangementKind; use ::layout::ArrangementKind;
use ::logging::PrintWarnings;
use ::resources; use ::resources;
use ::util::c::as_str; use ::util::c::as_str;
use ::util::hash_map_map; use ::util::hash_map_map;
use ::xdg; use ::xdg;
// traits, derives // traits, derives
use serde::Deserialize;
use std::io::BufReader; use std::io::BufReader;
use std::iter::FromIterator; use std::iter::FromIterator;
use serde::Deserialize; use ::logging::WarningHandler;
use util::WarningHandler;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
pub mod c { pub mod c {
@ -152,14 +154,6 @@ fn list_layout_sources(
ret ret
} }
struct PrintWarnings;
impl WarningHandler for PrintWarnings {
fn handle(&mut self, warning: &str) {
println!("{}", warning);
}
}
fn load_layout_data(source: DataSource) fn load_layout_data(source: DataSource)
-> Result<::layout::LayoutData, LoadError> -> Result<::layout::LayoutData, LoadError>
{ {
@ -216,21 +210,20 @@ fn load_layout_data_with_fallback(
#[derive(Debug, Deserialize, PartialEq)] #[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Layout { pub struct Layout {
/// FIXME: deprecate in favor of margins #[serde(default)]
bounds: Bounds, margins: Margins,
views: HashMap<String, Vec<ButtonIds>>, views: HashMap<String, Vec<ButtonIds>>,
#[serde(default)] #[serde(default)]
buttons: HashMap<String, ButtonMeta>, buttons: HashMap<String, ButtonMeta>,
outlines: HashMap<String, Outline> outlines: HashMap<String, Outline>
} }
#[derive(Debug, Clone, Deserialize, PartialEq)] #[derive(Debug, Clone, Deserialize, PartialEq, Default)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Bounds { struct Margins {
x: f64, top: f64,
y: f64, bottom: f64,
width: f64, side: f64,
height: f64,
} }
/// Buttons are embedded in a single string /// Buttons are embedded in a single string
@ -271,8 +264,8 @@ enum Action {
#[derive(Debug, Clone, Deserialize, PartialEq)] #[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Outline { struct Outline {
/// FIXME: replace with Size width: f64,
bounds: Bounds, height: f64,
} }
/// Errors encountered loading the layout into yaml /// Errors encountered loading the layout into yaml
@ -460,10 +453,10 @@ impl Layout {
}, },
// FIXME: use a dedicated field // FIXME: use a dedicated field
margins: layout::Margins { margins: layout::Margins {
top: self.bounds.x, top: self.margins.top,
left: self.bounds.y, left: self.margins.side,
bottom: 0.0, bottom: self.margins.bottom,
right: self.bounds.y, right: self.margins.side,
}, },
}), }),
warning_handler, warning_handler,
@ -649,9 +642,7 @@ fn create_button<H: WarningHandler>(
warning_handler.handle( warning_handler.handle(
&format!("No default outline defined! Using 1x1!") &format!("No default outline defined! Using 1x1!")
); );
Outline { Outline { width: 1f64, height: 1f64 }
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
}
}); });
layout::Button { layout::Button {
@ -659,8 +650,8 @@ fn create_button<H: WarningHandler>(
outline_name: CString::new(outline_name).expect("Bad outline"), outline_name: CString::new(outline_name).expect("Bad outline"),
// TODO: do layout before creating buttons // TODO: do layout before creating buttons
size: layout::Size { size: layout::Size {
width: outline.bounds.width, width: outline.width,
height: outline.bounds.height, height: outline.height,
}, },
label: label, label: label,
state: state, state: state,
@ -672,21 +663,14 @@ mod tests {
use super::*; use super::*;
use std::error::Error as ErrorTrait; use std::error::Error as ErrorTrait;
use ::logging::PanicWarn;
struct PanicWarn;
impl WarningHandler for PanicWarn {
fn handle(&mut self, warning: &str) {
panic!("{}", warning);
}
}
#[test] #[test]
fn test_parse_path() { fn test_parse_path() {
assert_eq!( assert_eq!(
Layout::from_file(PathBuf::from("tests/layout.yaml")).unwrap(), Layout::from_file(PathBuf::from("tests/layout.yaml")).unwrap(),
Layout { Layout {
bounds: Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }, margins: Margins { top: 0f64, bottom: 0f64, side: 0f64 },
views: hashmap!( views: hashmap!(
"base".into() => vec!("test".into()), "base".into() => vec!("test".into()),
), ),
@ -701,11 +685,7 @@ mod tests {
} }
}, },
outlines: hashmap!{ outlines: hashmap!{
"default".into() => Outline { "default".into() => Outline { width: 0f64, height: 0f64 },
bounds: Bounds {
x: 0f64, y: 0f64, width: 0f64, height: 0f64
},
}
}, },
} }
); );
@ -855,4 +835,21 @@ mod tests {
}, },
); );
} }
#[test]
fn test_layout_margins() {
let out = Layout::from_file(PathBuf::from("tests/layout_margins.yaml"))
.unwrap()
.build(PanicWarn).0
.unwrap();
assert_eq!(
out.margins,
layout::Margins {
top: 1.0,
bottom: 3.0,
left: 2.0,
right: 2.0,
}
);
}
} }

View File

@ -102,12 +102,8 @@ pub mod c {
imservice.pending = IMProtocolState { imservice.pending = IMProtocolState {
content_hint: { content_hint: {
ContentHint::from_bits(hint).unwrap_or_else(|| { ContentHint::from_bits(hint).unwrap_or_else(|| {
let out = ContentHint::from_bits_truncate(hint); eprintln!("Warning: received invalid hint flags");
eprintln!( ContentHint::NONE
"Warning: received hint flags with unknown bits set: 0x{:x}",
hint ^ out.bits(),
);
out
}) })
}, },
content_purpose: { content_purpose: {
@ -145,16 +141,6 @@ pub mod c {
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
let active_changed = imservice.current.active ^ imservice.pending.active; let active_changed = imservice.current.active ^ imservice.pending.active;
fn is_visible(state: &IMProtocolState) -> bool {
state.active
&& !state.content_hint.contains(
ContentHint::ON_SCREEN_INPUT_PROVIDED
)
}
let visible_changed = is_visible(&imservice.current)
^ is_visible(&imservice.pending);
imservice.serial += Wrapping(1u32); imservice.serial += Wrapping(1u32);
imservice.current = imservice.pending.clone(); imservice.current = imservice.pending.clone();
@ -162,18 +148,13 @@ pub mod c {
active: imservice.current.active, active: imservice.current.active,
..IMProtocolState::default() ..IMProtocolState::default()
}; };
if active_changed {
if active_changed && imservice.current.active { if imservice.current.active {
eekboard_context_service_set_hint_purpose(
imservice.ui_manager,
imservice.current.content_hint.bits(),
imservice.current.content_purpose.clone() as u32,
);
}
if visible_changed {
if is_visible(&imservice.current) {
eekboard_context_service_show_keyboard(imservice.ui_manager); eekboard_context_service_show_keyboard(imservice.ui_manager);
eekboard_context_service_set_hint_purpose(
imservice.ui_manager,
imservice.current.content_hint.bits(),
imservice.current.content_purpose.clone() as u32);
} else { } else {
eekboard_context_service_hide_keyboard(imservice.ui_manager); eekboard_context_service_hide_keyboard(imservice.ui_manager);
} }
@ -238,7 +219,6 @@ bitflags!{
const SENSITIVE_DATA = 0x80; const SENSITIVE_DATA = 0x80;
const LATIN = 0x100; const LATIN = 0x100;
const MULTILINE = 0x200; const MULTILINE = 0x200;
const ON_SCREEN_INPUT_PROVIDED = 0x400;
} }
} }

View File

@ -36,6 +36,7 @@ void squeek_layout_free(struct squeek_layout*);
void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, uint32_t timestamp,
EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard); EekGtkKeyboard *ui_keyboard);
void squeek_layout_release_all_only(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, uint32_t timestamp); void squeek_layout_release_all_only(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, uint32_t timestamp);
void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
@ -45,7 +46,8 @@ void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyb
void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
double x_widget, double y_widget, double x_widget, double y_widget,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, EekGtkKeyboard *ui_keyboard); uint32_t timestamp, EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr); void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr); void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
#endif #endif

View File

@ -26,6 +26,7 @@ use std::vec::Vec;
use ::action::Action; use ::action::Action;
use ::drawing; use ::drawing;
use ::keyboard::{ KeyState, PressType }; use ::keyboard::{ KeyState, PressType };
use ::manager;
use ::submission::{ Timestamp, VirtualKeyboard }; use ::submission::{ Timestamp, VirtualKeyboard };
use ::util::find_max_double; use ::util::find_max_double;
@ -99,7 +100,7 @@ pub mod c {
} }
} }
/// Scale + translate /// Translate and then scale
#[repr(C)] #[repr(C)]
pub struct Transformation { pub struct Transformation {
pub origin_x: f64, pub origin_x: f64,
@ -108,6 +109,14 @@ pub mod c {
} }
impl Transformation { impl Transformation {
/// Applies the new transformation after this one
pub fn chain(self, next: Transformation) -> Transformation {
Transformation {
origin_x: self.origin_x + self.scale * next.origin_x,
origin_y: self.origin_y + self.scale * next.origin_y,
scale: self.scale * next.scale,
}
}
fn forward(&self, p: Point) -> Point { fn forward(&self, p: Point) -> Point {
Point { Point {
x: (p.x - self.origin_x) / self.scale, x: (p.x - self.origin_x) / self.scale,
@ -195,7 +204,8 @@ pub mod c {
println!("{:?}", button); println!("{:?}", button);
} }
/// Positions the layout within the available space /// Positions the layout contents within the available space.
/// The origin of the transformation is the point inside the margins.
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn squeek_layout_calculate_transformation( fn squeek_layout_calculate_transformation(
@ -204,15 +214,10 @@ pub mod c {
allocation_height: f64, allocation_height: f64,
) -> Transformation { ) -> Transformation {
let layout = unsafe { &*layout }; let layout = unsafe { &*layout };
let size = layout.calculate_size(); layout.calculate_transformation(Size {
let h_scale = allocation_width / size.width; width: allocation_width,
let v_scale = allocation_height / size.height; height: allocation_height,
let scale = if h_scale < v_scale { h_scale } else { v_scale }; })
Transformation {
origin_x: (allocation_width - (scale * size.width)) / 2.0,
origin_y: (allocation_height - (scale * size.height)) / 2.0,
scale: scale,
}
} }
#[no_mangle] #[no_mangle]
@ -254,6 +259,7 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
widget_to_layout: Transformation, widget_to_layout: Transformation,
time: u32, time: u32,
manager: manager::c::Manager,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
@ -269,6 +275,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -340,6 +347,7 @@ pub mod c {
x_widget: f64, y_widget: f64, x_widget: f64, y_widget: f64,
widget_to_layout: Transformation, widget_to_layout: Transformation,
time: u32, time: u32,
manager: manager::c::Manager,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
@ -374,6 +382,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -391,6 +400,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -427,7 +437,7 @@ pub struct ButtonPlace<'a> {
offset: c::Point, offset: c::Point,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Size { pub struct Size {
pub width: f64, pub width: f64,
pub height: f64, pub height: f64,
@ -560,6 +570,7 @@ pub enum ArrangementKind {
Wide = 1, Wide = 1,
} }
#[derive(Debug, PartialEq)]
pub struct Margins { pub struct Margins {
pub top: f64, pub top: f64,
pub bottom: f64, pub bottom: f64,
@ -713,7 +724,8 @@ impl Layout {
}; };
} }
fn calculate_size(&self) -> Size { /// Calculates size without margins
fn calculate_inner_size(&self) -> Size {
Size { Size {
height: find_max_double( height: find_max_double(
self.views.iter(), self.views.iter(),
@ -725,6 +737,39 @@ impl Layout {
), ),
} }
} }
/// Size including margins
fn calculate_size(&self) -> Size {
let inner_size = self.calculate_inner_size();
Size {
width: self.margins.left + inner_size.width + self.margins.right,
height: (
self.margins.top
+ inner_size.height
+ self.margins.bottom
),
}
}
pub fn calculate_transformation(
&self,
available: Size,
) -> c::Transformation {
let size = self.calculate_size();
let h_scale = available.width / size.width;
let v_scale = available.height / size.height;
let scale = if h_scale < v_scale { h_scale } else { v_scale };
let outside_margins = c::Transformation {
origin_x: (available.width - (scale * size.width)) / 2.0,
origin_y: (available.height - (scale * size.height)) / 2.0,
scale: scale,
};
outside_margins.chain(c::Transformation {
origin_x: self.margins.left,
origin_y: self.margins.top,
scale: 1.0,
})
}
} }
mod procedures { mod procedures {
@ -814,6 +859,7 @@ mod seat {
widget_to_layout: &c::Transformation, widget_to_layout: &c::Transformation,
time: Timestamp, time: Timestamp,
ui_keyboard: c::EekGtkKeyboard, ui_keyboard: c::EekGtkKeyboard,
manager: manager::c::Manager,
key: &Rc<RefCell<KeyState>>, key: &Rc<RefCell<KeyState>>,
) { ) {
layout.release_key(virtual_keyboard, &mut key.clone(), time); layout.release_key(virtual_keyboard, &mut key.clone(), time);
@ -835,7 +881,8 @@ mod seat {
}; };
::popover::show( ::popover::show(
ui_keyboard, ui_keyboard,
widget_to_layout.reverse_bounds(bounds) widget_to_layout.reverse_bounds(bounds),
manager,
); );
} }
} }
@ -907,4 +954,58 @@ mod test {
.is_none() .is_none()
); );
} }
#[test]
fn check_bottom_margin() {
// just one button
let view = View::new(vec![
(
0.0,
Row {
angle: 0,
buttons: vec![(
0.0,
Box::new(Button {
size: Size { width: 1.0, height: 1.0 },
..*make_button_with_state("foo".into(), make_state())
}),
)]
},
),
]);
let layout = Layout {
current_view: String::new(),
keymap_str: CString::new("").unwrap(),
kind: ArrangementKind::Base,
locked_keys: HashSet::new(),
pressed_keys: HashSet::new(),
// Lots of bottom margin
margins: Margins {
top: 0.0,
left: 0.0,
right: 0.0,
bottom: 1.0,
},
views: hashmap! {
String::new() => view,
},
};
assert_eq!(
layout.calculate_inner_size(),
Size { width: 1.0, height: 1.0 }
);
assert_eq!(
layout.calculate_size(),
Size { width: 1.0, height: 2.0 }
);
// Don't change those values randomly!
// They take advantage of incidental precise float representation
// to even be comparable.
let transformation = layout.calculate_transformation(
Size { width: 2.0, height: 2.0 }
);
assert_eq!(transformation.scale, 1.0);
assert_eq!(transformation.origin_x, 0.5);
assert_eq!(transformation.origin_y, 0.0);
}
} }

View File

@ -24,6 +24,8 @@ mod keyboard;
mod layout; mod layout;
mod locale; mod locale;
mod locale_config; mod locale_config;
mod logging;
mod manager;
mod outputs; mod outputs;
mod popover; mod popover;
mod resources; mod resources;

View File

@ -16,6 +16,9 @@ mod c {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub struct Translation<'a>(pub &'a str);
fn cstring_safe(s: &str) -> CString { fn cstring_safe(s: &str) -> CString {
CString::new(s) CString::new(s)
.unwrap_or(CString::new("").unwrap()) .unwrap_or(CString::new("").unwrap())

110
src/logging.rs Normal file
View File

@ -0,0 +1,110 @@
/*! Logging library.
*
* This is probably the only part of squeekboard
* that should be doing any direct printing.
*
* There are several approaches to logging,
* in the order of increasing flexibility and/or purity:
*
* 1. `println!` directly
*
* It can't be easily replaced by a different solution
*
* 2. simple `log!` macro
*
* Replacing the destination at runtime other than globally would be awkward,
* so no easy way to suppress errors for things that don't matter,
* but formatting is still easy.
*
* 3. logging to a mutable destination type
*
* Can be easily replaced, but logging `Result` types,
* which should be done by calling a method on the result,
* can't be formatted directly.
* Cannot be parallelized.
*
* 4. logging to an immutable destination type
*
* Same as above, except it can be parallelized.
* It seems more difficult to pass the logger around,
* but this may be a solved problem from the area of functional programming.
*
* This library generally aims at the approach in 3.
* */
use std::error::Error;
/// Levels are not in order.
pub enum Level {
// Levels for reporting violated constraints
/// The program violated a self-imposed constraint,
/// ended up in an inconsistent state, and cannot recover.
/// Handlers must not actually panic. (should they?)
Panic,
/// The program violated a self-imposed constraint,
/// ended up in an inconsistent state, but some state can be recovered.
Bug,
/// Invalid data given by an external source,
/// some state of the program must be dropped.
Error,
// Still violated constraints, but harmless
/// Invalid data given by an external source, parts of data are ignored.
/// No previous program state needs to be dropped.
Warning,
/// External source not in an expected state,
/// but not violating any protocols (including no relevant protocol).
Surprise,
// Informational
/// A change in internal state that results in a change of behaviour
/// that a user can observe, and a tinkerer might find useful.
/// E.g. selection of external sources, like loading user's UI files,
/// language switch, overrides.
Info,
/// Information useful for application developer only.
/// Should be limited to information gotten from external sources,
/// and more tricky parts of internal state.
Debug,
}
/// Sugar for logging errors in results.
/// Approach 2.
pub trait Warn {
type Value;
fn ok_warn(self, msg: &str) -> Option<Self::Value>;
}
impl<T, E: Error> Warn for Result<T, E> {
type Value = T;
fn ok_warn(self, msg: &str) -> Option<T> {
self.map_err(|e| {
eprintln!("{}: {}", msg, e);
e
}).ok()
}
}
/// A mutable handler for text warnings.
/// Approach 3.
pub trait WarningHandler {
/// Handle a warning
fn handle(&mut self, warning: &str);
}
/// Prints warnings to stderr
pub struct PrintWarnings;
impl WarningHandler for PrintWarnings {
fn handle(&mut self, warning: &str) {
eprintln!("{}", warning);
}
}
/// Warning handler that will panic at any warning.
/// Don't use except in tests
pub struct PanicWarn;
impl WarningHandler for PanicWarn {
fn handle(&mut self, warning: &str) {
panic!("{}", warning);
}
}

34
src/manager.rs Normal file
View File

@ -0,0 +1,34 @@
/*! Procedures relating to the management of the switching of layouts */
use ::util;
pub mod c {
use std::os::raw::{c_char, c_void};
/// EekboardContextService*
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Manager(*const c_void);
#[no_mangle]
extern "C" {
pub fn eekboard_context_service_set_overlay(
manager: Manager,
name: *const c_char,
);
pub fn eekboard_context_service_get_overlay(
manager: Manager,
) -> *const c_char;
}
}
/// Returns the overlay name.
/// The result lifetime is "as long as the C copy lives"
pub fn get_overlay(manager: c::Manager) -> Option<String> {
let raw_str = unsafe {
c::eekboard_context_service_get_overlay(manager)
};
// this string is generated from Rust, should never be invalid
util::c::as_str(&raw_str).unwrap()
.map(String::from)
}

View File

@ -25,7 +25,6 @@ sources = [
'../eek/eek-xml-layout.c', '../eek/eek-xml-layout.c',
'../eek/layersurface.c', '../eek/layersurface.c',
dbus_src, dbus_src,
enums,
'../eekboard/key-emitter.c', '../eekboard/key-emitter.c',
'../eekboard/eekboard-context-service.c', '../eekboard/eekboard-context-service.c',
'../eekboard/eekboard-service.c', '../eekboard/eekboard-service.c',
@ -112,16 +111,3 @@ squeekboard = executable('squeekboard',
'-DEEK_COMPILATION=1'], '-DEEK_COMPILATION=1'],
) )
bindir = join_paths(prefix, get_option('bindir'))
test_layout = custom_target('squeekboard-test-layout',
build_by_default: true,
# meson doesn't track all inputs, cargo does
build_always_stale: true,
output: ['squeekboard-test-layout'],
console: true,
command: [cargo_build] + cargo_build_flags
+ ['--rename', 'test_layout', '@OUTPUT@', '--bin', 'test_layout'],
install: true,
install_dir: bindir,
)

View File

@ -2,9 +2,11 @@
use gio; use gio;
use gtk; use gtk;
use ::layout::c::EekGtkKeyboard; use std::ffi::CString;
use ::locale::compare_current_locale; use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale::{ Translation, compare_current_locale };
use ::locale_config::system_locale; use ::locale_config::system_locale;
use ::manager;
use ::resources; use ::resources;
use gio::ActionMapExt; use gio::ActionMapExt;
@ -92,7 +94,7 @@ mod variants {
} }
} }
fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder { fn make_menu_builder(inputs: Vec<(&str, Translation)>) -> gtk::Builder {
let mut xml: Vec<u8> = Vec::new(); let mut xml: Vec<u8> = Vec::new();
writeln!( writeln!(
xml, xml,
@ -101,7 +103,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<menu id=\"app-menu\"> <menu id=\"app-menu\">
<section>" <section>"
).unwrap(); ).unwrap();
for (input_name, human_name) in inputs { for (input_name, translation) in inputs {
writeln!( writeln!(
xml, xml,
" "
@ -110,7 +112,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<attribute name=\"action\">layout</attribute> <attribute name=\"action\">layout</attribute>
<attribute name=\"target\">{}</attribute> <attribute name=\"target\">{}</attribute>
</item>", </item>",
human_name, translation.0,
input_name, input_name,
).unwrap(); ).unwrap();
} }
@ -141,16 +143,79 @@ fn set_layout(kind: String, name: String) {
settings.apply(); settings.apply();
} }
pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) { /// A reference to what the user wants to see
#[derive(PartialEq, Clone, Debug)]
enum LayoutId {
/// Affects the layout in system settings
System {
kind: String,
name: String,
},
/// Only affects what this input method presents
Local(String),
}
impl LayoutId {
fn get_name(&self) -> &str {
match &self {
LayoutId::System { kind: _, name } => name.as_str(),
LayoutId::Local(name) => name.as_str(),
}
}
}
fn set_visible_layout(
manager: manager::c::Manager,
layout_id: LayoutId,
) {
match layout_id {
LayoutId::System { kind, name } => set_layout(kind, name),
LayoutId::Local(name) => {
let name = CString::new(name.as_str()).unwrap();
let name_ptr = name.as_ptr();
unsafe {
manager::c::eekboard_context_service_set_overlay(
manager,
name_ptr,
)
}
},
}
}
/// Takes into account first any overlays, then system layouts from the list
fn get_current_layout(
manager: manager::c::Manager,
system_layouts: &Vec<LayoutId>,
) -> Option<LayoutId> {
match manager::get_overlay(manager) {
Some(name) => Some(LayoutId::Local(name)),
None => system_layouts.get(0).map(LayoutId::clone),
}
}
pub fn show(
window: EekGtkKeyboard,
position: Bounds,
manager: manager::c::Manager,
) {
unsafe { gtk::set_initialized() }; unsafe { gtk::set_initialized() };
let window = unsafe { gtk::Widget::from_glib_none(window.0) }; let window = unsafe { gtk::Widget::from_glib_none(window.0) };
let overlay_layouts = resources::get_overlays().into_iter()
.map(|name| LayoutId::Local(name.to_string()));
let settings = gio::Settings::new("org.gnome.desktop.input-sources"); let settings = gio::Settings::new("org.gnome.desktop.input-sources");
let inputs = settings.get_value("sources").unwrap(); let inputs = settings.get_value("sources").unwrap();
let inputs = variants::get_tuples(inputs); let inputs = variants::get_tuples(inputs);
let input_names: Vec<&str> = inputs.iter() let system_layouts: Vec<LayoutId> = inputs.into_iter()
.map(|(_kind, name)| name.as_str()) .map(|(kind, name)| LayoutId::System { kind, name })
.collect();
let all_layouts: Vec<LayoutId> = system_layouts.clone()
.into_iter()
.chain(overlay_layouts)
.collect(); .collect();
let translations = system_locale() let translations = system_locale()
@ -162,26 +227,56 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
) )
.and_then(|lang| resources::get_layout_names(lang.as_str())); .and_then(|lang| resources::get_layout_names(lang.as_str()));
// sorted collection of human and machine names let translated_names = all_layouts.iter()
let mut human_names: Vec<(&str, &str)> = match translations { .map(LayoutId::get_name);
let translated_names: Vec<Translation> = match translations {
Some(translations) => { Some(translations) => {
input_names.iter() translated_names
.map(|name| (*name, *translations.get(name).unwrap_or(name))) .map(move |name| {
translations.get(name)
.map(|translation| translation.clone())
.unwrap_or(Translation(name))
})
.collect() .collect()
}, },
// display bare codes
None => { None => {
input_names.iter() translated_names.map(|name| Translation(name))
.map(|n| (*n, *n)) // turns &&str into &str
.collect() .collect()
} },
}; };
human_names.sort_unstable_by(|(_, human_label_a), (_, human_label_b)| { // sorted collection of human and machine names
compare_current_locale(human_label_a, human_label_b) let mut human_names: Vec<(Translation, LayoutId)> = translated_names
.into_iter()
.zip(all_layouts.clone().into_iter())
.collect();
human_names.sort_unstable_by(|(tr_a, _), (tr_b, _)| {
compare_current_locale(tr_a.0, tr_b.0)
}); });
let builder = make_menu_builder(human_names); // GVariant doesn't natively support `enum`s,
// so the `choices` vector will serve as a lookup table.
let choices_with_translations: Vec<(String, (Translation, LayoutId))>
= human_names.into_iter()
.enumerate()
.map(|(i, human_entry)| {(
format!("{}_{}", i, human_entry.1.get_name()),
human_entry,
)}).collect();
let builder = make_menu_builder(
choices_with_translations.iter()
.map(|(id, (translation, _))| (id.as_str(), translation.clone()))
.collect()
);
let choices: Vec<(String, LayoutId)>
= choices_with_translations.into_iter()
.map(|(id, (_tr, layout))| (id, layout))
.collect();
// Much more debuggable to populate the model & menu // Much more debuggable to populate the model & menu
// from a string representation // from a string representation
// than add items imperatively // than add items imperatively
@ -195,16 +290,21 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
height: position.width.floor() as i32, height: position.width.floor() as i32,
}); });
if let Some(current_name) = input_names.get(0) { if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
let current_name = current_name.to_variant(); let current_name_variant = choices.iter()
.find(
|(_id, layout)| layout == &current_layout
).unwrap()
.0.to_variant();
let layout_action = gio::SimpleAction::new_stateful( let layout_action = gio::SimpleAction::new_stateful(
"layout", "layout",
Some(current_name.type_()), Some(current_name_variant.type_()),
&current_name, &current_name_variant,
); );
layout_action.connect_change_state(|_action, state| { let menu_inner = menu.clone();
layout_action.connect_change_state(move |_action, state| {
match state { match state {
Some(v) => { Some(v) => {
v.get::<String>() v.get::<String>()
@ -212,10 +312,20 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
eprintln!("Variant is not string: {:?}", v); eprintln!("Variant is not string: {:?}", v);
None None
}) })
.map(|state| set_layout("xkb".into(), state)); .map(|state| {
let (_id, layout) = choices.iter()
.find(
|choices| state == choices.0
).unwrap();
set_visible_layout(
manager,
layout.clone(),
)
});
}, },
None => eprintln!("No variant selected"), None => eprintln!("No variant selected"),
}; };
menu_inner.popdown();
}); });
let action_group = gio::SimpleActionGroup::new(); let action_group = gio::SimpleActionGroup::new();

View File

@ -3,6 +3,7 @@
*/ */
use std::collections::HashMap; use std::collections::HashMap;
use ::locale::Translation;
use std::iter::FromIterator; use std::iter::FromIterator;
@ -23,6 +24,8 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("no", include_str!("../data/keyboards/no.yaml")), ("no", include_str!("../data/keyboards/no.yaml")),
("number", include_str!("../data/keyboards/number.yaml")), ("number", include_str!("../data/keyboards/number.yaml")),
("se", include_str!("../data/keyboards/se.yaml")), ("se", include_str!("../data/keyboards/se.yaml")),
// Overlays
("emoji", include_str!("../data/keyboards/emoji.yaml")),
]; ];
pub fn get_keyboard(needle: &str) -> Option<&'static str> { pub fn get_keyboard(needle: &str) -> Option<&'static str> {
@ -39,6 +42,18 @@ pub fn get_keyboard(needle: &str) -> Option<&'static str> {
}) })
} }
const OVERLAY_NAMES: &[*const str] = &[
"emoji"
];
pub fn get_overlays() -> Vec<&'static str> {
OVERLAY_NAMES.iter()
.map(|name| {
let name: *const str = *name;
unsafe { &*name }
}).collect()
}
/// Translations of the layout identifier strings /// Translations of the layout identifier strings
const LAYOUT_NAMES: &[(*const str, *const str)] = &[ const LAYOUT_NAMES: &[(*const str, *const str)] = &[
("de-DE", include_str!("../data/langs/de-DE.txt")), ("de-DE", include_str!("../data/langs/de-DE.txt")),
@ -49,7 +64,7 @@ const LAYOUT_NAMES: &[(*const str, *const str)] = &[
]; ];
pub fn get_layout_names(lang: &str) pub fn get_layout_names(lang: &str)
-> Option<HashMap<&'static str, &'static str>> -> Option<HashMap<&'static str, Translation<'static>>>
{ {
let translations = LAYOUT_NAMES.iter() let translations = LAYOUT_NAMES.iter()
.find(|(name, _data)| { .find(|(name, _data)| {
@ -63,7 +78,7 @@ pub fn get_layout_names(lang: &str)
translations.map(make_mapping) translations.map(make_mapping)
} }
fn parse_line(line: &str) -> Option<(&str, &str)> { fn parse_line(line: &str) -> Option<(&str, Translation)> {
let comment = line.trim().starts_with("#"); let comment = line.trim().starts_with("#");
if comment { if comment {
None None
@ -71,11 +86,11 @@ fn parse_line(line: &str) -> Option<(&str, &str)> {
let mut iter = line.splitn(2, " "); let mut iter = line.splitn(2, " ");
let name = iter.next().unwrap(); let name = iter.next().unwrap();
// will skip empty and unfinished lines // will skip empty and unfinished lines
iter.next().map(|tr| (name, tr.trim())) iter.next().map(|tr| (name, Translation(tr.trim())))
} }
} }
fn make_mapping(data: &str) -> HashMap<&str, &str> { fn make_mapping(data: &str) -> HashMap<&str, Translation> {
HashMap::from_iter( HashMap::from_iter(
data.split("\n") data.split("\n")
.filter_map(parse_line) .filter_map(parse_line)
@ -86,10 +101,17 @@ fn make_mapping(data: &str) -> HashMap<&str, &str> {
mod test { mod test {
use super::*; use super::*;
#[test]
fn check_overlays_present() {
for name in get_overlays() {
assert!(get_keyboard(name).is_some());
}
}
#[test] #[test]
fn mapping_line() { fn mapping_line() {
assert_eq!( assert_eq!(
Some(("name", "translation")), Some(("name", Translation("translation"))),
parse_line("name translation") parse_line("name translation")
); );
} }

View File

@ -21,7 +21,7 @@
use std::env; use std::env;
use glib::object::ObjectExt; use glib::object::ObjectExt;
use util::Warn; use logging::Warn;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
pub mod c { pub mod c {

View File

@ -3,7 +3,7 @@
use ::data::Layout; use ::data::Layout;
use xkbcommon::xkb; use xkbcommon::xkb;
use ::util::WarningHandler; use ::logging::WarningHandler;
pub struct CountAndPrint(u32); pub struct CountAndPrint(u32);

View File

@ -21,6 +21,7 @@ pub mod c {
use std::borrow::ToOwned; use std::borrow::ToOwned;
// The lifetime on input limits the existence of the result
pub fn as_str(s: &*const c_char) -> Result<Option<&str>, Utf8Error> { pub fn as_str(s: &*const c_char) -> Result<Option<&str>, Utf8Error> {
if s.is_null() { if s.is_null() {
Ok(None) Ok(None)
@ -189,27 +190,6 @@ impl<T> Borrow<Rc<T>> for Pointer<T> {
} }
} }
/// Sugar for logging errors in results
pub trait Warn {
type Value;
fn ok_warn(self, msg: &str) -> Option<Self::Value>;
}
impl<T, E: std::error::Error> Warn for Result<T, E> {
type Value = T;
fn ok_warn(self, msg: &str) -> Option<T> {
self.map_err(|e| {
eprintln!("{}: {}", msg, e);
e
}).ok()
}
}
pub trait WarningHandler {
/// Handle a warning
fn handle(&mut self, warning: &str);
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

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

View File

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

View File

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

View File

@ -1,16 +1,10 @@
--- ---
# punctuation # punctuation
bounds:
x: 0
y: 0
width: 0
height: 0
views: views:
base: base:
- "." - "."
outlines: outlines:
default: default: { width: 0, height: 0 }
bounds: { x: 0, y: 0, width: 0, height: 0 }
buttons: buttons:
".": ".":

View File

@ -1,16 +1,10 @@
--- ---
# punctuation # punctuation
bounds:
x: 0
y: 0
width: 0
height: 0
views: views:
base: base:
- "å" - "å"
outlines: outlines:
default: default: { width: 0, height: 0 }
bounds: { x: 0, y: 0, width: 0, height: 0 }
buttons: buttons:
å: å:

View File

@ -1,14 +1,8 @@
--- ---
# punctuation # punctuation
bounds:
x: 0
y: 0
width: 0
height: 0
views: views:
base: base:
- "か゚" # 2 codepoints - "か゚" # 2 codepoints
outlines: outlines:
default: default: { width: 0, height: 0 }
bounds: { x: 0, y: 0, width: 0, height: 0 }

View File

@ -0,0 +1,9 @@
---
# Margins present
margins: { top: 1, side: 2, bottom: 3 }
views:
base:
- "test"
outlines:
default: { width: 1, height: 1 }

View File

@ -0,0 +1,9 @@
---
# Margins present
margins: { top: 1, side: 2, bottom: 3 }
views:
base:
- "test"
outlines:
default: { width: 1, height: 1 }

View File

@ -58,6 +58,8 @@ foreach layout : [
'no', 'no',
'number', 'number',
'se', 'se',
'emoji',
] ]
test( test(
'test_layout_' + layout, 'test_layout_' + layout,

View File

@ -12,19 +12,6 @@ except AttributeError:
print("Terminal purpose not available on this GTK version", file=sys.stderr) print("Terminal purpose not available on this GTK version", file=sys.stderr)
terminal = [] terminal = []
def new_grid(items, set_type):
grid = Gtk.Grid(orientation='vertical', column_spacing=8, row_spacing=8)
i = 0
for text, value in items:
l = Gtk.Label(label=text)
e = Gtk.Entry(hexpand=True)
set_type(e, value)
grid.attach(l, 0, i, 1, 1)
grid.attach(e, 1, i, 1, 1)
i += 1
return grid
class App(Gtk.Application): class App(Gtk.Application):
purposes = [ purposes = [
@ -40,23 +27,20 @@ class App(Gtk.Application):
("PIN", Gtk.InputPurpose.PIN), ("PIN", Gtk.InputPurpose.PIN),
] + terminal ] + terminal
hints = [
("OSK provided", Gtk.InputHints.INHIBIT_OSK)
]
def do_activate(self): def do_activate(self):
w = Gtk.ApplicationWindow(application=self) w = Gtk.ApplicationWindow(application=self)
notebook = Gtk.Notebook() grid = Gtk.Grid(orientation='vertical', column_spacing=8, row_spacing=8)
def add_purpose(entry, purpose): i = 0
entry.set_input_purpose(purpose) for text, purpose in self.purposes:
def add_hint(entry, hint):
entry.set_input_hints(hint)
purpose_grid = new_grid(self.purposes, add_purpose)
hint_grid = new_grid(self.hints, add_hint)
notebook.append_page(purpose_grid, Gtk.Label(label="Purposes")) l = Gtk.Label(label=text)
notebook.append_page(hint_grid, Gtk.Label(label="Hints")) e = Gtk.Entry(hexpand=True)
w.add(notebook) e.set_input_purpose(purpose)
grid.attach(l, 0, i, 1, 1)
grid.attach(e, 1, i, 1, 1)
i += 1
w.add(grid)
w.show_all() w.show_all()
app = App() app = App()

19
tools/meson.build Normal file
View File

@ -0,0 +1,19 @@
entry = configure_file(
copy: true,
input: 'entry.py',
output: 'squeekboard-entry',
install: true,
install_dir: bindir,
)
test_layout = custom_target('squeekboard-test-layout',
build_by_default: true,
# meson doesn't track all inputs, cargo does
build_always_stale: true,
output: ['squeekboard-test-layout'],
console: true,
command: [cargo_build] + cargo_build_flags
+ ['--rename', 'test_layout', '@OUTPUT@', '--bin', 'test_layout'],
install: true,
install_dir: bindir,
)