Compare commits

...

60 Commits

Author SHA1 Message Date
1488aa97f3 Release 1.8.1 "Corona"
- Landscape layout doesn't crash
- CSS font is actually taken into account
- Failed start due to dbus is now communicated
- Better log messages
- Fixed Enter in numbers layout
- More consistent terminal layout
- Proper font sizes in terminal layout
2020-01-31 10:08:59 +00:00
4eaa8e316e cargo: Update deps 2020-01-31 10:08:48 +00:00
cbee649939 Merge branch 'update-docs' into 'master'
Update docs and CI builds

See merge request Librem5/squeekboard!320
2020-01-30 22:57:07 +00:00
99b1439d08 Use pip to install recommonmark 2020-01-30 16:51:35 +01:00
83fe2757ef Tidy build file and docs 2020-01-30 16:51:20 +01:00
d21d278710 Merge branch 'faster' into 'master'
build: Strip clap of optional features

See merge request Librem5/squeekboard!311
2020-01-29 09:27:42 +00:00
e6ca914d65 Merge branch 'return' into 'master'
number: Fix keysym for Return

See merge request Librem5/squeekboard!310
2020-01-29 09:26:05 +00:00
0d96a647f9 Merge branch 'packaging' into 'master'
debian: Add missing commas

See merge request Librem5/squeekboard!316
2020-01-29 09:05:07 +00:00
852289b5e3 Merge branch 'switch' into 'master'
setup: Connect ui to the state manager

See merge request Librem5/squeekboard!319
2020-01-28 22:04:47 +00:00
1f5e9566e4 debian: Add missing commas 2020-01-28 21:38:58 +00:00
7a588460bf setup: Connect ui to the state manager
This ensures that the layout type information is accessible to the state manager when new layout information arrive.

The should be thought of as a stopgap measure. A proper solution would be to separate the state properly, and probably turn layout information coming from random places into messages that some object (thread?) collects and displays.
2020-01-28 21:32:47 +00:00
d654b9cc73 Merge branch 'ux' into 'master'
layout: terminal: Replace actions button with period on symbols view

See merge request Librem5/squeekboard!317
2020-01-28 20:55:25 +00:00
3ed601a7e8 layout: terminal: Replace actions button with period on symbols view
Commit ab67bd2c5c took things a bit too
far and completely removed the period button.
2020-01-28 20:51:15 +01:00
63d68c004a Merge branch 'fix_wide' into 'master'
layouts: Fix segfault on switching to wide

See merge request Librem5/squeekboard!312
2020-01-28 19:43:46 +00:00
34c6d2ff28 Merge branch 'fintsize' into 'master'
font: Use font from style context

See merge request Librem5/squeekboard!313
2020-01-28 19:22:22 +00:00
9368a188b3 Merge branch 'ux' into 'master'
Terminal layout UX tweaks

Closes #175

See merge request Librem5/squeekboard!314
2020-01-28 19:16:07 +00:00
ab67bd2c5c layout: terminal: Show actions button on all views 2020-01-28 19:34:03 +01:00
f834fafd67 layout: terminal: Swap positions of preferences and actions button
This makes it consistent with regular layouts.

Helps with #175
2020-01-28 19:33:58 +01:00
4b34f18d34 font: Only pass relevant data to label renderer
This will help factoring the function out
2020-01-28 18:13:19 +00:00
d5682de47c font: Use font from style context
As a consequence, some dependency on renderer is gone.
2020-01-28 18:13:15 +00:00
2ffbdde758 layouts: Fix segfault on switching to wide 2020-01-28 16:42:58 +00:00
ac360b610f Merge branch 'log' into 'master'
Unify logging

See merge request Librem5/squeekboard!308
2020-01-28 11:42:02 +00:00
acfa48886d build: Strip clap of optional features
This makes the build marginally faster at the cost of losing non-essential command line parsing in test_layout.
2020-01-25 17:25:02 +00:00
f326929634 Merge branch 'text_input' into 'master'
Text input integration

See merge request Librem5/squeekboard!302
2020-01-24 09:41:14 +00:00
dbb8331294 number: Fix keysym for Return 2020-01-23 15:43:36 +00:00
09075e57c8 Merge branch 'fix_ci' into 'master'
ci: Clean up `..` before it's searched for artifacts

See merge request Librem5/squeekboard!305
2020-01-21 19:52:18 +00:00
2b65beba44 press_key: Use proper logging 2020-01-20 15:40:30 +00:00
5129d42577 Merge remote-tracking branch 'upstream/master' into log 2020-01-20 15:40:01 +00:00
2ed4862db8 Merge branch '1.8' into 'master'
Release 1.8.0

See merge request Librem5/squeekboard!303
2020-01-20 14:21:04 +00:00
8d06815279 Merge branch 'cleanups' into 'master'
C-side Cleanups

See merge request Librem5/squeekboard!300
2020-01-19 12:57:40 +00:00
c75e085dc8 logging: Unified to remove random eprint calls 2020-01-17 12:25:39 +00:00
cc418c3609 imservice: Return something more resembling an Error on failure
The error type is expected to be printable by logging utilities.
2020-01-17 11:59:47 +00:00
ea84f4f031 logging: Try to improve common operations
This adds sugar for logging `Result`s with a handler, makes names evoke something closer to "logging" than "warning", tries to remove any redundant `Logging` where the module name will do, and introduces a type strictly for bad things happening.
2020-01-16 15:57:46 +00:00
38398395bc Merge branch 'dbus_error' into 'master'
dbus: Log error on dbus exit

See merge request Librem5/squeekboard!307
2020-01-15 17:48:05 +00:00
81e0c15db9 dbus: Log error on dbus exit 2020-01-15 17:06:00 +00:00
60c68dbf5a ci: Clean up .. before it's searched for artifacts
GitLab doesn't always clean up the `..` directory, leaving things that are lated picked up as artifacts. The new rule cleans up anything that looks like an artifact before fresh ones are generated.
2020-01-14 18:47:04 +00:00
f3d852f552 Merge branch 'handling' into 'master'
Centralize handling release events

See merge request Librem5/squeekboard!289
2020-01-14 18:38:43 +00:00
e3f31cc17f imservice: Rename commit_state to done to match protocol 2020-01-14 18:16:36 +00:00
02c24a50d2 submission: Remove wildcard reexport 2020-01-14 11:38:44 +00:00
26dbcdeb62 keyboard: Gather up keymap handling, drop layout 2020-01-13 13:53:54 +00:00
0ef02ebfa3 levelkeyboard: Drop unused manager references 2020-01-13 13:53:54 +00:00
0ce19b4269 keyboard: Cleanups of unused code 2020-01-13 13:53:54 +00:00
326bb9319f submission: Take over virtual_keyboard handling 2020-01-13 13:53:54 +00:00
aafecfac02 EekGtkKeyboard: Use a direct reference to EekboardContext 2020-01-13 13:53:54 +00:00
e5d416fd4f imservice: Limited scope of unsafe 2020-01-13 13:53:54 +00:00
785717d477 submission: Create a new wrapper over imservice 2020-01-13 13:53:48 +00:00
51f55fbff8 submission: Move away from virtual-keyboard 2020-01-11 16:20:09 +00:00
92c9572ac2 services: Split out layout management from EekboardContextService
Layout management was pointlessly bound with the EekboardContextService with inheritance. Splitting it out will make it easier to further break apart layout state management, settings, and input method in the future.
2020-01-11 15:33:26 +00:00
58b087e35a eekboard_context_service: Drop unused enable property 2020-01-09 20:13:22 +00:00
14d5881f1e key-emitter: Remove unused 2020-01-09 19:57:14 +00:00
7dd8bd54c2 context: Moved keymap setting together with its generation 2020-01-09 16:42:17 +00:00
4c2cef30f2 dbus: Rename handler from eekboard_service 2020-01-09 16:25:53 +00:00
3ecfd701d9 dbus: Remove unneeded gobjectness
Also removed the code linking dbus interface stop to application quit. DBus going missing was not handled, and isn't a fatal error anyway.
2020-01-09 16:13:09 +00:00
033a1cf200 dbus_service: Remove unused function 2020-01-09 15:53:49 +00:00
9f59279307 managers: Move visible flag to UI manager 2020-01-09 14:14:48 +00:00
7e72722a47 UI: Drop indirection for show/hide functions 2020-01-09 13:30:02 +00:00
375daa68c8 layout: Make handling presses uniform 2020-01-09 12:09:28 +00:00
34db364a62 layout: Centralize handling key releases 2020-01-08 18:52:09 +00:00
950310c8a5 keyboard: Introduce a KeyCode type wrapping u32 2020-01-08 18:52:09 +00:00
e77eccf7db action: Rename Level to View 2020-01-08 18:52:09 +00:00
52 changed files with 1435 additions and 1602 deletions

View File

@ -22,7 +22,8 @@ build_docs:
paths:
- _build
script:
- apt-get -y install python3-recommonmark python3-sphinx
- apt-get -y install python3-pip python3-sphinx
- pip3 install recommonmark
- ./doc/build.sh _build
build_meson:
@ -45,6 +46,7 @@ build_deb:
paths:
- "*.deb"
script:
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
@ -59,6 +61,7 @@ build_deb:arm64:
paths:
- "*.deb"
script:
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b

79
Cargo.lock generated
View File

@ -2,20 +2,12 @@
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.6"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atk-sys"
version = "0.7.0"
@ -27,16 +19,6 @@ dependencies = [
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.4"
@ -77,18 +59,14 @@ name = "clap"
version = "2.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -254,14 +232,6 @@ dependencies = [
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -328,7 +298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.7"
version = "1.0.8"
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)",
@ -339,7 +309,7 @@ name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -347,16 +317,16 @@ name = "regex"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"aho-corasick 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.13"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -393,9 +363,9 @@ name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -403,23 +373,18 @@ name = "serde_yaml"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
"dtoa 0.4.5 (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.104 (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 = "strsim"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.8 (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)",
]
@ -455,11 +420,6 @@ name = "utf8-ranges"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
@ -497,16 +457,14 @@ dependencies = [
]
[metadata]
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum aho-corasick 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5f56c476256dc249def911d6f7580b5fc7e875895b5d7ee88f5d602208035744"
"checksum atk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7017e53393e713212aed7aea336b6553be4927f58c37070a56c2fe3d107e489"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum cairo-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd940f0d609699e343ef71c4af5f66423afbf30d666f796dabd8fd15229cf5b6"
"checksum cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d25596627380be4381247dba06c69ad05ca21b3b065bd9827e416882ac41dcd2"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
"checksum fragile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f8140122fa0d5dcb9fc8627cfce2b37cc1500f752636d46ea28bc26785c2f9"
"checksum gdk 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc52c7244046df9d959df87289f1fc5cca23f9f850bab0c967963e2ecb83a96"
"checksum gdk-pixbuf 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc3aa730cb4df3de5d9fed59f43afdf9e5fb2d3d10bfcbd04cec031435ce87f5"
@ -519,7 +477,6 @@ dependencies = [
"checksum gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08475e4a08f27e6e2287005950114735ed61cec2cb8c1187682a5aec8c69b715"
"checksum gtk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56a6b30f194f09a17bb7ffa95c3ecdb405abd3b75ff981f831b1f6d18fe115ff"
"checksum gtk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d487d333a4b87072e6bf9f2e55befa0ebef01b9496c2e263c0f4a1ff3d6c04b1"
"checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
@ -529,21 +486,19 @@ dependencies = [
"checksum pango 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c2cb169402a3eb1ba034a7cc7d95b8b1c106e9be5ba4be79a5a93dc1a2795f4"
"checksum pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6eb49268e69dd0c1da5d3001a61aac08e2e9d2bfbe4ae4b19b9963c998f6453"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum proc-macro2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc"
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d9d8297cc20bbb6184f8b45ff61c8ee6a9ac56c156cec8e38c3e5084773c44ad"
"checksum regex-syntax 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90"
"checksum regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8"
"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -4,7 +4,7 @@ version = "0.1.0"
[dependencies]
bitflags = "1.0.*"
clap = "2.32.*"
clap = { version = "2.32.*", default-features = false }
maplit = "1.0.*"
regex = "1.1.*"
serde = { version = "1.0.*", features = ["derive"] }

View File

@ -23,7 +23,7 @@ buttons:
Return:
outline: outline7
icon: "key-enter"
keysym: "BackSpace"
keysym: "Return"
asterisk:
text: "*"
numbersign:

View File

@ -12,12 +12,12 @@ views:
- "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 show_actions space preferences Return"
- "show_numbers preferences space show_actions 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"
- "show_numbers preferences space show_actions Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # $ % & - _ + ( )"

View File

@ -2,6 +2,7 @@ sq_view {
background-color: rgba(0, 0, 0, 255);
color: #ffffff;
font-family: cantarell, sans-serif;
font-size: 25px;
}
sq_view sq_button {
@ -35,6 +36,10 @@ sq_button.locked {
color: #2b292f;
}
sq_button.action {
font-size: 0.75em;
}
#Return {
background: #1c71d8;
border-color: #1a5fb4;

View File

@ -2,6 +2,7 @@ sq_view {
background-color: @theme_base_color; /*rgba(0, 0, 0, 255);*/
color: @theme_text_color; /*#ffffff;*/
font-family: cantarell, sans-serif;
font-size: 25px;
}
sq_view sq_button {
@ -38,6 +39,10 @@ sq_button.locked {
color: @theme_bg_color; /*#2b292f;*/
}
sq_button.action {
font-size: 0.75em;
}
#Return {
background: @theme_selected_bg_color; /* #1c71d8; */
border-color: @borders; /*#1a5fb4;*/

53
debian/changelog vendored
View File

@ -1,3 +1,56 @@
squeekboard (1.8.1) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* action: Rename Level to View
* keyboard: Introduce a KeyCode type wrapping u32
* layout: Centralize handling key releases
* layout: Make handling presses uniform
* UI: Drop indirection for show/hide functions
* managers: Move visible flag to UI manager
* dbus_service: Remove unused function
* dbus: Remove unneeded gobjectness
* dbus: Rename handler from eekboard_service
* context: Moved keymap setting together with its generation
* key-emitter: Remove unused
* eekboard_context_service: Drop unused enable property
* services: Split out layout management from EekboardContextService
* submission: Move away from virtual-keyboard
* submission: Create a new wrapper over imservice
* imservice: Limited scope of unsafe
* EekGtkKeyboard: Use a direct reference to EekboardContext
* submission: Take over virtual_keyboard handling
* keyboard: Cleanups of unused code
* levelkeyboard: Drop unused manager references
* keyboard: Gather up keymap handling, drop layout
* submission: Remove wildcard reexport
* imservice: Rename commit_state to done to match protocol
* ci: Clean up `..` before it's searched for artifacts
* dbus: Log error on dbus exit
* logging: Try to improve common operations
* imservice: Return something more resembling an Error on failure
* logging: Unified to remove random eprint calls
* press_key: Use proper logging
* number: Fix keysym for Return
* build: Strip clap of optional features
* layouts: Fix segfault on switching to wide
* font: Use font from style context
* font: Only pass relevant data to label renderer
[ Sebastian Krzyszkowiak ]
* layout: terminal: Swap positions of preferences and actions button
* layout: terminal: Show actions button on all views
* layout: terminal: Replace actions button with period on symbols view
[ Dorota Czaplejewicz ]
* setup: Connect ui to the state manager
* debian: Add missing commas
[ David Boddie ]
* Tidy build file and docs
* Use pip to install recommonmark
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Fri, 31 Jan 2020 09:59:12 +0000
squeekboard (1.8.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]

8
debian/control vendored
View File

@ -35,8 +35,8 @@ Architecture: linux-any
Depends:
# for the Adwaita-dark theme
gnome-themes-extra-data,
${shlibs:Depends}
${misc:Depends}
${shlibs:Depends},
${misc:Depends},
Description: On-screen keyboard for Wayland
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.
@ -45,8 +45,8 @@ Architecture: linux-any
Depends:
python3,
python3-gi,
${shlibs:Depends}
${misc:Depends}
${shlibs:Depends},
${misc:Depends},
Description: Resources for making Squeekboard layouts
Tools for creating and testing Squeekboard layouts:
.

View File

@ -12,6 +12,10 @@ TARGET_DIR="${1:-./}"
SPHINX=sphinx-build
if [ ! -d $DOCS_DIR/_static ]; then
mkdir -p $DOCS_DIR/_static
fi
if ! which sphinx-build ; then
SPHINX=sphinx-build-3
fi

View File

@ -9,12 +9,12 @@ Contents
Introduction
------------
Squeekboard is the on-screen keyobard for the Librem 5 phone. For more information, look at the [README](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md).
Squeekboard is the on-screen keyboard for the Librem 5 phone. For more information, look at the [README](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md).
Layouts
-------
Squeekboard allows user-provided keyboard layouts. They can be created without recompiling the keyboard code. The tutorial](/tutorial.md) explains the process in detail.
Squeekboard allows user-provided keyboard layouts. They can be created without recompiling the keyboard code. The [tutorial](/tutorial.md) explains the process in detail.
Layouts are created using a text-based format, based on YAML.

View File

@ -34,13 +34,15 @@ So at least I will try to start writing a short how-to here and edit this post a
* Additionally take a look at https://source.puri.sm/Librem5/squeekboard/blob/master/HACKING.md#testing
* You can either test it locally on your Linux system or use the [QEMU Librem 5 image ](https://developer.puri.sm/Librem5/Development_Environment/Boards/emulators.html)
* To test squeekboard locally, you need phoc. Either compile that from the sources as well or use the CI repository ci.puri.sm for Debian based systems:
`deb [arch=amd64] http://ci.puri.sm/ scratch librem5`
`deb [arch=amd64] http://ci.puri.sm/ scratch librem5`
Squeekboard can be installed from there as a Debian package, too (thats what I often do). But beware - there be dragons! You could bork your system with these packages and you should probably disable this repository again after installing what you need - these packages are not meant for production systems (or so I heard :wink: )
**Creating the keyboard layout**
* To be written: For the time being, take a look at [Using non-latin language on Librem 5 ](https://forums.puri.sm/t/using-non-latin-language-on-librem-5/7103/5)
* The correct name of the .yaml file can be found with the command `gsettings get org.gnome.desktop.input-sources sources`
The output should be something like this: `[('xkb', 'us'), ('xkb', 'de')]`
So f.ex. “de.yaml” would be the correct name for the German keyboard layout.
* The translations for the keyboard layout names in the different languages can be found at `data/langs/`

View File

@ -39,6 +39,7 @@
#include "eekboard/eekboard-context-service.h"
#include "src/layout.h"
#include "src/submission.h"
enum {
PROP_0,
@ -54,6 +55,8 @@ enum {
typedef struct _EekGtkKeyboardPrivate
{
EekRenderer *renderer;
EekboardContextService *eekboard_context; // unowned reference
struct submission *submission; // unowned reference
LevelKeyboard *keyboard; // unowned reference; it's kept in server-context (FIXME)
GdkEventSequence *sequence; // unowned reference
@ -122,7 +125,8 @@ static void depress(EekGtkKeyboard *self,
{
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_depress(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
squeek_layout_depress(priv->keyboard->layout,
priv->submission,
x, y, eek_renderer_get_transformation(priv->renderer), time, self);
}
@ -130,18 +134,20 @@ static void drag(EekGtkKeyboard *self,
gdouble x, gdouble y, guint32 time)
{
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->submission,
x, y, eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
priv->eekboard_context, self);
}
static void release(EekGtkKeyboard *self, guint32 time)
{
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
squeek_layout_release(priv->keyboard->layout,
priv->submission,
eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
priv->eekboard_context, self);
}
static gboolean
@ -229,7 +235,8 @@ eek_gtk_keyboard_real_unmap (GtkWidget *self)
if (priv->keyboard) {
squeek_layout_release_all_only(
priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
priv->keyboard->layout,
priv->submission,
gdk_event_get_time(NULL));
}
@ -264,7 +271,8 @@ eek_gtk_keyboard_dispose (GObject *object)
if (priv->keyboard) {
squeek_layout_release_all_only(
priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
priv->keyboard->layout,
priv->submission,
gdk_event_get_time(NULL));
priv->keyboard = NULL;
}
@ -311,11 +319,14 @@ eek_gtk_keyboard_init (EekGtkKeyboard *self)
* Returns: a #GtkWidget
*/
GtkWidget *
eek_gtk_keyboard_new (LevelKeyboard *keyboard)
eek_gtk_keyboard_new (LevelKeyboard *keyboard, EekboardContextService *eekservice,
struct submission *submission)
{
EekGtkKeyboard *ret = EEK_GTK_KEYBOARD(g_object_new (EEK_TYPE_GTK_KEYBOARD, NULL));
EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (ret);
priv->keyboard = keyboard;
priv->eekboard_context = eekservice;
priv->submission = submission;
return GTK_WIDGET(ret);
}

View File

@ -28,6 +28,9 @@
#include <glib.h>
#include <gtk/gtk.h>
#include "eek/eek-types.h"
struct submission;
typedef struct _LevelKeyboard LevelKeyboard; // including causes weird bugs
G_BEGIN_DECLS
@ -45,7 +48,7 @@ struct _EekGtkKeyboardClass
};
GType eek_gtk_keyboard_get_type (void) G_GNUC_CONST;
GtkWidget *eek_gtk_keyboard_new (LevelKeyboard *keyboard);
GtkWidget *eek_gtk_keyboard_new (LevelKeyboard *keyboard, EekboardContextService *eekservice, struct submission *submission);
G_END_DECLS
#endif /* EEK_GTK_KEYBOARD_H */

View File

@ -18,41 +18,78 @@
* 02110-1301 USA
*/
/**
* SECTION:eek-keyboard
* @short_description: Base class of a keyboard
* @see_also: #EekSection
*
* The #EekKeyboardClass class represents a keyboard, which consists
* of one or more sections of the #EekSectionClass class.
*/
#include "config.h"
#include <glib/gprintf.h>
#include "eekboard/eekboard-context-service.h"
#include "eekboard/key-emitter.h"
#include "keymap.h"
#define _XOPEN_SOURCE 500
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/random.h> // TODO: this is Linux-specific
#include <xkbcommon/xkbcommon.h>
#include "eek-keyboard.h"
void level_keyboard_deinit(LevelKeyboard *self) {
void level_keyboard_free(LevelKeyboard *self) {
xkb_keymap_unref(self->keymap);
close(self->keymap_fd);
squeek_layout_free(self->layout);
}
void level_keyboard_free(LevelKeyboard *self) {
level_keyboard_deinit(self);
g_free(self);
}
void level_keyboard_init(LevelKeyboard *self, struct squeek_layout *layout) {
self->layout = layout;
}
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_layout *layout) {
LevelKeyboard*
level_keyboard_new (const gchar *keyboard_type,
enum squeek_arrangement_kind t)
{
struct squeek_layout *layout = squeek_load_layout(keyboard_type, t);
LevelKeyboard *keyboard = g_new0(LevelKeyboard, 1);
level_keyboard_init(keyboard, layout);
keyboard->manager = manager;
if (!keyboard) {
g_error("Failed to create a keyboard");
}
keyboard->layout = layout;
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
g_error("No context created");
}
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap)
g_error("Bad keymap:\n%s", keymap_str);
xkb_context_unref(context);
keyboard->keymap = keymap;
keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
keyboard->keymap_len = strlen(keymap_str) + 1;
g_autofree char *path = strdup("/eek_keymap-XXXXXX");
char *r = &path[strlen(path) - 6];
getrandom(r, 6, GRND_NONBLOCK);
for (unsigned i = 0; i < 6; i++) {
r[i] = (r[i] & 0b1111111) | 0b1000000; // A-z
r[i] = r[i] > 'z' ? '?' : r[i]; // The randomizer doesn't need to be good...
}
int keymap_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
if (keymap_fd < 0) {
g_error("Failed to set up keymap fd");
}
keyboard->keymap_fd = keymap_fd;
shm_unlink(path);
if (ftruncate(keymap_fd, (off_t)keyboard->keymap_len)) {
g_error("Failed to increase keymap fd size");
}
char *ptr = mmap(NULL, keyboard->keymap_len, PROT_WRITE, MAP_SHARED,
keymap_fd, 0);
if ((void*)ptr == (void*)-1) {
g_error("Failed to set up mmap");
}
strncpy(ptr, keymap_str, keyboard->keymap_len);
munmap(ptr, keyboard->keymap_len);
return keyboard;
}

View File

@ -40,16 +40,15 @@ struct _LevelKeyboard {
size_t keymap_len; // length of the data inside keymap_fd
guint id; // as a key to layout choices
EekboardContextService *manager; // unowned reference
};
typedef struct _LevelKeyboard LevelKeyboard;
gchar * eek_keyboard_get_keymap
(LevelKeyboard *keyboard);
LevelKeyboard *level_keyboard_new(EekboardContextService *manager, struct squeek_layout *layout);
void level_keyboard_deinit(LevelKeyboard *self);
LevelKeyboard*
level_keyboard_new (const gchar *keyboard_type,
enum squeek_arrangement_kind t);
void level_keyboard_free(LevelKeyboard *self);
G_END_DECLS

View File

@ -42,22 +42,17 @@ typedef struct _EekRendererPrivate
GtkStyleContext *view_context; // owned
GtkStyleContext *button_context; // TODO: maybe move a copy to each button
gdouble border_width; // FIXME: border of what?
gdouble allocation_width;
gdouble allocation_height;
gint scale_factor; /* the outputs scale factor */
struct transformation widget_to_layout;
PangoFontDescription *font; // owned reference
} EekRendererPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (EekRenderer, eek_renderer, G_TYPE_OBJECT)
/* eek-keyboard-drawing.c */
static void eek_renderer_render_button_label (EekRenderer *self, cairo_t *cr, GtkStyleContext *ctx,
const struct squeek_button *button);
static void render_button_label (cairo_t *cr, GtkStyleContext *ctx,
const gchar *label, EekBounds bounds);
void eek_render_button (EekRenderer *self,
cairo_t *cr, const struct squeek_button *button,
@ -86,8 +81,7 @@ render_outline (cairo_t *cr,
position.x, position.y, position.width, position.height);
}
static void render_button_in_context(EekRenderer *self,
gint scale_factor,
static void render_button_in_context(gint scale_factor,
cairo_t *cr,
GtkStyleContext *ctx,
const struct squeek_button *button) {
@ -130,7 +124,11 @@ static void render_button_in_context(EekRenderer *self,
return;
}
}
eek_renderer_render_button_label (self, cr, ctx, button);
const gchar *label = squeek_button_get_label(button);
if (label) {
render_button_label (cr, ctx, label, squeek_button_get_bounds(button));
}
}
void
@ -162,7 +160,7 @@ eek_render_button (EekRenderer *self,
}
gtk_style_context_add_class(ctx, outline_name);
render_button_in_context(self, priv->scale_factor, cr, ctx, button);
render_button_in_context(priv->scale_factor, cr, ctx, button);
// Save and restore functions don't work if gtk_render_* was used in between
gtk_style_context_set_state(ctx, GTK_STATE_FLAG_NORMAL);
@ -173,43 +171,16 @@ eek_render_button (EekRenderer *self,
}
static void
eek_renderer_render_button_label (EekRenderer *self,
cairo_t *cr,
GtkStyleContext *ctx,
const struct squeek_button *button)
render_button_label (cairo_t *cr,
GtkStyleContext *ctx,
const gchar *label,
EekBounds bounds)
{
EekRendererPrivate *priv = eek_renderer_get_instance_private (self);
const gchar *label = squeek_button_get_label(button);
if (!label) {
return;
}
PangoFontDescription *font;
gdouble scale;
if (!priv->font) {
const PangoFontDescription *base_font;
gdouble size;
base_font = pango_context_get_font_description (priv->pcontext);
// FIXME: Base font size on the same size unit used for button sizing,
// and make the default about 1/3 of the current row height
size = 30000.0;
priv->font = pango_font_description_copy (base_font);
pango_font_description_set_size (priv->font, (gint)round(size * 0.6));
}
EekBounds bounds = squeek_button_get_bounds(button);
scale = MIN((bounds.width - priv->border_width) / bounds.width,
(bounds.height - priv->border_width) / bounds.height);
font = pango_font_description_copy (priv->font);
pango_font_description_set_size (font,
(gint)round(pango_font_description_get_size (font) * scale));
gtk_style_context_get(ctx,
gtk_style_context_get_state(ctx),
"font", &font,
NULL);
PangoLayout *layout = pango_cairo_create_layout (cr);
pango_layout_set_font_description (layout, font);
pango_font_description_free (font);
@ -219,8 +190,7 @@ eek_renderer_render_button_label (EekRenderer *self,
if (line->resolved_dir == PANGO_DIRECTION_RTL) {
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
}
pango_layout_set_width (layout,
PANGO_SCALE * bounds.width * scale);
pango_layout_set_width (layout, PANGO_SCALE * bounds.width);
PangoRectangle extents = { 0, };
pango_layout_get_extents (layout, NULL, &extents);
@ -331,7 +301,6 @@ eek_renderer_finalize (GObject *object)
g_object_unref(priv->css_provider);
g_object_unref(priv->view_context);
g_object_unref(priv->button_context);
pango_font_description_free (priv->font);
G_OBJECT_CLASS (eek_renderer_parent_class)->finalize (object);
}
@ -390,11 +359,9 @@ eek_renderer_init (EekRenderer *self)
priv->keyboard = NULL;
priv->pcontext = NULL;
priv->border_width = 1.0;
priv->allocation_width = 0.0;
priv->allocation_height = 0.0;
priv->scale_factor = 1;
priv->font = NULL;
GtkIconTheme *theme = gtk_icon_theme_get_default ();

View File

@ -38,6 +38,7 @@ G_BEGIN_DECLS
typedef struct _EekBounds EekBounds;
typedef struct _EekboardContextService EekboardContextService;
typedef struct _ServerContextService ServerContextService;
typedef struct _LevelKeyboard LevelKeyboard;
/**

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:eek-xml-layout
* @short_description: Layout engine which loads layout information from XML
*/
#include "config.h"
#include "eek-keyboard.h"
#include "src/layout.h"
#include "eek-xml-layout.h"
LevelKeyboard *
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
EekboardContextService *manager,
enum squeek_arrangement_kind t)
{
struct squeek_layout *layout = squeek_load_layout(keyboard_type, t);
return level_keyboard_new(manager, layout);
}

View File

@ -1,36 +0,0 @@
/*
* Copyright (C) 2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !defined(__EEK_H_INSIDE__) && !defined(EEK_COMPILATION)
#error "Only <eek/eek.h> can be included directly."
#endif
#ifndef EEK_XML_LAYOUT_H
#define EEK_XML_LAYOUT_H 1
#include "eek-types.h"
#include "src/layout.h"
G_BEGIN_DECLS
LevelKeyboard *
eek_xml_layout_real_create_keyboard (const char *keyboard_type,
EekboardContextService *manager,
enum squeek_arrangement_kind t);
G_END_DECLS
#endif /* EEK_XML_LAYOUT_H */

View File

@ -1,8 +0,0 @@
#include <gdk/gdk.h>
#include <xkbcommon/xkbcommon.h>
gboolean
squeek_keymap_get_entries_for_keyval (struct xkb_keymap *xkb_keymap,
guint keyval,
GdkKeymapKey **keys,
guint *n_keys);

View File

@ -16,31 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:eekboard-context-service
* @short_description: base server implementation of eekboard input
* context service
*
* The #EekboardService class provides a base server side
* implementation of eekboard input context service.
*/
#include "config.h"
#include <fcntl.h>
#include <stdio.h>
#define _XOPEN_SOURCE 500
#include <string.h>
#include <sys/mman.h>
#include <sys/random.h> // TODO: this is Linux-specific
#include <xkbcommon/xkbcommon.h>
#include <gio/gio.h>
#include "eekboard/key-emitter.h"
#include "wayland.h"
#include "eek/eek-xml-layout.h"
#include "eek/eek-keyboard.h"
#include "src/server-context-service.h"
#include "eekboard/eekboard-context-service.h"
@ -48,13 +32,10 @@
enum {
PROP_0, // Magic: without this, keyboard is not useable in g_object_notify
PROP_KEYBOARD,
PROP_VISIBLE,
PROP_LAST
};
enum {
ENABLED,
DISABLED,
DESTROYED,
LAST_SIGNAL
};
@ -65,105 +46,32 @@ static guint signals[LAST_SIGNAL] = { 0, };
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServicePrivate))
struct _EekboardContextServicePrivate {
gboolean enabled;
gboolean visible;
LevelKeyboard *keyboard; // currently used keyboard
GHashTable *keyboard_hash; // a table of available keyboards, per layout
char *overlay;
GSettings *settings;
GSettings *settings; // Owned reference
uint32_t hint;
uint32_t purpose;
// Maybe TODO: it's used only for fetching layout type.
// Maybe let UI push the type to this structure?
ServerContextService *ui; // unowned reference
/// Needed for keymap changes after keyboard updates
struct submission *submission; // unowned
};
G_DEFINE_TYPE_WITH_PRIVATE (EekboardContextService, eekboard_context_service, G_TYPE_OBJECT);
static LevelKeyboard *
eekboard_context_service_real_create_keyboard (EekboardContextService *self,
const gchar *keyboard_type,
enum squeek_arrangement_kind t)
{
LevelKeyboard *keyboard = eek_xml_layout_real_create_keyboard(keyboard_type, self, t);
if (!keyboard) {
g_error("Failed to create a keyboard");
}
struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
g_error("No context created");
}
const gchar *keymap_str = squeek_layout_get_keymap(keyboard->layout);
struct xkb_keymap *keymap = xkb_keymap_new_from_string(context, keymap_str,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!keymap)
g_error("Bad keymap:\n%s", keymap_str);
xkb_context_unref(context);
keyboard->keymap = keymap;
keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
keyboard->keymap_len = strlen(keymap_str) + 1;
g_autofree char *path = strdup("/eek_keymap-XXXXXX");
char *r = &path[strlen(path) - 6];
getrandom(r, 6, GRND_NONBLOCK);
for (unsigned i = 0; i < 6; i++) {
r[i] = (r[i] & 0b1111111) | 0b1000000; // A-z
r[i] = r[i] > 'z' ? '?' : r[i]; // The randomizer doesn't need to be good...
}
int keymap_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
if (keymap_fd < 0) {
g_error("Failed to set up keymap fd");
}
keyboard->keymap_fd = keymap_fd;
shm_unlink(path);
if (ftruncate(keymap_fd, (off_t)keyboard->keymap_len)) {
g_error("Failed to increase keymap fd size");
}
char *ptr = mmap(NULL, keyboard->keymap_len, PROT_WRITE, MAP_SHARED,
keymap_fd, 0);
if ((void*)ptr == (void*)-1) {
g_error("Failed to set up mmap");
}
strncpy(ptr, keymap_str, keyboard->keymap_len);
munmap(ptr, keyboard->keymap_len);
return keyboard;
}
static void
eekboard_context_service_real_show_keyboard (EekboardContextService *self)
{
self->priv->visible = TRUE;
}
static void
eekboard_context_service_real_hide_keyboard (EekboardContextService *self)
{
self->priv->visible = FALSE;
}
static void
eekboard_context_service_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE(object);
(void)value;
switch (prop_id) {
case PROP_KEYBOARD:
if (context->priv->keyboard)
g_object_unref (context->priv->keyboard);
context->priv->keyboard = g_value_get_object (value);
break;
case PROP_VISIBLE:
context->priv->visible = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -182,9 +90,6 @@ eekboard_context_service_get_property (GObject *object,
case PROP_KEYBOARD:
g_value_set_object (value, context->priv->keyboard);
break;
case PROP_VISIBLE:
g_value_set_boolean (value, context->priv->visible);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -244,10 +149,15 @@ eekboard_context_service_update_layout(EekboardContextService *context, enum squ
}
// generic part follows
LevelKeyboard *keyboard = eekboard_context_service_real_create_keyboard(context, keyboard_layout, t);
LevelKeyboard *keyboard = level_keyboard_new(keyboard_layout, t);
// set as current
LevelKeyboard *previous_keyboard = context->priv->keyboard;
context->priv->keyboard = keyboard;
// Update the keymap if necessary.
// TODO: Update submission on change event
if (context->priv->submission) {
submission_set_keyboard(context->priv->submission, keyboard);
}
g_object_notify (G_OBJECT(context), "keyboard");
@ -258,7 +168,12 @@ eekboard_context_service_update_layout(EekboardContextService *context, enum squ
}
static void update_layout_and_type(EekboardContextService *context) {
eekboard_context_service_update_layout(context, server_context_service_get_layout_type(context));
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
enum squeek_arrangement_kind layout_kind = ARRANGEMENT_KIND_BASE;
if (priv->ui) {
layout_kind = server_context_service_get_layout_type(priv->ui);
}
eekboard_context_service_update_layout(context, layout_kind);
}
static gboolean
@ -279,12 +194,6 @@ static void
eekboard_context_service_constructed (GObject *object)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE (object);
context->virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
squeek_wayland->virtual_keyboard_manager,
squeek_wayland->seat);
if (!context->virtual_keyboard) {
g_error("Programmer error: Failed to receive a virtual keyboard instance");
}
update_layout_and_type(context);
}
@ -294,48 +203,10 @@ eekboard_context_service_class_init (EekboardContextServiceClass *klass)
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
klass->show_keyboard = eekboard_context_service_real_show_keyboard;
klass->hide_keyboard = eekboard_context_service_real_hide_keyboard;
gobject_class->constructed = eekboard_context_service_constructed;
gobject_class->set_property = eekboard_context_service_set_property;
gobject_class->get_property = eekboard_context_service_get_property;
gobject_class->dispose = eekboard_context_service_dispose;
/**
* EekboardContextService::enabled:
* @context: an #EekboardContextService
*
* Emitted when @context is enabled.
*/
signals[ENABLED] =
g_signal_new (I_("enabled"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(EekboardContextServiceClass, enabled),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EekboardContextService::disabled:
* @context: an #EekboardContextService
*
* Emitted when @context is enabled.
*/
signals[DISABLED] =
g_signal_new (I_("disabled"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(EekboardContextServiceClass, disabled),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EekboardContextService::destroyed:
* @context: an #EekboardContextService
@ -361,24 +232,10 @@ eekboard_context_service_class_init (EekboardContextServiceClass *klass)
pspec = g_param_spec_pointer("keyboard",
"Keyboard",
"Keyboard",
G_PARAM_READWRITE);
G_PARAM_READABLE);
g_object_class_install_property (gobject_class,
PROP_KEYBOARD,
pspec);
/**
* EekboardContextService:visible:
*
* Flag to indicate if keyboard is visible or not.
*/
pspec = g_param_spec_boolean ("visible",
"Visible",
"Visible",
FALSE,
G_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_VISIBLE,
pspec);
}
static void
@ -404,62 +261,6 @@ eekboard_context_service_init (EekboardContextService *self)
self->priv->overlay = NULL;
}
/**
* eekboard_context_service_enable:
* @context: an #EekboardContextService
*
* Enable @context. This function is called when @context is pushed
* by eekboard_service_push_context().
*/
void
eekboard_context_service_enable (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
if (!context->priv->enabled) {
context->priv->enabled = TRUE;
g_signal_emit (context, signals[ENABLED], 0);
}
}
/**
* eekboard_context_service_disable:
* @context: an #EekboardContextService
*
* Disable @context. This function is called when @context is pushed
* by eekboard_service_pop_context().
*/
void
eekboard_context_service_disable (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
if (context->priv->enabled) {
context->priv->enabled = FALSE;
g_signal_emit (context, signals[DISABLED], 0);
}
}
void
eekboard_context_service_show_keyboard (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
if (!context->priv->visible) {
EEKBOARD_CONTEXT_SERVICE_GET_CLASS(context)->show_keyboard (context);
}
}
void
eekboard_context_service_hide_keyboard (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
if (context->priv->visible) {
EEKBOARD_CONTEXT_SERVICE_GET_CLASS(context)->hide_keyboard (context);
}
}
/**
* eekboard_context_service_destroy:
* @context: an #EekboardContextService
@ -471,9 +272,6 @@ eekboard_context_service_destroy (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
if (context->priv->enabled) {
eekboard_context_service_disable (context);
}
g_free(context->priv->overlay);
g_signal_emit (context, signals[DESTROYED], 0);
}
@ -491,14 +289,6 @@ eekboard_context_service_get_keyboard (EekboardContextService *context)
return context->priv->keyboard;
}
void eekboard_context_service_set_keymap(EekboardContextService *context,
const LevelKeyboard *keyboard)
{
zwp_virtual_keyboard_v1_keymap(context->virtual_keyboard,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keyboard->keymap_fd, keyboard->keymap_len);
}
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
uint32_t hint, uint32_t purpose)
{
@ -521,3 +311,19 @@ const char*
eekboard_context_service_get_overlay(EekboardContextService *context) {
return context->priv->overlay;
}
EekboardContextService *eekboard_context_service_new(void)
{
return g_object_new (EEKBOARD_TYPE_CONTEXT_SERVICE, NULL);
}
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission) {
context->priv->submission = submission;
if (context->priv->submission) {
submission_set_keyboard(context->priv->submission, context->priv->keyboard);
}
}
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui) {
context->priv->ui = ui;
}

View File

@ -22,7 +22,8 @@
#ifndef EEKBOARD_CONTEXT_SERVICE_H
#define EEKBOARD_CONTEXT_SERVICE_H 1
#include <eek/eek.h>
#include "src/submission.h"
#include "src/layout.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "text-input-unstable-v3-client-protocol.h"
@ -46,6 +47,8 @@ typedef struct _EekboardContextServicePrivate EekboardContextServicePrivate;
/**
* EekboardContextService:
*
* Handles layout state, gsettings, and virtual-keyboard.
*
* TODO: Restrict to managing keyboard layouts, and maybe button repeats,
* and the virtual keyboard protocol.
*
@ -56,15 +59,11 @@ struct _EekboardContextService {
GObject parent;
EekboardContextServicePrivate *priv;
struct zwp_virtual_keyboard_v1 *virtual_keyboard;
};
/**
* EekboardContextServiceClass:
* @create_keyboard: virtual function for create a keyboard from string
* @show_keyboard: virtual function for show a keyboard
* @hide_keyboard: virtual function for hide a keyboard
* @enabled: class handler for #EekboardContextService::enabled signal
* @disabled: class handler for #EekboardContextService::disabled signal
*/
@ -75,12 +74,8 @@ struct _EekboardContextServiceClass {
/*< public >*/
struct squeek_view *(*create_keyboard) (EekboardContextService *self,
const gchar *keyboard_type);
void (*show_keyboard) (EekboardContextService *self);
void (*hide_keyboard) (EekboardContextService *self);
/* signals */
void (*enabled) (EekboardContextService *self);
void (*disabled) (EekboardContextService *self);
void (*destroyed) (EekboardContextService *self);
/*< private >*/
@ -90,12 +85,9 @@ struct _EekboardContextServiceClass {
GType eekboard_context_service_get_type
(void) G_GNUC_CONST;
void eekboard_context_service_enable (EekboardContextService *context);
void eekboard_context_service_disable (EekboardContextService *context);
void eekboard_context_service_show_keyboard
(EekboardContextService *context);
void eekboard_context_service_hide_keyboard
(EekboardContextService *context);
EekboardContextService *eekboard_context_service_new(void);
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission);
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui);
void eekboard_context_service_destroy (EekboardContextService *context);
LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context);

View File

@ -1,310 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:eekboard-service
* @short_description: base implementation of eekboard service
*
* Provides a dbus object, and contains the context.
*
* The #EekboardService class provides a base server side
* implementation of eekboard service.
*/
#include "config.h"
#include "sm.puri.OSK0.h"
#include <stdio.h>
#include <gio/gio.h>
#include "eekboard/eekboard-service.h"
enum {
PROP_0,
PROP_OBJECT_PATH,
PROP_CONNECTION,
PROP_LAST
};
enum {
DESTROYED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0, };
typedef struct _EekboardServicePrivate
{
GDBusConnection *connection;
SmPuriOSK0 *dbus_interface;
GDBusNodeInfo *introspection_data;
guint registration_id;
char *object_path;
EekboardContextService *context; // unowned reference
} EekboardServicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (EekboardService, eekboard_service, G_TYPE_OBJECT)
static void
eekboard_service_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
EekboardService *service = EEKBOARD_SERVICE(object);
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
GDBusConnection *connection;
switch (prop_id) {
case PROP_OBJECT_PATH:
if (priv->object_path)
g_free (priv->object_path);
priv->object_path = g_value_dup_string (value);
break;
case PROP_CONNECTION:
connection = g_value_get_object (value);
if (priv->connection)
g_object_unref (priv->connection);
priv->connection = g_object_ref (connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
eekboard_service_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EekboardService *service = EEKBOARD_SERVICE(object);
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
switch (prop_id) {
case PROP_OBJECT_PATH:
g_value_set_string (value, priv->object_path);
break;
case PROP_CONNECTION:
g_value_set_object (value, priv->connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
eekboard_service_dispose (GObject *object)
{
EekboardService *service = EEKBOARD_SERVICE(object);
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
if (priv->connection) {
if (priv->registration_id > 0) {
g_dbus_connection_unregister_object (priv->connection,
priv->registration_id);
priv->registration_id = 0;
}
g_object_unref (priv->connection);
priv->connection = NULL;
}
if (priv->introspection_data) {
g_dbus_node_info_unref (priv->introspection_data);
priv->introspection_data = NULL;
}
if (priv->context) {
g_signal_handlers_disconnect_by_data (priv->context, service);
priv->context = NULL;
}
G_OBJECT_CLASS (eekboard_service_parent_class)->dispose (object);
}
static void
eekboard_service_finalize (GObject *object)
{
EekboardService *service = EEKBOARD_SERVICE(object);
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
g_free (priv->object_path);
G_OBJECT_CLASS (eekboard_service_parent_class)->finalize (object);
}
static gboolean
handle_set_visible(SmPuriOSK0 *object, GDBusMethodInvocation *invocation,
gboolean arg_visible, gpointer user_data) {
EekboardService *service = user_data;
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
if (priv->context) {
if (arg_visible) {
eekboard_context_service_show_keyboard (priv->context);
} else {
eekboard_context_service_hide_keyboard (priv->context);
}
}
sm_puri_osk0_complete_set_visible(object, invocation);
return TRUE;
}
static void on_visible(EekboardService *service,
GParamSpec *pspec,
EekboardContextService *context)
{
gboolean visible;
EekboardServicePrivate *priv;
g_return_if_fail (EEKBOARD_IS_SERVICE (service));
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE (context));
priv = eekboard_service_get_instance_private (service);
g_object_get (context, "visible", &visible, NULL);
sm_puri_osk0_set_visible(priv->dbus_interface, visible);
}
static void
eekboard_service_constructed (GObject *object)
{
EekboardService *service = EEKBOARD_SERVICE(object);
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
priv->dbus_interface = sm_puri_osk0_skeleton_new();
g_signal_connect(priv->dbus_interface, "handle-set-visible",
G_CALLBACK(handle_set_visible), service);
if (priv->connection && priv->object_path) {
GError *error = NULL;
if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(priv->dbus_interface),
priv->connection,
priv->object_path,
&error)) {
g_warning("Error registering dbus object: %s\n", error->message);
g_clear_error(&error);
}
}
}
static void
eekboard_service_class_init (EekboardServiceClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
klass->create_context = NULL;
gobject_class->constructed = eekboard_service_constructed;
gobject_class->set_property = eekboard_service_set_property;
gobject_class->get_property = eekboard_service_get_property;
gobject_class->dispose = eekboard_service_dispose;
gobject_class->finalize = eekboard_service_finalize;
/**
* EekboardService::destroyed:
* @service: an #EekboardService
*
* The ::destroyed signal is emitted when the service is vanished.
*/
signals[DESTROYED] =
g_signal_new (I_("destroyed"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* EekboardService:object-path:
*
* D-Bus object path.
*/
pspec = g_param_spec_string ("object-path",
"Object-path",
"Object-path",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_OBJECT_PATH,
pspec);
/**
* EekboardService:connection:
*
* D-Bus connection.
*/
pspec = g_param_spec_object ("connection",
"Connection",
"Connection",
G_TYPE_DBUS_CONNECTION,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_CONNECTION,
pspec);
}
static void
eekboard_service_init (EekboardService *self)
{
EekboardServicePrivate *priv = eekboard_service_get_instance_private (self);
priv->context = NULL;
}
/**
* eekboard_service_new:
* @connection: a #GDBusConnection
* @object_path: object path
*/
EekboardService *
eekboard_service_new (GDBusConnection *connection,
const gchar *object_path)
{
return g_object_new (EEKBOARD_TYPE_SERVICE,
"object-path", object_path,
"connection", connection,
NULL);
}
void
eekboard_service_set_context(EekboardService *service,
EekboardContextService *context)
{
EekboardServicePrivate *priv = eekboard_service_get_instance_private (service);
g_return_if_fail (!priv->context);
priv->context = context;
g_signal_connect_swapped (priv->context,
"notify::visible",
G_CALLBACK(on_visible),
service);
}

View File

@ -1,55 +0,0 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EEKBOARD_SERVICE_H
#define EEKBOARD_SERVICE_H 1
#define __EEKBOARD_SERVICE_H_INSIDE__ 1
#include "eekboard/eekboard-context-service.h"
G_BEGIN_DECLS
#define EEKBOARD_SERVICE_PATH "/sm/puri/OSK0"
#define EEKBOARD_SERVICE_INTERFACE "sm.puri.OSK0"
#define EEKBOARD_TYPE_SERVICE (eekboard_service_get_type())
G_DECLARE_DERIVABLE_TYPE (EekboardService, eekboard_service, EEKBOARD, SERVICE, GObject)
/**
* EekboardServiceClass:
* @create_context: virtual function for creating a context
*/
struct _EekboardServiceClass {
/*< private >*/
GObjectClass parent_class;
/*< public >*/
EekboardContextService *(*create_context) (EekboardService *self);
/*< private >*/
/* padding */
gpointer pdummy[24];
};
GType eekboard_service_get_type (void) G_GNUC_CONST;
EekboardService * eekboard_service_new (GDBusConnection *connection,
const gchar *object_path);
void eekboard_service_set_context(EekboardService *service,
EekboardContextService *context);
G_END_DECLS
#endif /* EEKBOARD_SERVICE_H */

View File

@ -1,136 +0,0 @@
/*
* Copyright (C) 2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2019 Purism, SPC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* This file is responsible for managing keycode data and emitting keycodes. */
#include "eekboard/key-emitter.h"
#include <gdk/gdk.h>
#include <X11/XKBlib.h>
#include "eekboard/eekboard-context-service.h"
// TODO: decide whether it's this struct that carries the keyboard around in key-emitter or if the whole manager should be dragged around
// if this is the carrier, then it should be made part of the manager
// hint: check which fields need to be persisted between keypresses; which between keyboards
typedef struct {
struct zwp_virtual_keyboard_v1 *virtual_keyboard; // unowned copy
struct xkb_keymap *keymap; // unowned copy
XkbDescRec *xkb;
guint modifier_keycodes[8];
guint modifier_indices[MOD_IDX_LAST];
guint group;
} SeatEmitter;
int send_virtual_keyboard_key(
struct zwp_virtual_keyboard_v1 *keyboard,
unsigned int keycode,
unsigned is_press,
uint32_t timestamp
) {
zwp_virtual_keyboard_v1_key(keyboard, timestamp, keycode, (unsigned)is_press);
return 0;
}
/* Finds the first key code for each modifier and saves it in modifier_keycodes */
static void
update_modifier_info (SeatEmitter *client)
{
client->modifier_indices[MOD_IDX_SHIFT] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_SHIFT);
client->modifier_indices[MOD_IDX_CAPS] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_CAPS);
client->modifier_indices[MOD_IDX_CTRL] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_CTRL);
client->modifier_indices[MOD_IDX_ALT] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_ALT);
client->modifier_indices[MOD_IDX_NUM] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_NUM);
client->modifier_indices[MOD_IDX_MOD3] = xkb_keymap_mod_get_index(client->keymap, "Mod3");
client->modifier_indices[MOD_IDX_LOGO] = xkb_keymap_mod_get_index(client->keymap, XKB_MOD_NAME_LOGO);
client->modifier_indices[MOD_IDX_ALTGR] = xkb_keymap_mod_get_index(client->keymap, "Mod5");
client->modifier_indices[MOD_IDX_NUMLK] = xkb_keymap_mod_get_index(client->keymap, "NumLock");
client->modifier_indices[MOD_IDX_ALSO_ALT] = xkb_keymap_mod_get_index(client->keymap, "Alt");
client->modifier_indices[MOD_IDX_LVL3] = xkb_keymap_mod_get_index(client->keymap, "LevelThree");
client->modifier_indices[MOD_IDX_LALT] = xkb_keymap_mod_get_index(client->keymap, "LAlt");
client->modifier_indices[MOD_IDX_RALT] = xkb_keymap_mod_get_index(client->keymap, "RAlt");
client->modifier_indices[MOD_IDX_RCONTROL] = xkb_keymap_mod_get_index(client->keymap, "RControl");
client->modifier_indices[MOD_IDX_LCONTROL] = xkb_keymap_mod_get_index(client->keymap, "LControl");
client->modifier_indices[MOD_IDX_SCROLLLK] = xkb_keymap_mod_get_index(client->keymap, "ScrollLock");
client->modifier_indices[MOD_IDX_LVL5] = xkb_keymap_mod_get_index(client->keymap, "LevelFive");
client->modifier_indices[MOD_IDX_ALSO_ALTGR] = xkb_keymap_mod_get_index(client->keymap, "AltGr");
client->modifier_indices[MOD_IDX_META] = xkb_keymap_mod_get_index(client->keymap, "Meta");
client->modifier_indices[MOD_IDX_SUPER] = xkb_keymap_mod_get_index(client->keymap, "Super");
client->modifier_indices[MOD_IDX_HYPER] = xkb_keymap_mod_get_index(client->keymap, "Hyper");
/*
for (xkb_mod_index_t i = 0;
i < xkb_keymap_num_mods(client->keymap);
i++) {
g_log("squeek", G_LOG_LEVEL_DEBUG, "%s", xkb_keymap_mod_get_name(client->keymap, i));
}*/
}
static void
send_fake_key (SeatEmitter *emitter,
LevelKeyboard *keyboard,
guint keycode,
gboolean pressed,
uint32_t timestamp)
{
zwp_virtual_keyboard_v1_modifiers(emitter->virtual_keyboard, 0, 0, 0, 0);
send_virtual_keyboard_key (emitter->virtual_keyboard, keycode - 8, (unsigned)pressed, timestamp);
zwp_virtual_keyboard_v1_modifiers(emitter->virtual_keyboard, 0, 0, 0, 0);
}
void
emit_key_activated (EekboardContextService *manager,
LevelKeyboard *keyboard,
guint keycode,
gboolean pressed,
uint32_t timestamp)
{
/* FIXME: figure out how to deal with Client after key presses go through
if (g_strcmp0 (eek_symbol_get_name (symbol), "cycle-keyboard") == 0) {
client->keyboards_head = g_slist_next (client->keyboards_head);
if (client->keyboards_head == NULL)
client->keyboards_head = client->keyboards;
eekboard_context_set_keyboard (client->context,
GPOINTER_TO_UINT(client->keyboards_head->data),
NULL);
return;
}
if (g_strcmp0 (eek_symbol_get_name (symbol), "preferences") == 0) {
gchar *argv[2];
GError *error;
argv[0] = g_build_filename (LIBEXECDIR, "eekboard-setup", NULL);
argv[1] = NULL;
error = NULL;
if (!g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, NULL, &error)) {
g_warning ("can't spawn %s: %s", argv[0], error->message);
g_error_free (error);
}
g_free (argv[0]);
return;
}
*/
SeatEmitter emitter = {0};
emitter.virtual_keyboard = manager->virtual_keyboard;
update_modifier_info (&emitter);
send_fake_key (&emitter, keyboard, keycode, pressed, timestamp);
}

View File

@ -1,45 +0,0 @@
#ifndef KEYEMITTER_H
#define KEYEMITTER_H
#include <inttypes.h>
#include <glib.h>
#include "eek/eek.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
/// Indices obtained by xkb_keymap_mod_get_name
enum mod_indices {
MOD_IDX_SHIFT,
MOD_IDX_CAPS,
MOD_IDX_CTRL,
MOD_IDX_ALT,
MOD_IDX_NUM,
MOD_IDX_MOD3,
MOD_IDX_LOGO,
MOD_IDX_ALTGR,
MOD_IDX_NUMLK, // Caution, not sure which is the right one
MOD_IDX_ALSO_ALT, // Not sure why, alt emits the first alt on my setup
MOD_IDX_LVL3,
// Not sure if the next 4 are used at all
MOD_IDX_LALT,
MOD_IDX_RALT,
MOD_IDX_RCONTROL,
MOD_IDX_LCONTROL,
MOD_IDX_SCROLLLK,
MOD_IDX_LVL5,
MOD_IDX_ALSO_ALTGR, // Not used on my layout
MOD_IDX_META,
MOD_IDX_SUPER,
MOD_IDX_HYPER,
MOD_IDX_LAST,
};
void
emit_key_activated (EekboardContextService *manager, LevelKeyboard *keyboard,
guint keycode,
gboolean pressed, uint32_t timestamp);
#endif // KEYEMITTER_H

View File

@ -6,8 +6,8 @@ use std::ffi::CString;
#[derive(Debug, Clone, PartialEq)]
pub struct KeySym(pub String);
/// Use to switch layouts
type Level = String;
/// Use to switch views
type View = String;
/// Use to send modified keypresses
#[derive(Debug, Clone, PartialEq)]
@ -20,12 +20,12 @@ pub enum Modifier {
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
/// Switch to this view
SetLevel(Level),
SetView(View),
/// Switch to a view and latch
LockLevel {
lock: Level,
LockView {
lock: View,
/// When unlocked by pressing it or emitting a key
unlock: Level,
unlock: View,
},
/// Set this modifier TODO: release?
SetModifier(Modifier),

View File

@ -21,7 +21,7 @@ use ::keyboard::{
};
use ::layout;
use ::layout::ArrangementKind;
use ::logging::PrintWarnings;
use ::logging;
use ::resources;
use ::util::c::as_str;
use ::util::hash_map_map;
@ -31,7 +31,7 @@ use ::xdg;
use serde::Deserialize;
use std::io::BufReader;
use std::iter::FromIterator;
use ::logging::WarningHandler;
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
@ -157,7 +157,7 @@ fn list_layout_sources(
fn load_layout_data(source: DataSource)
-> Result<::layout::LayoutData, LoadError>
{
let handler = PrintWarnings{};
let handler = logging::Print {};
match source {
DataSource::File(path) => {
Layout::from_file(path.clone())
@ -190,16 +190,21 @@ fn load_layout_data_with_fallback(
(
LoadError::BadData(Error::Missing(e)),
DataSource::File(file)
) => eprintln!( // TODO: print in debug logging level
) => log_print!(
logging::Level::Debug,
"Tried file {:?}, but it's missing: {}",
file, e
),
(e, source) => eprintln!(
(e, source) => log_print!(
logging::Level::Warning,
"Failed to load layout from {}: {}, skipping",
source, e
),
},
Ok(layout) => return (kind, layout),
Ok(layout) => {
log_print!(logging::Level::Info, "Loaded layout {}", source);
return (kind, layout);
}
}
}
@ -330,7 +335,7 @@ impl Layout {
serde_yaml::from_reader(infile).map_err(Error::Yaml)
}
pub fn build<H: WarningHandler>(self, mut warning_handler: H)
pub fn build<H: logging::Handler>(self, mut warning_handler: H)
-> (Result<::layout::LayoutData, FormattingError>, H)
{
let button_names = self.views.values()
@ -464,7 +469,7 @@ impl Layout {
}
}
fn create_action<H: WarningHandler>(
fn create_action<H: logging::Handler>(
button_info: &HashMap<String, ButtonMeta>,
name: &str,
view_names: Vec<&String>,
@ -494,15 +499,18 @@ fn create_action<H: WarningHandler>(
(None, None, Some(text)) => SubmitData::Text(text.clone()),
(None, None, None) => SubmitData::Text(name.into()),
_ => {
warning_handler.handle(&format!(
"Button {} has more than one of (action, keysym, text)",
name
));
warning_handler.handle(
logging::Level::Warning,
&format!(
"Button {} has more than one of (action, keysym, text)",
name,
),
);
SubmitData::Text("".into())
},
};
fn filter_view_name<H: WarningHandler>(
fn filter_view_name<H: logging::Handler>(
button_name: &str,
view_name: String,
view_names: &Vec<&String>,
@ -511,10 +519,13 @@ fn create_action<H: WarningHandler>(
if view_names.contains(&&view_name) {
view_name
} else {
warning_handler.handle(&format!("Button {} switches to missing view {}",
button_name,
view_name,
));
warning_handler.handle(
logging::Level::Warning,
&format!("Button {} switches to missing view {}",
button_name,
view_name,
),
);
"base".into()
}
}
@ -522,7 +533,7 @@ fn create_action<H: WarningHandler>(
match submission {
SubmitData::Action(
Action::SetView(view_name)
) => ::action::Action::SetLevel(
) => ::action::Action::SetView(
filter_view_name(
name, view_name.clone(), &view_names,
warning_handler,
@ -530,7 +541,7 @@ fn create_action<H: WarningHandler>(
),
SubmitData::Action(Action::Locking {
lock_view, unlock_view
}) => ::action::Action::LockLevel {
}) => ::action::Action::LockView {
lock: filter_view_name(
name,
lock_view.clone(),
@ -553,27 +564,24 @@ fn create_action<H: WarningHandler>(
match keysym_valid(keysym.as_str()) {
true => keysym.clone(),
false => {
warning_handler.handle(&format!(
"Keysym name invalid: {}",
keysym,
));
warning_handler.handle(
logging::Level::Warning,
&format!(
"Keysym name invalid: {}",
keysym,
),
);
"space".into() // placeholder
},
}
)),
},
SubmitData::Text(text) => ::action::Action::Submit {
text: {
CString::new(text.clone())
.map_err(|e| {
warning_handler.handle(&format!(
"Text {} contains problems: {:?}",
text,
e
));
e
}).ok()
},
text: CString::new(text.clone()).or_warn(
warning_handler,
logging::Problem::Warning,
&format!("Text {} contains problems", text),
),
keys: text.chars().map(|codepoint| {
let codepoint_string = codepoint.to_string();
::action::KeySym(match keysym_valid(codepoint_string.as_str()) {
@ -587,7 +595,7 @@ fn create_action<H: WarningHandler>(
/// TODO: Since this will receive user-provided data,
/// all .expect() on them should be turned into soft fails
fn create_button<H: WarningHandler>(
fn create_button<H: logging::Handler>(
button_info: &HashMap<String, ButtonMeta>,
outlines: &HashMap<String, Outline>,
name: &str,
@ -611,14 +619,11 @@ fn create_button<H: WarningHandler>(
} else if let Some(text) = &button_meta.text {
::layout::Label::Text(
CString::new(text.as_str())
.unwrap_or_else(|e| {
warning_handler.handle(&format!(
"Text {} is invalid: {}",
text,
e,
));
CString::new("").unwrap()
})
.or_warn(
warning_handler,
logging::Problem::Warning,
&format!("Text {} is invalid", text),
).unwrap_or_else(|| CString::new("").unwrap())
)
} else {
::layout::Label::Text(cname.clone())
@ -629,7 +634,10 @@ fn create_button<H: WarningHandler>(
if outlines.contains_key(outline) {
outline.clone()
} else {
warning_handler.handle(&format!("Outline named {} does not exist! Using default for button {}", outline, name));
warning_handler.handle(
logging::Level::Warning,
&format!("Outline named {} does not exist! Using default for button {}", outline, name)
);
"default".into()
}
}
@ -638,12 +646,11 @@ fn create_button<H: WarningHandler>(
let outline = outlines.get(&outline_name)
.map(|outline| (*outline).clone())
.unwrap_or_else(|| {
warning_handler.handle(
&format!("No default outline defined! Using 1x1!")
);
Outline { width: 1f64, height: 1f64 }
});
.or_warn(
warning_handler,
logging::Problem::Warning,
"No default outline defined! Using 1x1!",
).unwrap_or(Outline { width: 1f64, height: 1f64 });
layout::Button {
name: cname,
@ -663,7 +670,7 @@ mod tests {
use super::*;
use std::error::Error as ErrorTrait;
use ::logging::PanicWarn;
use ::logging::ProblemPanic;
#[test]
fn test_parse_path() {
@ -733,7 +740,7 @@ mod tests {
fn test_layout_punctuation() {
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
.unwrap()
.build(PanicWarn).0
.build(ProblemPanic).0
.unwrap();
assert_eq!(
out.views["base"]
@ -748,7 +755,7 @@ mod tests {
fn test_layout_unicode() {
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
.unwrap()
.build(PanicWarn).0
.build(ProblemPanic).0
.unwrap();
assert_eq!(
out.views["base"]
@ -764,7 +771,7 @@ mod tests {
fn test_layout_unicode_multi() {
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
.unwrap()
.build(PanicWarn).0
.build(ProblemPanic).0
.unwrap();
assert_eq!(
out.views["base"]
@ -779,7 +786,7 @@ mod tests {
#[test]
fn parsing_fallback() {
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
.map(|layout| layout.build(PanicWarn).0.unwrap())
.map(|layout| layout.build(ProblemPanic).0.unwrap())
.is_ok()
);
}
@ -827,7 +834,7 @@ mod tests {
},
".",
Vec::new(),
&mut PanicWarn,
&mut ProblemPanic,
),
::action::Action::Submit {
text: Some(CString::new(".").unwrap()),
@ -840,7 +847,7 @@ mod tests {
fn test_layout_margins() {
let out = Layout::from_file(PathBuf::from("tests/layout_margins.yaml"))
.unwrap()
.build(PanicWarn).0
.build(ProblemPanic).0
.unwrap();
assert_eq!(
out.margins,

124
src/dbus.c Normal file
View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "dbus.h"
#include <stdio.h>
#include <gio/gio.h>
void
dbus_handler_destroy(DBusHandler *service)
{
g_free (service->object_path);
if (service->connection) {
if (service->registration_id > 0) {
g_dbus_connection_unregister_object (service->connection,
service->registration_id);
service->registration_id = 0;
}
g_object_unref (service->connection);
service->connection = NULL;
}
if (service->introspection_data) {
g_dbus_node_info_unref (service->introspection_data);
service->introspection_data = NULL;
}
if (service->context) {
g_signal_handlers_disconnect_by_data (service->context, service);
service->context = NULL;
}
free(service);
}
static gboolean
handle_set_visible(SmPuriOSK0 *object, GDBusMethodInvocation *invocation,
gboolean arg_visible, gpointer user_data) {
DBusHandler *service = user_data;
if (service->context) {
if (arg_visible) {
server_context_service_show_keyboard (service->context);
} else {
server_context_service_hide_keyboard (service->context);
}
}
sm_puri_osk0_complete_set_visible(object, invocation);
return TRUE;
}
static void on_visible(DBusHandler *service,
GParamSpec *pspec,
ServerContextService *context)
{
(void)pspec;
gboolean visible;
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (context));
g_object_get (context, "visible", &visible, NULL);
sm_puri_osk0_set_visible(service->dbus_interface, visible);
}
DBusHandler *
dbus_handler_new (GDBusConnection *connection,
const gchar *object_path)
{
DBusHandler *self = calloc(1, sizeof(DBusHandler));
self->object_path = g_strdup(object_path);
self->connection = connection;
self->dbus_interface = sm_puri_osk0_skeleton_new();
g_signal_connect(self->dbus_interface, "handle-set-visible",
G_CALLBACK(handle_set_visible), self);
if (self->connection && self->object_path) {
GError *error = NULL;
if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(self->dbus_interface),
self->connection,
self->object_path,
&error)) {
g_warning("Error registering dbus object: %s\n", error->message);
g_clear_error(&error);
// TODO: return an error
}
}
return self;
}
void
dbus_handler_set_ui_context(DBusHandler *service,
ServerContextService *context)
{
g_return_if_fail (!service->context);
service->context = context;
g_signal_connect_swapped (service->context,
"notify::visible",
G_CALLBACK(on_visible),
service);
}

48
src/dbus.h Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
* Copyright (C) 2019-2020 Purism, SPC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DBUS_H_
#define DBUS_H_ 1
#include "server-context-service.h"
#include "sm.puri.OSK0.h"
G_BEGIN_DECLS
#define DBUS_SERVICE_PATH "/sm/puri/OSK0"
#define DBUS_SERVICE_INTERFACE "sm.puri.OSK0"
typedef struct _DBusHandler
{
GDBusConnection *connection;
SmPuriOSK0 *dbus_interface;
GDBusNodeInfo *introspection_data;
guint registration_id;
char *object_path;
ServerContextService *context; // unowned reference
} DBusHandler;
DBusHandler * dbus_handler_new (GDBusConnection *connection,
const gchar *object_path);
void dbus_handler_set_ui_context(DBusHandler *service,
ServerContextService *context);
void dbus_handler_destroy(DBusHandler*);
G_END_DECLS
#endif /* DBUS_H_ */

View File

@ -1,9 +1,17 @@
#include "imservice.h"
#include "submission.h"
#include <glib.h>
#include "eekboard/eekboard-context-service.h"
struct imservice;
void imservice_handle_input_method_activate(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_input_method_deactivate(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_surrounding_text(void *data, struct zwp_input_method_v2 *input_method,
const char *text, uint32_t cursor, uint32_t anchor);
void imservice_handle_done(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_content_type(void *data, struct zwp_input_method_v2 *input_method, uint32_t hint, uint32_t purpose);
void imservice_handle_text_change_cause(void *data, struct zwp_input_method_v2 *input_method, uint32_t cause);
void imservice_handle_unavailable(void *data, struct zwp_input_method_v2 *input_method);
static const struct zwp_input_method_v2_listener input_method_listener = {
.activate = imservice_handle_input_method_activate,
@ -11,29 +19,34 @@ static const struct zwp_input_method_v2_listener input_method_listener = {
.surrounding_text = imservice_handle_surrounding_text,
.text_change_cause = imservice_handle_text_change_cause,
.content_type = imservice_handle_content_type,
.done = imservice_handle_commit_state,
.done = imservice_handle_done,
.unavailable = imservice_handle_unavailable,
};
struct imservice* get_imservice(EekboardContextService *context,
struct zwp_input_method_manager_v2 *manager,
struct wl_seat *seat) {
struct zwp_input_method_v2 *im = zwp_input_method_manager_v2_get_input_method(manager, seat);
struct imservice *imservice = imservice_new(im, context);
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat,
EekboardContextService *state) {
struct zwp_input_method_v2 *im = NULL;
if (immanager) {
im = zwp_input_method_manager_v2_get_input_method(immanager, seat);
}
struct zwp_virtual_keyboard_v1 *vk = NULL;
if (vkmanager) {
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmanager, seat);
}
return submission_new(im, vk, state);
}
/* Add a listener, passing the imservice instance to make it available to
callbacks. */
/// Un-inlined
struct zwp_input_method_v2 *imservice_manager_get_input_method(struct zwp_input_method_manager_v2 *manager,
struct wl_seat *seat) {
return zwp_input_method_manager_v2_get_input_method(manager, seat);
}
/// Un-inlined to let Rust link to it
void imservice_connect_listeners(struct zwp_input_method_v2 *im, struct imservice* imservice) {
zwp_input_method_v2_add_listener(im, &input_method_listener, imservice);
return imservice;
}
void imservice_make_visible(EekboardContextService *context) {
eekboard_context_service_show_keyboard (context);
}
void imservice_try_hide(EekboardContextService *context) {
eekboard_context_service_hide_keyboard (context);
}
/// Declared explicitly because _destroy is inline,

View File

@ -1,25 +0,0 @@
#ifndef __IMSERVICE_H
#define __IMSERVICE_H
#include "input-method-unstable-v2-client-protocol.h"
#include "eek/eek-types.h"
struct imservice;
struct imservice* get_imservice(EekboardContextService *context,
struct zwp_input_method_manager_v2 *manager,
struct wl_seat *seat);
// Defined in Rust
struct imservice* imservice_new(struct zwp_input_method_v2 *im,
EekboardContextService *context);
void imservice_handle_input_method_activate(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_input_method_deactivate(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_surrounding_text(void *data, struct zwp_input_method_v2 *input_method,
const char *text, uint32_t cursor, uint32_t anchor);
void imservice_handle_commit_state(void *data, struct zwp_input_method_v2 *input_method);
void imservice_handle_content_type(void *data, struct zwp_input_method_v2 *input_method, uint32_t hint, uint32_t purpose);
void imservice_handle_text_change_cause(void *data, struct zwp_input_method_v2 *input_method, uint32_t cause);
void imservice_handle_unavailable(void *data, struct zwp_input_method_v2 *input_method);
#endif

View File

@ -1,58 +1,47 @@
use std::boxed::Box;
use std::ffi::CString;
use std::fmt;
use std::num::Wrapping;
use std::string::String;
use ::logging;
use ::util::c::into_cstring;
// Traits
use std::convert::TryFrom;
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::{c_char, c_void};
pub use ::submission::c::UIManager;
pub use ::submission::c::StateManager;
// The following defined in C
/// struct zwp_input_method_v2*
#[repr(transparent)]
pub struct InputMethod(*const c_void);
/// EekboardContextService*
#[repr(transparent)]
pub struct UIManager(*const c_void);
#[no_mangle]
extern "C" {
fn imservice_destroy_im(im: *mut c::InputMethod);
fn eekboard_context_service_set_hint_purpose(imservice: *const UIManager, hint: u32, purpose: u32);
fn eekboard_context_service_show_keyboard(imservice: *const UIManager);
fn eekboard_context_service_hide_keyboard(imservice: *const UIManager);
#[allow(improper_ctypes)] // IMService will never be dereferenced in C
pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService);
fn eekboard_context_service_set_hint_purpose(state: *const StateManager, hint: u32, purpose: u32);
fn server_context_service_show_keyboard(imservice: *const UIManager);
fn server_context_service_hide_keyboard(imservice: *const UIManager);
}
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
#[no_mangle]
pub unsafe extern "C"
fn imservice_new(im: *const InputMethod, ui_manager: *const UIManager) -> *mut IMService {
Box::<IMService>::into_raw(Box::new(
IMService {
im: im,
ui_manager: ui_manager,
pending: IMProtocolState::default(),
current: IMProtocolState::default(),
preedit_string: String::new(),
serial: Wrapping(0u32),
}
))
}
// TODO: is unsafe needed here?
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_input_method_activate(imservice: *mut IMService,
im: *const InputMethod)
{
@ -65,7 +54,7 @@ pub mod c {
}
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_input_method_deactivate(imservice: *mut IMService,
im: *const InputMethod)
{
@ -77,7 +66,7 @@ pub mod c {
}
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_surrounding_text(imservice: *mut IMService,
im: *const InputMethod,
text: *const c_char, cursor: u32, _anchor: u32)
@ -93,7 +82,7 @@ pub mod c {
}
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_content_type(imservice: *mut IMService,
im: *const InputMethod,
hint: u32, purpose: u32)
@ -101,23 +90,27 @@ pub mod c {
let imservice = check_imservice(imservice, im).unwrap();
imservice.pending = IMProtocolState {
content_hint: {
ContentHint::from_bits(hint).unwrap_or_else(|| {
eprintln!("Warning: received invalid hint flags");
ContentHint::NONE
})
ContentHint::from_bits(hint)
.or_print(
logging::Problem::Warning,
"Received invalid hint flags",
)
.unwrap_or(ContentHint::NONE)
},
content_purpose: {
ContentPurpose::try_from(purpose).unwrap_or_else(|_e| {
eprintln!("Warning: Received invalid purpose value");
ContentPurpose::Normal
})
ContentPurpose::try_from(purpose)
.or_print(
logging::Problem::Warning,
"Received invalid purpose value",
)
.unwrap_or(ContentPurpose::Normal)
},
..imservice.pending.clone()
};
}
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_text_change_cause(imservice: *mut IMService,
im: *const InputMethod,
cause: u32)
@ -125,56 +118,68 @@ pub mod c {
let imservice = check_imservice(imservice, im).unwrap();
imservice.pending = IMProtocolState {
text_change_cause: {
ChangeCause::try_from(cause).unwrap_or_else(|_e| {
eprintln!("Warning: received invalid cause value");
ChangeCause::InputMethod
})
ChangeCause::try_from(cause)
.or_print(
logging::Problem::Warning,
"Received invalid cause value",
)
.unwrap_or(ChangeCause::InputMethod)
},
..imservice.pending.clone()
};
}
#[no_mangle]
pub unsafe extern "C"
fn imservice_handle_commit_state(imservice: *mut IMService,
pub extern "C"
fn imservice_handle_done(imservice: *mut IMService,
im: *const InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
let active_changed = imservice.current.active ^ imservice.pending.active;
imservice.serial += Wrapping(1u32);
imservice.current = imservice.pending.clone();
imservice.pending = IMProtocolState {
active: imservice.current.active,
..IMProtocolState::default()
};
if active_changed {
if imservice.current.active {
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);
if let Some(ui) = imservice.ui_manager {
unsafe { server_context_service_show_keyboard(ui); }
}
unsafe {
eekboard_context_service_set_hint_purpose(
imservice.state_manager,
imservice.current.content_hint.bits(),
imservice.current.content_purpose.clone() as u32,
);
}
} else {
eekboard_context_service_hide_keyboard(imservice.ui_manager);
if let Some(ui) = imservice.ui_manager {
unsafe { server_context_service_hide_keyboard(ui); }
}
}
}
}
// TODO: this is really untested
#[no_mangle]
pub unsafe extern "C"
pub extern "C"
fn imservice_handle_unavailable(imservice: *mut IMService,
im: *mut InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
imservice_destroy_im(im);
unsafe { imservice_destroy_im(im); }
// no need to care about proper double-buffering,
// the keyboard is already decommissioned
imservice.current.active = false;
eekboard_context_service_hide_keyboard(imservice.ui_manager);
}
if let Some(ui) = imservice.ui_manager {
unsafe { server_context_service_hide_keyboard(ui); }
}
}
// FIXME: destroy and deallocate
@ -246,10 +251,17 @@ pub enum ContentPurpose {
Terminal = 13,
}
// Utilities from ::logging need a printable error type
pub struct UnrecognizedValue;
impl fmt::Display for UnrecognizedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unrecognized value")
}
}
impl TryFrom<u32> for ContentPurpose {
// There's only one way to fail: number not in protocol,
// so no special error type is needed
type Error = ();
type Error = UnrecognizedValue;
fn try_from(num: u32) -> Result<Self, Self::Error> {
use self::ContentPurpose::*;
match num {
@ -267,7 +279,7 @@ impl TryFrom<u32> for ContentPurpose {
11 => Ok(Time),
12 => Ok(Datetime),
13 => Ok(Terminal),
_ => Err(()),
_ => Err(UnrecognizedValue),
}
}
}
@ -280,14 +292,12 @@ pub enum ChangeCause {
}
impl TryFrom<u32> for ChangeCause {
// There's only one way to fail: number not in protocol,
// so no special error type is needed
type Error = ();
type Error = UnrecognizedValue;
fn try_from(num: u32) -> Result<Self, Self::Error> {
match num {
0 => Ok(ChangeCause::InputMethod),
1 => Ok(ChangeCause::Other),
_ => Err(())
_ => Err(UnrecognizedValue)
}
}
}
@ -320,10 +330,38 @@ pub struct IMService {
/// Owned reference (still created and destroyed in C)
pub im: *const c::InputMethod,
/// Unowned reference. Be careful, it's shared with C at large
ui_manager: *const c::UIManager,
state_manager: *const c::StateManager,
/// Unowned reference. Be careful, it's shared with C at large
pub ui_manager: Option<*const c::UIManager>,
pending: IMProtocolState,
current: IMProtocolState, // turn current into an idiomatic representation?
preedit_string: String,
serial: Wrapping<u32>,
}
impl IMService {
pub fn new(
im: *mut c::InputMethod,
state_manager: *const c::StateManager,
) -> Box<IMService> {
// IMService will be referenced to by C,
// so it needs to stay in the same place in memory via Box
let imservice = Box::new(IMService {
im,
ui_manager: None,
state_manager,
pending: IMProtocolState::default(),
current: IMProtocolState::default(),
preedit_string: String::new(),
serial: Wrapping(0u32),
});
unsafe {
c::imservice_connect_listeners(
im,
imservice.as_ref() as *const IMService,
);
}
imservice
}
}

View File

@ -7,6 +7,7 @@ use std::io;
use std::string::FromUtf8Error;
use ::action::Action;
use ::logging;
use std::io::Write;
use std::iter::{ FromIterator, IntoIterator };
@ -17,16 +18,39 @@ pub enum PressType {
Pressed = 1,
}
pub type KeyCode = u32;
#[derive(Debug, Clone)]
pub struct KeyState {
pub pressed: PressType,
pub locked: bool,
/// A cache of raw keycodes derived from Action::Sumbit given a keymap
pub keycodes: Vec<u32>,
pub keycodes: Vec<KeyCode>,
/// Static description of what the key does when pressed or released
pub action: Action,
}
impl KeyState {
#[must_use]
pub fn into_activated(self) -> KeyState {
match self.action {
Action::LockView { lock: _, unlock: _ } => KeyState {
locked: self.locked ^ true,
..self
},
_ => self,
}
}
#[must_use]
pub fn into_released(self) -> KeyState {
KeyState {
pressed: PressType::Released,
..self
}
}
}
/// Sorts an iterator by converting it to a Vector and back
fn sorted<'a, I: Iterator<Item=&'a str>>(
iter: I
@ -87,7 +111,12 @@ pub fn generate_keymap(
for (name, state) in keystates.iter() {
if let Action::Submit { text: _, keys } = &state.action {
if let 0 = keys.len() { eprintln!("Key {} has no keysyms", name); };
if let 0 = keys.len() {
log_print!(
logging::Level::Warning,
"Key {} has no keysyms", name,
);
};
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
write!(
buf,

View File

@ -33,17 +33,22 @@ const char *squeek_layout_get_keymap(const struct squeek_layout*);
enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *);
void squeek_layout_free(struct squeek_layout*);
void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
void squeek_layout_release(struct squeek_layout *layout,
struct submission *submission,
struct transformation widget_to_layout,
uint32_t timestamp,
EekboardContextService *manager,
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_depress(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
void squeek_layout_release_all_only(struct squeek_layout *layout,
struct submission *submission,
uint32_t timestamp);
void squeek_layout_depress(struct squeek_layout *layout,
struct submission *submission,
double x_widget, double y_widget,
struct transformation widget_to_layout,
uint32_t timestamp, EekGtkKeyboard *ui_keyboard);
void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
void squeek_layout_drag(struct squeek_layout *layout,
struct submission *submission,
double x_widget, double y_widget,
struct transformation widget_to_layout,
uint32_t timestamp, EekboardContextService *manager,

View File

@ -20,17 +20,21 @@
use std::cell::RefCell;
use std::collections::{ HashMap, HashSet };
use std::ffi::CString;
use std::fmt;
use std::rc::Rc;
use std::vec::Vec;
use ::action::Action;
use ::drawing;
use ::keyboard::{ KeyState, PressType };
use ::logging;
use ::manager;
use ::submission::{ Timestamp, VirtualKeyboard };
use ::submission::{ Submission, Timestamp };
use ::util::find_max_double;
// Traits
use std::borrow::Borrow;
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
@ -143,6 +147,11 @@ pub mod c {
}
}
}
// This is constructed only in C, no need for warnings
#[allow(dead_code)]
#[repr(transparent)]
pub struct LevelKeyboard(*const c_void);
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
@ -244,19 +253,12 @@ pub mod c {
pub mod procedures {
use super::*;
use ::submission::c::ZwpVirtualKeyboardV1;
// This is constructed only in C, no need for warnings
#[allow(dead_code)]
#[repr(transparent)]
pub struct LevelKeyboard(*const c_void);
/// Release pointer in the specified position
#[no_mangle]
pub extern "C"
fn squeek_layout_release(
layout: *mut Layout,
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
submission: *mut Submission,
widget_to_layout: Transformation,
time: u32,
manager: manager::c::Manager,
@ -264,42 +266,49 @@ pub mod c {
) {
let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
let submission = unsafe { &mut *submission };
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
};
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
submission,
Some(&ui_backend),
time,
ui_keyboard,
manager,
Some(manager),
key,
);
}
drawing::queue_redraw(ui_keyboard);
}
/// Release all buittons but don't redraw
/// Release all buttons but don't redraw
#[no_mangle]
pub extern "C"
fn squeek_layout_release_all_only(
layout: *mut Layout,
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
submission: *mut Submission,
time: u32,
) {
let layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
let submission = unsafe { &mut *submission };
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
layout.release_key(
&virtual_keyboard,
seat::handle_release_key(
layout,
submission,
None, // don't update UI
Timestamp(time),
None, // don't switch layouts
&mut key.clone(),
Timestamp(time)
);
}
}
@ -308,13 +317,14 @@ pub mod c {
pub extern "C"
fn squeek_layout_depress(
layout: *mut Layout,
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
submission: *mut Submission,
x_widget: f64, y_widget: f64,
widget_to_layout: Transformation,
time: u32,
ui_keyboard: EekGtkKeyboard,
) {
let layout = unsafe { &mut *layout };
let submission = unsafe { &mut *submission };
let point = widget_to_layout.forward(
Point { x: x_widget, y: y_widget }
);
@ -325,11 +335,12 @@ pub mod c {
.map(|place| place.button.state.clone())
};
if let Some(mut state) = state {
layout.press_key(
&VirtualKeyboard(virtual_keyboard),
&mut state,
if let Some(state) = state {
seat::handle_press_key(
layout,
submission,
Timestamp(time),
&state,
);
// maybe TODO: draw on the display buffer here
drawing::queue_redraw(ui_keyboard);
@ -343,7 +354,7 @@ pub mod c {
pub extern "C"
fn squeek_layout_drag(
layout: *mut Layout,
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
submission: *mut Submission,
x_widget: f64, y_widget: f64,
widget_to_layout: Transformation,
time: u32,
@ -352,9 +363,12 @@ pub mod c {
) {
let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
let point = widget_to_layout.forward(
let submission = unsafe { &mut *submission };
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
};
let point = ui_backend.widget_to_layout.forward(
Point { x: x_widget, y: y_widget }
);
@ -369,7 +383,7 @@ pub mod c {
)})
};
if let Some((mut state, _button, _view_position)) = button_info {
if let Some((state, _button, _view_position)) = button_info {
let mut found = false;
for wrapped_key in pressed {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
@ -378,17 +392,21 @@ pub mod c {
} else {
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
submission,
Some(&ui_backend),
time,
ui_keyboard,
manager,
Some(manager),
key,
);
}
}
if !found {
layout.press_key(&virtual_keyboard, &mut state, time);
seat::handle_press_key(
layout,
submission,
time,
&state,
);
// maybe TODO: draw on the display buffer here
}
} else {
@ -396,11 +414,10 @@ pub mod c {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
seat::handle_release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
submission,
Some(&ui_backend),
time,
ui_keyboard,
manager,
Some(manager),
key,
);
}
@ -612,8 +629,15 @@ pub struct LayoutData {
pub margins: Margins,
}
#[derive(Debug)]
struct NoSuchView;
impl fmt::Display for NoSuchView {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "No such view")
}
}
// Unfortunately, changes are not atomic due to mutability :(
// An error will not be recoverable
// The usage of &mut on Rc<RefCell<KeyState>> doesn't mean anything special.
@ -644,86 +668,6 @@ impl Layout {
}
}
fn release_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
mut key: &mut Rc<RefCell<KeyState>>,
time: Timestamp,
) {
if !self.pressed_keys.remove(&::util::Pointer(key.clone())) {
eprintln!("Warning: key {:?} was not pressed", key);
}
virtual_keyboard.switch(
&mut key.borrow_mut(),
PressType::Released,
time,
);
self.set_level_from_press(&mut key);
}
fn press_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
key: &mut Rc<RefCell<KeyState>>,
time: Timestamp,
) {
if !self.pressed_keys.insert(::util::Pointer(key.clone())) {
eprintln!("Warning: key {:?} was already pressed", key);
}
virtual_keyboard.switch(
&mut key.borrow_mut(),
PressType::Pressed,
time,
);
}
fn set_level_from_press(&mut self, key: &Rc<RefCell<KeyState>>) {
let keys = self.locked_keys.clone();
for key in &keys {
self.locked_keys.remove(key);
self.set_state_from_press(key.borrow());
}
// Don't handle the same key twice, but handle it at least once,
// because its press is the reason we're here
if !keys.contains(&::util::Pointer(key.clone())) {
self.set_state_from_press(key);
}
}
fn set_state_from_press(&mut self, key: &Rc<RefCell<KeyState>>) {
// Action should not hold a reference to key,
// because key is later borrowed for mutation. So action is cloned.
// RefCell::borrow() is covered up by (dyn Borrow)::borrow()
// if used like key.borrow() :(
let action = RefCell::borrow(key).action.clone();
let view_name = match action {
Action::SetLevel(name) => {
Some(name.clone())
},
Action::LockLevel { lock, unlock } => {
let locked = {
let mut key = key.borrow_mut();
key.locked ^= true;
key.locked
};
if locked {
self.locked_keys.insert(::util::Pointer(key.clone()));
}
Some(if locked { lock } else { unlock }.clone())
},
_ => None,
};
if let Some(view_name) = view_name {
if let Err(_e) = self.set_view(view_name.clone()) {
eprintln!("No such view: {}, ignoring switch", view_name)
};
};
}
/// Calculates size without margins
fn calculate_inner_size(&self) -> Size {
Size {
@ -848,44 +792,187 @@ mod procedures {
}
}
pub struct UIBackend {
widget_to_layout: c::Transformation,
keyboard: c::EekGtkKeyboard,
}
/// Top level procedures, dispatching to everything
mod seat {
use super::*;
// TODO: turn into release_button
pub fn handle_release_key(
layout: &mut Layout,
virtual_keyboard: &VirtualKeyboard,
widget_to_layout: &c::Transformation,
time: Timestamp,
ui_keyboard: c::EekGtkKeyboard,
manager: manager::c::Manager,
key: &Rc<RefCell<KeyState>>,
) {
layout.release_key(virtual_keyboard, &mut key.clone(), time);
let view = layout.get_current_view();
let action = RefCell::borrow(key).action.clone();
if let Action::ShowPreferences = action {
let places = ::layout::procedures::find_key_places(
view, key
fn try_set_view(layout: &mut Layout, view_name: String) {
layout.set_view(view_name.clone())
.or_print(
logging::Problem::Bug,
&format!("Bad view {}, ignoring", view_name),
);
// getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard
if let Some((offset, button)) = places.get(0) {
let bounds = c::Bounds {
x: offset.x, y: offset.y,
width: button.size.width,
height: button.size.height,
};
::popover::show(
ui_keyboard,
widget_to_layout.reverse_bounds(bounds),
manager,
);
}
/// A vessel holding an obligation to switch view.
/// Use with #[must_use]
struct ViewChange<'a> {
layout: &'a mut Layout,
view_name: Option<String>,
}
impl<'a> ViewChange<'a> {
fn choose_view(self, view_name: String) -> ViewChange<'a> {
ViewChange {
view_name: Some(view_name),
..self
}
}
fn apply(self) {
if let Some(name) = self.view_name {
try_set_view(self.layout, name);
}
}
}
/// Find all impermanent view changes and undo them in an arbitrary order.
/// Return an obligation to actually switch the view.
/// The final view is the "unlock" view
/// from one of the currently stuck keys.
// As long as only one stuck button is used, this should be fine.
// This is guaranteed because pressing a lock button unlocks all others.
// TODO: Make some broader guarantee about the resulting view,
// e.g. by maintaining a stack of stuck keys.
#[must_use]
fn unstick_locks(layout: &mut Layout) -> ViewChange {
let mut new_view = None;
for key in layout.locked_keys.clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
let mut key = RefCell::borrow_mut(key);
match &key.action {
Action::LockView { lock: _, unlock: view } => {
new_view = Some(view.clone());
},
a => log_print!(
logging::Level::Bug,
"Non-locking action {:?} was found inside locked keys",
a,
),
};
key.locked = false;
}
ViewChange {
layout,
view_name: new_view,
}
}
pub fn handle_press_key(
layout: &mut Layout,
submission: &mut Submission,
time: Timestamp,
rckey: &Rc<RefCell<KeyState>>,
) {
if !layout.pressed_keys.insert(::util::Pointer(rckey.clone())) {
log_print!(
logging::Level::Bug,
"Key {:?} was already pressed", rckey,
);
}
let mut key = rckey.borrow_mut();
submission.virtual_keyboard.switch(
&key.keycodes,
PressType::Pressed,
time,
);
key.pressed = PressType::Pressed;
}
pub fn handle_release_key(
layout: &mut Layout,
submission: &mut Submission,
ui: Option<&UIBackend>,
time: Timestamp,
manager: Option<manager::c::Manager>,
rckey: &Rc<RefCell<KeyState>>,
) {
let key: KeyState = {
RefCell::borrow(rckey).clone()
};
let action = key.action.clone();
// update
let key = key.into_released();
let key = match action {
Action::LockView { lock: _, unlock: _ } => key.into_activated(),
_ => key,
};
// process changes
match action {
Action::Submit { text: _, keys: _ } => {
unstick_locks(layout).apply();
submission.virtual_keyboard.switch(
&key.keycodes,
PressType::Released,
time,
);
},
Action::SetView(view) => {
try_set_view(layout, view)
},
Action::LockView { lock, unlock } => {
// The button that triggered this will be in the right state
// due to commit at the end.
unstick_locks(layout)
// It doesn't matter what the resulting view should be,
// it's getting changed anyway.
.choose_view(
match key.locked {
true => lock.clone(),
false => unlock.clone(),
}
)
.apply()
},
// only show when UI is present
Action::ShowPreferences => if let Some(ui) = &ui {
// only show when layout manager is available
if let Some(manager) = manager {
let view = layout.get_current_view();
let places = ::layout::procedures::find_key_places(
view, &rckey,
);
// Getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard.
if let Some((position, button)) = places.get(0) {
let bounds = c::Bounds {
x: position.x,
y: position.y,
width: button.size.width,
height: button.size.height,
};
::popover::show(
ui.keyboard,
ui.widget_to_layout.reverse_bounds(bounds),
manager,
);
}
}
},
Action::SetModifier(_) => log_print!(
logging::Level::Bug,
"Modifiers unsupported",
),
};
let pointer = ::util::Pointer(rckey.clone());
// Apply state changes
layout.pressed_keys.remove(&pointer);
if key.locked {
layout.locked_keys.insert(pointer);
} else {
layout.locked_keys.remove(&pointer);
}
// Commit activated button state changes
RefCell::replace(rckey, key);
}
}
@ -900,7 +987,7 @@ mod test {
pressed: PressType::Released,
locked: false,
keycodes: Vec::new(),
action: Action::SetLevel("default".into()),
action: Action::SetView("default".into()),
}))
}

View File

@ -15,6 +15,9 @@ extern crate regex;
extern crate serde;
extern crate xkbcommon;
#[macro_use]
mod logging;
mod action;
pub mod data;
mod drawing;
@ -24,13 +27,13 @@ mod keyboard;
mod layout;
mod locale;
mod locale_config;
mod logging;
mod manager;
mod outputs;
mod popover;
mod resources;
mod submission;
mod style;
mod submission;
pub mod tests;
pub mod util;
mod vkeyboard;
mod xdg;

View File

@ -8,6 +8,7 @@
use std::cmp;
use std::ffi::{ CStr, CString };
use std::fmt;
use std::os::raw::c_char;
use std::ptr;
use std::str::Utf8Error;
@ -47,6 +48,12 @@ pub enum Error {
NoInfo,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self, f)
}
}
pub struct XkbInfo(c::GnomeXkbInfo);
impl XkbInfo {

View File

@ -26,13 +26,15 @@
* 4. logging to an immutable destination type
*
* Same as above, except it can be parallelized.
* Logs being outputs, they get returned
* instead of being misleadingly passed back through arguments.
* 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;
use std::fmt::Display;
/// Levels are not in order.
pub enum Level {
@ -66,18 +68,84 @@ pub enum Level {
Debug,
}
/// Sugar for logging errors in results.
/// Approach 2.
pub trait Warn {
type Value;
fn or_warn(self, msg: &str) -> Option<Self::Value>;
impl Level {
fn as_str(&self) -> &'static str {
match self {
Level::Panic => "Panic",
Level::Bug => "Bug",
Level::Error => "Error",
Level::Warning => "Warning",
Level::Surprise => "Surprise",
Level::Info => "Info",
Level::Debug => "Debug",
}
}
}
impl<T, E: Error> Warn for Result<T, E> {
impl From<Problem> for Level {
fn from(problem: Problem) -> Level {
use self::Level::*;
match problem {
Problem::Panic => Panic,
Problem::Bug => Bug,
Problem::Error => Error,
Problem::Warning => Warning,
Problem::Surprise => Surprise,
}
}
}
/// Only levels which indicate problems
/// To use with `Result::Err` handlers,
/// which are needed only when something went off the optimal path.
/// A separate type ensures that `Err`
/// can't end up misclassified as a benign event like `Info`.
pub enum Problem {
Panic,
Bug,
Error,
Warning,
Surprise,
}
/// Sugar for approach 2
// TODO: avoid, deprecate.
// Handler instances should be long lived, not one per call.
macro_rules! log_print {
($level:expr, $($arg:tt)*) => (::logging::print($level, &format!($($arg)*)))
}
/// Approach 2
pub fn print(level: Level, message: &str) {
Print{}.handle(level, message)
}
/// Sugar for logging errors in results.
pub trait Warn where Self: Sized {
type Value;
/// Approach 2.
fn or_print(self, level: Problem, message: &str) -> Option<Self::Value> {
self.or_warn(&mut Print {}, level.into(), message)
}
/// Approach 3.
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<Self::Value>;
}
impl<T, E: Display> Warn for Result<T, E> {
type Value = T;
fn or_warn(self, msg: &str) -> Option<T> {
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<T> {
self.map_err(|e| {
eprintln!("{}: {}", msg, e);
handler.handle(level.into(), &format!("{}: {}", message, e));
e
}).ok()
}
@ -85,9 +153,14 @@ impl<T, E: Error> Warn for Result<T, E> {
impl<T> Warn for Option<T> {
type Value = T;
fn or_warn(self, msg: &str) -> Option<T> {
fn or_warn<H: Handler>(
self,
handler: &mut H,
level: Problem,
message: &str,
) -> Option<T> {
self.or_else(|| {
eprintln!("{}", msg);
handler.handle(level.into(), message);
None
})
}
@ -95,26 +168,34 @@ impl<T> Warn for Option<T> {
/// A mutable handler for text warnings.
/// Approach 3.
pub trait WarningHandler {
/// Handle a warning
fn handle(&mut self, warning: &str);
pub trait Handler {
/// Handle a log message
fn handle(&mut self, level: Level, message: &str);
}
/// Prints warnings to stderr
pub struct PrintWarnings;
/// Prints info to stdout, everything else to stderr
pub struct Print;
impl WarningHandler for PrintWarnings {
fn handle(&mut self, warning: &str) {
eprintln!("{}", warning);
impl Handler for Print {
fn handle(&mut self, level: Level, message: &str) {
match level {
Level::Info => println!("Info: {}", message),
l => eprintln!("{}: {}", l.as_str(), message),
}
}
}
/// Warning handler that will panic at any warning.
/// Warning handler that will panic
/// at any warning, error, surprise, bug, or panic.
/// Don't use except in tests
pub struct PanicWarn;
pub struct ProblemPanic;
impl WarningHandler for PanicWarn {
fn handle(&mut self, warning: &str) {
panic!("{}", warning);
impl Handler for ProblemPanic {
fn handle(&mut self, level: Level, message: &str) {
use self::Level::*;
match level {
Panic | Bug | Error | Warning | Surprise => panic!("{}", message),
l => Print{}.handle(l, message),
}
}
}

View File

@ -12,6 +12,7 @@ config_h = configure_file(
sources = [
config_h,
'dbus.c',
'imservice.c',
'server-context-service.c',
'wayland.c',
@ -21,12 +22,9 @@ sources = [
'../eek/eek-keyboard.c',
'../eek/eek-renderer.c',
'../eek/eek-types.c',
'../eek/eek-xml-layout.c',
'../eek/layersurface.c',
dbus_src,
'../eekboard/key-emitter.c',
'../eekboard/eekboard-context-service.c',
'../eekboard/eekboard-service.c',
# '../eekboard/eekboard-xklutil.c',
squeekboard_resources,
wl_proto_sources,

View File

@ -1,7 +1,10 @@
/*! Managing Wayland outputs */
use std::vec::Vec;
use ::logging;
// traits
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
@ -113,14 +116,11 @@ pub mod c {
_make: *const c_char, _model: *const c_char,
transform: i32,
) {
let transform = Transform::from_u32(transform as u32).unwrap_or_else(
|| {
eprintln!(
"Warning: received invalid wl_output.transform value"
);
Transform::Normal
}
);
let transform = Transform::from_u32(transform as u32)
.or_print(
logging::Problem::Warning,
"Received invalid wl_output.transform value",
).unwrap_or(Transform::Normal);
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
@ -129,7 +129,10 @@ pub mod c {
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.transform = Some(transform) },
None => eprintln!("Wayland error: Got mode on unknown output"),
None => log_print!(
logging::Level::Warning,
"Got geometry on unknown output",
),
};
}
@ -141,10 +144,12 @@ pub mod c {
height: i32,
_refresh: i32,
) {
let flags = Mode::from_bits(flags).unwrap_or_else(|| {
eprintln!("Warning: received invalid wl_output.mode flags");
Mode::NONE
});
let flags = Mode::from_bits(flags)
.or_print(
logging::Problem::Warning,
"Received invalid wl_output.mode flags",
).unwrap_or(Mode::NONE);
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
@ -156,7 +161,10 @@ pub mod c {
state.current_mode = Some(super::Mode { width, height});
}
},
None => eprintln!("Wayland error: Got mode on unknown output"),
None => log_print!(
logging::Level::Warning,
"Got mode on unknown output",
),
};
}
@ -169,7 +177,10 @@ pub mod c {
let output = find_output_mut(&mut collection, wl_output);
match output {
Some(output) => { output.current = output.pending.clone(); }
None => eprintln!("Wayland error: Got done on unknown output"),
None => log_print!(
logging::Level::Warning,
"Got done on unknown output",
),
};
}
@ -185,7 +196,10 @@ pub mod c {
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.scale = factor; }
None => eprintln!("Wayland error: Got done on unknown output"),
None => log_print!(
logging::Level::Warning,
"Got scale on unknown output",
),
};
}
@ -258,7 +272,10 @@ pub mod c {
}
},
_ => {
eprintln!("Not enough info registered on output");
log_print!(
logging::Level::Surprise,
"Not enough info received on output",
);
0
},
}

View File

@ -7,6 +7,7 @@ use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale;
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
use ::locale_config::system_locale;
use ::logging;
use ::manager;
use ::resources;
@ -221,14 +222,10 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
LayoutId::System { name, kind: _ } => {
xkb_translator.get_display_name(name)
.map(|s| Status::Translated(OwnedTranslation(s)))
.unwrap_or_else(|e| {
eprintln!(
"No display name for xkb layout {}: {:?}",
name,
e,
);
Status::Remaining(name.clone())
})
.or_print(
logging::Problem::Surprise,
&format!("No display name for xkb layout {}", name),
).unwrap_or_else(|| Status::Remaining(name.clone()))
},
LayoutId::Local(name) => Status::Remaining(name.clone()),
});
@ -242,10 +239,13 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
.as_ref()
.to_owned()
)
.or_warn("No locale detected")
.or_print(logging::Problem::Surprise, "No locale detected")
.and_then(|lang| {
resources::get_layout_names(lang.as_str())
.or_warn(&format!("No translations for locale {}", lang))
.or_print(
logging::Problem::Surprise,
&format!("No translations for locale {}", lang),
)
});
match builtin_translations {
@ -361,10 +361,10 @@ pub fn show(
match state {
Some(v) => {
v.get::<String>()
.or_else(|| {
eprintln!("Variant is not string: {:?}", v);
None
})
.or_print(
logging::Problem::Bug,
&format!("Variant is not string: {:?}", v)
)
.map(|state| {
let (_id, layout) = choices.iter()
.find(
@ -376,7 +376,10 @@ pub fn show(
)
});
},
None => eprintln!("No variant selected"),
None => log_print!(
logging::Level::Debug,
"No variant selected",
),
};
menu_inner.popdown();
});

View File

@ -23,24 +23,31 @@
#include "eek/eek.h"
#include "eek/eek-gtk-keyboard.h"
#include "eek/layersurface.h"
#include "eekboard/eekboard-context-service.h"
#include "submission.h"
#include "wayland.h"
#include "server-context-service.h"
enum {
PROP_0,
PROP_SIZE_CONSTRAINT_LANDSCAPE,
PROP_SIZE_CONSTRAINT_PORTRAIT,
PROP_VISIBLE,
PROP_LAST
};
typedef struct _ServerContextServiceClass ServerContextServiceClass;
struct _ServerContextService {
EekboardContextService parent;
GObject parent;
EekboardContextService *state; // unowned
/// Needed for instantiating the widget
struct submission *submission; // unowned
gboolean visible;
PhoshLayerSurface *window;
GtkWidget *widget;
GtkWidget *widget; // nullable
guint hiding;
guint last_requested_height;
enum squeek_arrangement_kind last_type;
@ -50,10 +57,10 @@ struct _ServerContextService {
};
struct _ServerContextServiceClass {
EekboardContextServiceClass parent_class;
GObjectClass parent_class;
};
G_DEFINE_TYPE (ServerContextService, server_context_service, EEKBOARD_TYPE_CONTEXT_SERVICE);
G_DEFINE_TYPE(ServerContextService, server_context_service, G_TYPE_OBJECT);
static void
on_destroy (GtkWidget *widget, gpointer user_data)
@ -65,7 +72,7 @@ on_destroy (GtkWidget *widget, gpointer user_data)
context->window = NULL;
context->widget = NULL;
eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
//eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
}
static void
@ -76,17 +83,6 @@ on_notify_keyboard (GObject *object,
GParamSpec *spec,
ServerContextService *context)
{
const LevelKeyboard *keyboard = eekboard_context_service_get_keyboard (EEKBOARD_CONTEXT_SERVICE(context));
if (!keyboard)
g_error("Programmer error: keyboard layout was unset!");
// The keymap will get set even if the window is hidden.
// It's not perfect,
// but simpler than adding a check in the window showing procedure
eekboard_context_service_set_keymap(EEKBOARD_CONTEXT_SERVICE(context),
keyboard);
/* Recreate the keyboard widget to keep in sync with the keymap. */
if (context->window)
make_widget(context);
@ -95,8 +91,8 @@ on_notify_keyboard (GObject *object,
g_object_get (context, "visible", &visible, NULL);
if (visible) {
eekboard_context_service_hide_keyboard(EEKBOARD_CONTEXT_SERVICE(context));
eekboard_context_service_show_keyboard(EEKBOARD_CONTEXT_SERVICE(context));
server_context_service_hide_keyboard(context);
server_context_service_show_keyboard(context);
}
}
@ -149,7 +145,7 @@ on_surface_configure(PhoshLayerSurface *surface, ServerContextService *context)
enum squeek_arrangement_kind new_type = get_type((uint32_t)width, (uint32_t)height);
if (context->last_type != new_type) {
context->last_type = new_type;
eekboard_context_service_update_layout(EEKBOARD_CONTEXT_SERVICE(context), context->last_type);
eekboard_context_service_update_layout(context->state, context->last_type);
}
guint desired_height = calculate_height(width);
@ -229,35 +225,15 @@ make_widget (ServerContextService *context)
context->widget = NULL;
}
LevelKeyboard *keyboard = eekboard_context_service_get_keyboard (EEKBOARD_CONTEXT_SERVICE(context));
LevelKeyboard *keyboard = eekboard_context_service_get_keyboard (context->state);
context->widget = eek_gtk_keyboard_new (keyboard);
context->widget = eek_gtk_keyboard_new (keyboard, context->state, context->submission);
gtk_widget_set_has_tooltip (context->widget, TRUE);
gtk_container_add (GTK_CONTAINER(context->window), context->widget);
gtk_widget_show (context->widget);
}
static void
server_context_service_real_show_keyboard (EekboardContextService *_context)
{
ServerContextService *context = SERVER_CONTEXT_SERVICE(_context);
if (context->hiding) {
g_source_remove (context->hiding);
context->hiding = 0;
}
if (!context->window)
make_window (context);
if (!context->widget)
make_widget (context);
EEKBOARD_CONTEXT_SERVICE_CLASS (server_context_service_parent_class)->
show_keyboard (_context);
gtk_widget_show (GTK_WIDGET(context->window));
}
static gboolean
on_hide (ServerContextService *context)
{
@ -268,20 +244,49 @@ on_hide (ServerContextService *context)
}
static void
server_context_service_real_hide_keyboard (EekboardContextService *_context)
server_context_service_real_show_keyboard (ServerContextService *context)
{
ServerContextService *context = SERVER_CONTEXT_SERVICE(_context);
if (context->hiding) {
g_source_remove (context->hiding);
context->hiding = 0;
}
if (!context->hiding)
context->hiding = g_timeout_add (200, (GSourceFunc) on_hide, context);
if (!context->window)
make_window (context);
if (!context->widget)
make_widget (context);
EEKBOARD_CONTEXT_SERVICE_CLASS (server_context_service_parent_class)->
hide_keyboard (_context);
context->visible = TRUE;
gtk_widget_show (GTK_WIDGET(context->window));
}
static void
server_context_service_real_destroyed (EekboardContextService *_context)
server_context_service_real_hide_keyboard (ServerContextService *context)
{
if (!context->hiding)
context->hiding = g_timeout_add (200, (GSourceFunc) on_hide, context);
context->visible = FALSE;
}
void
server_context_service_show_keyboard (ServerContextService *context)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(context));
if (!context->visible) {
server_context_service_real_show_keyboard (context);
}
}
void
server_context_service_hide_keyboard (ServerContextService *context)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(context));
if (context->visible) {
server_context_service_real_hide_keyboard (context);
}
}
static void
@ -306,6 +311,9 @@ server_context_service_set_property (GObject *object,
&context->size_constraint_portrait[0],
&context->size_constraint_portrait[1]);
break;
case PROP_VISIBLE:
context->visible = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -313,6 +321,23 @@ server_context_service_set_property (GObject *object,
}
}
static void
server_context_service_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ServerContextService *context = SERVER_CONTEXT_SERVICE(object);
switch (prop_id) {
case PROP_VISIBLE:
g_value_set_boolean (value, context->visible);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
server_context_service_dispose (GObject *object)
{
@ -327,15 +352,11 @@ server_context_service_dispose (GObject *object)
static void
server_context_service_class_init (ServerContextServiceClass *klass)
{
EekboardContextServiceClass *context_class = EEKBOARD_CONTEXT_SERVICE_CLASS(klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
context_class->show_keyboard = server_context_service_real_show_keyboard;
context_class->hide_keyboard = server_context_service_real_hide_keyboard;
context_class->destroyed = server_context_service_real_destroyed;
gobject_class->set_property = server_context_service_set_property;
gobject_class->get_property = server_context_service_get_property;
gobject_class->dispose = server_context_service_dispose;
pspec = g_param_spec_variant ("size-constraint-landscape",
@ -357,24 +378,39 @@ server_context_service_class_init (ServerContextServiceClass *klass)
g_object_class_install_property (gobject_class,
PROP_SIZE_CONSTRAINT_PORTRAIT,
pspec);
/**
* Flag to indicate if keyboard is visible or not.
*/
pspec = g_param_spec_boolean ("visible",
"Visible",
"Visible",
FALSE,
G_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_VISIBLE,
pspec);
}
static void
server_context_service_init (ServerContextService *context)
server_context_service_init (ServerContextService *state) {
(void)state;
}
ServerContextService *
server_context_service_new (EekboardContextService *state, struct submission *submission)
{
g_signal_connect (context,
ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
ui->submission = submission;
ui->state = state;
g_signal_connect (state,
"notify::keyboard",
G_CALLBACK(on_notify_keyboard),
context);
ui);
return ui;
}
EekboardContextService *
server_context_service_new ()
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *service)
{
return EEKBOARD_CONTEXT_SERVICE(g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL));
}
enum squeek_arrangement_kind server_context_service_get_layout_type(EekboardContextService *service)
{
return SERVER_CONTEXT_SERVICE(service)->last_type;
return service->last_type;
}

View File

@ -18,8 +18,8 @@
#ifndef SERVER_CONTEXT_SERVICE_H
#define SERVER_CONTEXT_SERVICE_H 1
#include "eekboard/eekboard-service.h"
#include "src/layout.h"
#include "src/submission.h"
G_BEGIN_DECLS
@ -30,12 +30,16 @@ G_BEGIN_DECLS
#define SERVER_IS_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SERVER_TYPE_CONTEXT_SERVICE))
#define SERVER_CONTEXT_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SERVER_TYPE_CONTEXT_SERVICE, ServerContextServiceClass))
/** Manages the liecycle of the window displaying layouts. */
/** Manages the lifecycle of the window displaying layouts. */
typedef struct _ServerContextService ServerContextService;
EekboardContextService *server_context_service_new ();
enum squeek_arrangement_kind server_context_service_get_layout_type(EekboardContextService*);
GType server_context_service_get_type
(void) G_GNUC_CONST;
ServerContextService *server_context_service_new(EekboardContextService *state, struct submission *submission);
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
void server_context_service_show_keyboard (ServerContextService *context);
void server_context_service_hide_keyboard (ServerContextService *context);
G_END_DECLS
#endif /* SERVER_CONTEXT_SERVICE_H */

View File

@ -25,10 +25,11 @@
#include "config.h"
#include "eekboard/eekboard-service.h"
#include "eek/eek.h"
#include "imservice.h"
#include "eekboard/eekboard-context-service.h"
#include "dbus.h"
#include "outputs.h"
#include "submission.h"
#include "server-context-service.h"
#include "wayland.h"
@ -38,8 +39,10 @@
/// Global application state
struct squeekboard {
struct squeek_wayland wayland;
EekboardContextService *context;
struct imservice *imservice;
DBusHandler *dbus_handler;
EekboardContextService *settings_context;
ServerContextService *ui_context;
struct submission *submission;
};
@ -64,31 +67,14 @@ on_name_lost (GDBusConnection *connection,
gpointer user_data)
{
// TODO: could conceivable continue working
// if intrnal changes stop sending dbus changes
(void)connection;
(void)name;
(void)user_data;
g_error("DBus unavailable, unclear how to continue.");
exit (1);
}
static void
on_destroyed (EekboardService *service,
gpointer user_data)
{
(void)service;
GMainLoop *loop = user_data;
g_main_loop_quit (loop);
}
static EekboardContextService *create_context() {
EekboardContextService *context = server_context_service_new ();
g_object_set_data_full (G_OBJECT(context),
"owner", g_strdup ("sender"),
(GDestroyNotify)g_free);
eekboard_context_service_enable (context);
return context;
}
// Wayland
static void
@ -209,7 +195,11 @@ main (int argc, char **argv)
exit(1);
}
instance.context = create_context();
if (!instance.wayland.input_method_manager) {
g_warning("Wayland input method interface not available");
}
instance.settings_context = eekboard_context_service_new();
// set up dbus
@ -257,17 +247,16 @@ main (int argc, char **argv)
break;
}
EekboardService *service = eekboard_service_new (connection, EEKBOARD_SERVICE_PATH);
DBusHandler *service = dbus_handler_new(connection, DBUS_SERVICE_PATH);
if (service == NULL) {
g_printerr ("Can't create dbus server\n");
exit (1);
} else {
eekboard_service_set_context(service, instance.context);
}
instance.dbus_handler = service;
guint owner_id = g_bus_own_name_on_connection (connection,
EEKBOARD_SERVICE_INTERFACE,
DBUS_SERVICE_INTERFACE,
G_BUS_NAME_OWNER_FLAGS_NONE,
on_name_acquired,
on_name_lost,
@ -278,24 +267,33 @@ main (int argc, char **argv)
exit (1);
}
struct imservice *imservice = NULL;
if (instance.wayland.input_method_manager) {
imservice = get_imservice(instance.context,
instance.wayland.input_method_manager,
instance.wayland.seat);
if (imservice) {
instance.imservice = imservice;
} else {
g_warning("Failed to register as an input method");
}
instance.submission = get_submission(instance.wayland.input_method_manager,
instance.wayland.virtual_keyboard_manager,
instance.wayland.seat,
instance.settings_context);
eekboard_context_service_set_submission(instance.settings_context, instance.submission);
ServerContextService *ui_context = server_context_service_new(
instance.settings_context,
instance.submission);
if (!ui_context) {
g_error("Could not initialize GUI");
exit(1);
}
instance.ui_context = ui_context;
if (instance.submission) {
submission_set_ui(instance.submission, instance.ui_context);
}
if (instance.dbus_handler) {
dbus_handler_set_ui_context(instance.dbus_handler, instance.ui_context);
}
eekboard_context_service_set_ui(instance.settings_context, instance.ui_context);
session_register();
GMainLoop *loop = g_main_loop_new (NULL, FALSE);
g_signal_connect (service, "destroyed", G_CALLBACK(on_destroyed), loop);
g_main_loop_run (loop);
g_bus_unown_name (owner_id);

View File

@ -19,6 +19,7 @@
/*! CSS data loading. */
use std::env;
use ::logging;
use glib::object::ObjectExt;
use logging::Warn;
@ -84,7 +85,10 @@ fn get_theme_name(settings: &gtk::Settings) -> GtkTheme {
match &e {
env::VarError::NotPresent => {},
// maybe TODO: forward this warning?
e => eprintln!("GTK_THEME variable invalid: {}", e),
e => log_print!(
logging::Level::Surprise,
"GTK_THEME variable invalid: {}", e,
),
};
e
}).ok();
@ -94,15 +98,13 @@ fn get_theme_name(settings: &gtk::Settings) -> GtkTheme {
None => GtkTheme {
name: {
settings.get_property("gtk-theme-name")
// maybe TODO: is this worth a warning?
.or_warn("No theme name")
.or_print(logging::Problem::Surprise, "No theme name")
.and_then(|value| value.get::<String>())
.unwrap_or(DEFAULT_THEME_NAME.into())
},
variant: {
settings.get_property("gtk-application-prefer-dark-theme")
// maybe TODO: is this worth a warning?
.or_warn("No settings key")
.or_print(logging::Problem::Surprise, "No settings key")
.and_then(|value| value.get::<bool>())
.and_then(|dark_preferred| match dark_preferred {
true => Some("dark".into()),

19
src/submission.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef __SUBMISSION_H
#define __SUBMISSION_H
#include "input-method-unstable-v2-client-protocol.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "eek/eek-types.h"
struct submission;
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat,
EekboardContextService *state);
// Defined in Rust
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state);
void submission_set_ui(struct submission *self, ServerContextService *ui_context);
void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard);
#endif

View File

@ -1,69 +1,100 @@
/*! Managing the events belonging to virtual-keyboard interface. */
/*! Managing the state of text input in the application.
*
* This is a library module.
*
* It needs to combine text-input and virtual-keyboard protocols
* to achieve a consistent view of the text-input state,
* and to submit exactly what the user wanted.
*
* It must also not get tripped up by sudden disappearances of interfaces.
*
* The virtual-keyboard interface is always present.
*
* The text-input interface may not be presented,
* and, for simplicity, no further attempt to claim it is made.
*
* The text-input interface may be enabled and disabled at arbitrary times,
* and those events SHOULD NOT cause any lost events.
* */
use ::keyboard::{ KeyState, PressType };
use ::imservice::IMService;
use ::vkeyboard::VirtualKeyboard;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::c_void;
use ::imservice::c::InputMethod;
use ::layout::c::LevelKeyboard;
use ::vkeyboard::c::ZwpVirtualKeyboardV1;
// The following defined in C
/// ServerContextService*
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct ZwpVirtualKeyboardV1(*const c_void);
pub struct UIManager(*const c_void);
/// EekboardContextService*
#[repr(transparent)]
pub struct StateManager(*const c_void);
#[no_mangle]
extern "C" {
/// Checks if point falls within bounds,
/// which are relative to origin and rotated by angle (I think)
pub fn eek_virtual_keyboard_v1_key(
virtual_keyboard: ZwpVirtualKeyboardV1,
timestamp: u32,
keycode: u32,
press: u32,
);
pub extern "C"
fn submission_new(
im: *mut InputMethod,
vk: ZwpVirtualKeyboardV1,
state_manager: *const StateManager
) -> *mut Submission {
let imservice = if im.is_null() {
None
} else {
Some(IMService::new(im, state_manager))
};
// TODO: add vkeyboard too
Box::<Submission>::into_raw(Box::new(
Submission {
imservice,
virtual_keyboard: VirtualKeyboard(vk),
}
))
}
/// Use to initialize the UI reference
#[no_mangle]
pub extern "C"
fn submission_set_ui(submission: *mut Submission, ui_manager: *const UIManager) {
if submission.is_null() {
panic!("Null submission pointer");
}
let submission: &mut Submission = unsafe { &mut *submission };
if let Some(ref mut imservice) = &mut submission.imservice {
imservice.ui_manager = if ui_manager.is_null() {
None
} else {
Some(ui_manager)
}
};
}
#[no_mangle]
pub extern "C"
fn submission_set_keyboard(submission: *mut Submission, keyboard: LevelKeyboard) {
if submission.is_null() {
panic!("Null submission pointer");
}
let submission: &mut Submission = unsafe { &mut *submission };
submission.virtual_keyboard.update_keymap(keyboard);
}
}
#[derive(Clone, Copy)]
pub struct Timestamp(pub u32);
/// Layout-independent backend. TODO: Have one instance per program or seat
pub struct VirtualKeyboard(pub c::ZwpVirtualKeyboardV1);
impl VirtualKeyboard {
// TODO: split out keyboard state management
pub fn switch(
&self,
key: &mut KeyState,
action: PressType,
timestamp: Timestamp,
) {
key.pressed = action.clone();
let keycodes_count = key.keycodes.len();
for keycode in key.keycodes.iter() {
let keycode = keycode - 8;
match (&key.pressed, keycodes_count) {
// Pressing a key made out of a single keycode is simple:
// press on press, release on release.
(_, 1) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, action.clone() as u32
);
},
// A key made of multiple keycodes
// has to submit them one after the other
(PressType::Pressed, _) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Pressed as u32
);
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Released as u32
);
},
// Design choice here: submit multiple all at press time
// and do nothing at release time
(PressType::Released, _) => {},
}
}
}
pub struct Submission {
// used by C callbacks internally, TODO: make use with virtual keyboard
#[allow(dead_code)]
imservice: Option<Box<IMService>>,
pub virtual_keyboard: VirtualKeyboard,
}

View File

@ -1,17 +1,22 @@
/*! Testing functionality */
use ::data::Layout;
use ::logging;
use xkbcommon::xkb;
use ::logging::WarningHandler;
pub struct CountAndPrint(u32);
impl WarningHandler for CountAndPrint {
fn handle(&mut self, warning: &str) {
self.0 = self.0 + 1;
println!("{}", warning);
impl logging::Handler for CountAndPrint {
fn handle(&mut self, level: logging::Level, warning: &str) {
use logging::Level::*;
match level {
Panic | Bug | Error | Warning | Surprise => {
self.0 += 1;
},
_ => {}
}
logging::Print{}.handle(level, warning)
}
}
@ -34,7 +39,7 @@ fn check_layout(layout: Layout) {
let (layout, handler) = layout.build(handler);
if handler.0 > 0 {
println!("{} mistakes in layout", handler.0)
println!("{} problems while parsing layout", handler.0)
}
let layout = layout.expect("layout broken");

79
src/vkeyboard.rs Normal file
View File

@ -0,0 +1,79 @@
/*! Managing the events belonging to virtual-keyboard interface. */
use ::keyboard::{ KeyCode, PressType };
use ::layout::c::LevelKeyboard;
use ::submission::Timestamp;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::c_void;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct ZwpVirtualKeyboardV1(*const c_void);
#[no_mangle]
extern "C" {
pub fn eek_virtual_keyboard_v1_key(
virtual_keyboard: ZwpVirtualKeyboardV1,
timestamp: u32,
keycode: u32,
press: u32,
);
pub fn eek_virtual_keyboard_update_keymap(
virtual_keyboard: ZwpVirtualKeyboardV1,
keyboard: LevelKeyboard,
);
}
}
/// Layout-independent backend. TODO: Have one instance per program or seat
pub struct VirtualKeyboard(pub c::ZwpVirtualKeyboardV1);
impl VirtualKeyboard {
// TODO: split out keyboard state management
pub fn switch(
&self,
keycodes: &Vec<KeyCode>,
action: PressType,
timestamp: Timestamp,
) {
let keycodes_count = keycodes.len();
for keycode in keycodes.iter() {
let keycode = keycode - 8;
match (action, keycodes_count) {
// Pressing a key made out of a single keycode is simple:
// press on press, release on release.
(_, 1) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, action.clone() as u32
);
},
// A key made of multiple keycodes
// has to submit them one after the other
(PressType::Pressed, _) => unsafe {
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Pressed as u32
);
c::eek_virtual_keyboard_v1_key(
self.0, timestamp.0, keycode, PressType::Released as u32
);
},
// Design choice here: submit multiple all at press time
// and do nothing at release time
(PressType::Released, _) => {},
}
}
}
pub fn update_keymap(&self, keyboard: LevelKeyboard) {
unsafe {
c::eek_virtual_keyboard_update_keymap(
self.0,
keyboard,
);
}
}
}

View File

@ -1,3 +1,5 @@
#include "eek/eek-keyboard.h"
#include "wayland.h"
struct squeek_wayland *squeek_wayland = NULL;
@ -11,6 +13,13 @@ eek_virtual_keyboard_v1_key(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard
zwp_virtual_keyboard_v1_key(zwp_virtual_keyboard_v1, time, key, state);
}
void eek_virtual_keyboard_update_keymap(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, const LevelKeyboard *keyboard) {
zwp_virtual_keyboard_v1_keymap(zwp_virtual_keyboard_v1,
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keyboard->keymap_fd, keyboard->keymap_len);
}
int squeek_output_add_listener(struct wl_output *wl_output,
const struct wl_output_listener *listener, void *data) {
return wl_output_add_listener(wl_output, listener, data);