Compare commits

...

73 Commits

Author SHA1 Message Date
dcz
a65332c2a9 Merge branch '1171' into '1.17'
Release 1.17.1

See merge request World/Phosh/squeekboard!542
2022-04-16 10:34:33 +00:00
2faa98d85f Release 1.17.1 2022-04-05 13:34:23 +00:00
b594fcf905 state: fix "wide mode" detection in portrait orientation
We need to check if we should use the wide layout based on the
*logical* display width, not its *physical* resolution.
2022-04-05 12:23:20 +00:00
313dbdce33 panel: Use scaling to set height 2022-04-05 12:23:11 +00:00
c51b001836 Do not reset pending state on zwp_input_method_v2.done 2022-04-05 12:22:44 +00:00
bcbc9f7ba6 ci: Allow failure on sid 2022-04-05 12:22:36 +00:00
b56500af0a build: Replace missing crates.io dependency with Purism-hosted one 2022-04-05 12:22:03 +00:00
c912b73c4b Release 1.17.0 "Ergodicity"
New translations:
- Hebrew (thanks Yosef Or Boczko)
- Galician (Fran Dieguez)

New Romanian layout (thanks Cosmin Humeniuc)

Others:
- Updated visual design
- Sizing system rework
- Fix crash without dbus (thanks William Wold)
2022-02-26 11:10:49 +00:00
7b8ba7ab82 cargo: Update lockfile 2022-02-26 11:05:57 +00:00
dcz
dd2871b6bb Merge branch 'output' into 'master'
Derive panel size from outputs

See merge request World/Phosh/squeekboard!528
2022-02-26 10:49:52 +00:00
dcz
216deecd33 Merge branch 'dbus-check-null' into 'master'
Check if dbus handler is null before using

See merge request World/Phosh/squeekboard!530
2022-02-26 10:49:12 +00:00
22cc85b2e2 Add Hebrew translation 2022-02-14 16:05:29 +00:00
0b9350d19b Check if dbus handler is null before using 2022-02-08 10:56:00 -05:00
aad71116c5 Add Galician translation 2022-02-04 15:22:34 +00:00
16d6871422 panel: Apply a hard limit of 1/2 height 2022-02-04 09:40:21 +00:00
78ff02e255 output: Use new source of panel height information
This removed duplicate calculation of ideal height as well.
2022-02-04 09:40:21 +00:00
a3f91701d0 outputs: Remove ui manager 2022-02-04 09:40:21 +00:00
697be64418 visibility: Forward panel height information to window creation 2022-02-04 09:40:21 +00:00
a4b67c65ff Don't reach for globals to choose output
This actually removes the size request from panel creation. Incidentally, this still works becuae the following configure event gets the sizes from glib.
2022-02-04 09:40:21 +00:00
f040e708a4 Carry output information on visible command all the way to C 2022-02-04 09:40:18 +00:00
e6c19a1e6a deps: Vendor assert_matches
The library is small and simple enough to be considered "finished". In addition, it doesn't seem to be shipped by Debian.

In relation to its usefulness, it's little effrt to copy it.
2022-02-04 09:38:06 +00:00
dcz
98ecce518b Merge branch 'tiny' into 'master'
Output sensing

See merge request World/Phosh/squeekboard!524
2022-02-02 17:41:14 +00:00
dcz
500375dcf2 Merge branch 'doc' into 'master'
docs: Detail the release process better

See merge request World/Phosh/squeekboard!523
2022-02-01 16:33:03 +00:00
4e04bc306f Update Brazilian Portuguese translation 2022-01-31 07:48:41 +00:00
dcz
dfcb3ce020 Merge branch 'layout_ro' into 'master'
Add Romanian layout

See merge request World/Phosh/squeekboard!525
2022-01-30 17:52:57 +00:00
dcz
5cf007f419 Merge branch 'Torbuntu-master-patch-58062' into 'master'
Make compatible with latest cargo deps

See merge request World/Phosh/squeekboard!527
2022-01-30 17:47:57 +00:00
Tor
417fe35e91 Make compatible with latest cargo deps 2022-01-30 17:47:57 +00:00
3f598086b7 Store preferred output 2022-01-30 12:43:17 +00:00
dcz
dfe2c60646 Merge branch 'snwh-master-patch-66054' into 'master'
data: Update stylesheet with upstream design

Closes #322

See merge request World/Phosh/squeekboard!520
2022-01-29 11:36:08 +00:00
8ec79428a9 data: Update stylesheet with upstream design
- adjust main font size
- adjust margins and border radii
- drop borders from keys
- put more items in common.css
- update dark and light style definitions
- use mixing of fg color wherever possible
2022-01-28 12:26:23 -03:30
3b0b8bea0d Save outputs state 2022-01-28 15:26:22 +00:00
ba00ec86a1 Add Romanian layout 2022-01-27 20:42:17 +02:00
f15f97d4c9 outputs: Handle removal
Currrently, Squeekboard doesn't do anything with this information.

It still expects one output to be present, or it will crash.
2022-01-26 15:19:58 +00:00
d3eb68ed5a outputs: Notify the state manager about changes 2022-01-26 15:19:58 +00:00
14a485deba outputs: Clean up for more Rust usage 2022-01-26 15:19:58 +00:00
236f7d4daf ffi: Remove unnecessary pointers to InputMethod
InputMethod is already a pointer.
2022-01-26 15:19:58 +00:00
f4f44a49ae wayland: Move initialization to the Rust side
This will help make the init procedure safer, by limiting the number of Rust objects that need to be carried to the C side and may be mangled on the way there.

The second benefit is that it allows outputs to become part of new state management.
2022-01-26 15:19:58 +00:00
1b72cbdfaa docstrings: Clarify the purpose of Receiver 2022-01-26 15:19:58 +00:00
ff3f7228b5 cleanup: Remove unused header lines 2022-01-26 15:19:58 +00:00
b6ce4151bd Merge branch 'rel' into 'master'
Release 1.16.0 "Clostridium botulinum"

See merge request World/Phosh/squeekboard!522
2022-01-25 12:15:22 +00:00
d49ce45de0 Release 1.16.0 "Clostridium botulinum"
This release contains fixes that make the project's dependencies more reliable, and therefore less likely to blow up when the world changes.

Apart from that, there are a lot of extra translations:
- Brazilian Portugese (Rafael Fontenelle)
- Ukrainian (Yuri Chornoivan)
- Swedish (Luna Jernberg)
- Friulian (Fabio Tomat)
- Romanian (Daniel Șerbănescu)
- Slovenian (Matej Urbančič)
- Dutch (Nathan Follens)
- Finnish (Jiri Grönroos)
- Persian (Danial Behzadi)
- Catalan (Jordi Mas i Hernandez)
2022-01-25 11:26:26 +00:00
a341fca43a cargo: Bump dependencies 2022-01-25 11:23:25 +00:00
db6109b298 docs: Detail the release process better 2022-01-25 11:22:31 +00:00
dcz
145d12d01a Merge branch 'fix' into 'master'
CI: Use byzantium as the base

See merge request World/Phosh/squeekboard!521
2022-01-24 19:34:16 +00:00
506df8cf15 CI: Use byzantium as the base
Bookworm diverged from Byzantium, leading to incompatible dependencies.
2022-01-24 19:12:51 +00:00
dcz
21ecbb3ef3 Merge branch 'entry4' into 'master'
Add a GTK4 keyboard hint test tool

See merge request World/Phosh/squeekboard!518
2022-01-21 13:15:24 +00:00
29da31af20 Add Catalan translation 2022-01-20 14:36:58 +00:00
23b35733cb Add entry test using GTK4
This helps debugging GTK4 interaction issues
2022-01-20 10:36:13 +01:00
55cd225c74 entry: Add another input hint 2022-01-20 10:17:11 +01:00
e55ae67da6 entry: Only activate purpose timer when focused
This avoids verbose printing when the widget is never focused
2022-01-20 10:17:11 +01:00
79bc670ad0 entry: Mark as executable
Saves some typing
2022-01-20 10:17:11 +01:00
dcz
ebc2dd39f6 Merge branch 'vers' into 'master'
build: Pin transitive dependencies

Closes #321

See merge request World/Phosh/squeekboard!519
2022-01-19 17:06:27 +00:00
dcz
71768e27c0 Merge branch 'noreg' into 'master'
ci: Use bookworm image

See merge request World/Phosh/squeekboard!510
2022-01-19 16:49:21 +00:00
aaac755869 cargo: Update Cargo.lock with pinned dependencies 2022-01-19 16:33:33 +00:00
0430ba9213 build: Pin transitive dependencies
gtk-sys and cairo-sys were set to "anything" in the hope that the dependency resolver in cargo would use the same version as other dependencies, which consume objects from them.
That's not the case though, and they went out of sync.
This pins the dependencies to what is de facto being used.
2022-01-19 16:33:33 +00:00
0c17924c50 Add Persian translation 2022-01-11 14:31:39 +00:00
5286ff50a5 Add Finnish translation 2021-12-26 15:15:43 +00:00
3ee2185714 Add Dutch translation 2021-12-25 13:55:12 +00:00
7ea30819aa Add Slovenian translation 2021-12-23 15:18:45 +00:00
3647581cd7 Add Romanian translation 2021-12-22 19:14:12 +00:00
dcz
d76b385316 Merge branch 'droidbittin-master-patch-70834' into 'master'
Update LINGUAS

See merge request World/Phosh/squeekboard!516
2021-12-22 14:45:01 +00:00
3240006516 Add Friulian translation 2021-12-22 14:07:20 +00:00
2d0aa7aef1 Update LINGUAS 2021-12-22 14:02:56 +00:00
dcz
4dd4c8c319 Merge branch 'master' into 'master'
Add Swedish Translation

See merge request World/Phosh/squeekboard!515
2021-12-22 13:33:27 +00:00
324438acac Update sv.po 2021-12-22 13:17:58 +00:00
a643b05f57 Add Swedish Translation 2021-12-22 13:16:51 +00:00
7adf325831 Add Ukrainian translation 2021-12-22 12:47:09 +00:00
b85903cb21 Add Brazilian Portuguese translation 2021-12-22 12:39:07 +00:00
dcz
67d2f8d8e6 Merge branch 'po-popover' into 'master'
po: Fix ui file name

See merge request World/Phosh/squeekboard!514
2021-12-22 10:36:38 +00:00
6979b6d08d po: Fix ui file name
The file was renamed to match the code

Fixes: 2aa9cf2 ("popover: Make the ui file match the code file name")
2021-12-22 09:39:12 +01:00
44b9c8f869 Merge branch 'rel' into 'master'
Release 1.15.0 "Feedback loop"

See merge request World/Phosh/squeekboard!513
2021-12-21 15:48:51 +00:00
6dae43b437 ci: Use bookworm image
The image for x86_64 is a custom one from the registry.
2021-12-17 14:04:58 +00:00
8eb1c9b4a7 build: Remove regex crate 2021-12-17 14:04:22 +00:00
61 changed files with 2187 additions and 635 deletions

View File

@ -1,4 +1,4 @@
image: debian:bullseye image: pureos/byzantium
stages: stages:
- build - build
@ -6,10 +6,7 @@ stages:
before_script: before_script:
- apt-get -y update - apt-get -y update
- apt-get -y install wget ca-certificates gnupg - apt-get -y install ca-certificates
- echo "deb [trusted=yes] http://ci.puri.sm/ bullseyeci main" > /etc/apt/sources.list.d/ci.list
- wget -O- https://ci.puri.sm/ci-repo.key | apt-key add -
- apt-get -y update
build_docs: build_docs:
stage: build stage: build
@ -56,6 +53,7 @@ build_deb:
- cp ../*.deb . - cp ../*.deb .
build_deb:arm64: build_deb:arm64:
image: pureos/byzantium
tags: tags:
- aarch64 - aarch64
stage: build stage: build
@ -74,6 +72,28 @@ build_deb:arm64:
- debuild -i -us -uc -b - debuild -i -us -uc -b
- cp ../*.deb . - cp ../*.deb .
build_deb:future:
image: debian:sid
allow_failure: true
tags:
- aarch64
stage: build
artifacts:
paths:
- '*.deb'
script:
- rm -f ../*.deb
- mv debian/control-newer debian/control
- apt-get -y build-dep .
- apt-get -y install devscripts
- REV=$(git log -1 --format=%h)
- VER=$(dpkg-parsechangelog -SVersion)
- DEBFULLNAME="Librem5 CI"
- EMAIL="librem5-builds@lists.community.puri.sm"
- dch -v"$VER+librem5ci$CI_PIPELINE_ID.$REV" "$MSG"
- debuild -i -us -uc -b
- cp ../*.deb .
test_lintian: test_lintian:
stage: test stage: test
needs: needs:

View File

@ -1,11 +1,13 @@
# Dependencies which change based on build flags # Dependencies which change based on build flags
bitflags = "1.2.*" bitflags = "1.2.*"
clap = { version = "2.33.*", default-features = false } clap = { version = "2.33.*", default-features = false }
regex = { version = "1.3.*", default-features = false, features = ["std", "unicode-case"] }
[dependencies.cairo-rs] [dependencies.cairo-rs]
version = "0.7.*" version = "0.7.*"
[dependencies.cairo-sys-rs]
version = "0.9"
[dependencies.gdk] [dependencies.gdk]
version = "0.11.*" version = "0.11.*"
@ -17,6 +19,14 @@ features = ["v2_44"]
version = "0.8.*" version = "0.8.*"
features = ["v2_44"] features = ["v2_44"]
[dependencies.glib-sys]
version = "*"
features = ["v2_44"]
[dependencies.gtk] [dependencies.gtk]
version = "0.7.*" version = "0.7.*"
features = ["v3_22"] features = ["v3_22"]
[dependencies.gtk-sys]
version = "0.9"
features = ["v3_22"]

View File

@ -1,22 +0,0 @@
# Dependencies which change based on build flags
bitflags = "1.0.*"
clap = { version = "2.32.*", default-features = false }
regex = { version = "1.1.*", default-features = false, features = ['use_std'] }
[dependencies.cairo-rs]
version = "0.5.*"
[dependencies.gdk]
version = "0.9.*"
[dependencies.gio]
version = "0.5.*"
features = ["v2_44"]
[dependencies.glib]
version = "0.6.*"
features = ["v2_44"]
[dependencies.gtk]
version = "0.5.*"
features = ["v3_22"]

33
Cargo.deps.newer Normal file
View File

@ -0,0 +1,33 @@
# Dependencies which change based on build flags
# For the newer-than-Byzantium config
bitflags = "1.3.*"
clap = { version = "2.33.*", default-features = false }
[dependencies.cairo-rs]
version = "0.14.*"
[dependencies.cairo-sys-rs]
version = "0.14.*"
[dependencies.gdk]
version = "0.14.*"
[dependencies.gio]
version = "0.14.*"
features = ["v2_58"]
[dependencies.glib]
version = "0.14.*"
features = ["v2_58"]
[dependencies.glib-sys]
version = "0.14.*"
features = ["v2_58"]
[dependencies.gtk]
version = "0.14.*"
features = ["v3_22"]
[dependencies.gtk-sys]
version = "0.14.*"
features = ["v3_22"]

4
Cargo.deps.online Normal file
View File

@ -0,0 +1,4 @@
# Dependencies which are only used with online, crates.io builds.
[patch.crates-io]
# Dependency was yanked, but gio 0.7 needs it.
fragile = { git = "https://source.puri.sm/dorota.czaplejewicz/fragile.git", tag = "0.3.0" }

80
Cargo.lock generated
View File

@ -30,9 +30,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -67,27 +67,21 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.72" version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.33.3" version = "2.33.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "826bf7bc84f9435630275cb8e802a4a0ec792b615969934bd16d42ffed10f207"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"textwrap", "textwrap",
"unicode-width", "unicode-width",
] ]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]] [[package]]
name = "fragile" name = "fragile"
version = "0.3.0" version = "0.3.0"
@ -273,9 +267,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown", "hashbrown",
@ -289,9 +283,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.108" version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
@ -344,43 +338,28 @@ dependencies = [
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.22" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.32" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.10" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]] [[package]]
name = "rs" name = "rs"
version = "0.1.0" version = "0.1.0"
@ -396,26 +375,31 @@ dependencies = [
"gtk", "gtk",
"gtk-sys", "gtk-sys",
"maplit", "maplit",
"regex",
"serde", "serde",
"serde_yaml", "serde_yaml",
"xkbcommon", "xkbcommon",
] ]
[[package]] [[package]]
name = "serde" name = "ryu"
version = "1.0.130" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "serde"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.130" version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -424,21 +408,21 @@ dependencies = [
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.8.21" version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
dependencies = [ dependencies = [
"dtoa",
"indexmap", "indexmap",
"ryu",
"serde", "serde",
"yaml-rust", "yaml-rust",
] ]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.82" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -17,22 +17,9 @@ name = "test_layout"
path = "@path@/examples/test_layout.rs" path = "@path@/examples/test_layout.rs"
[features] [features]
gio_v0_5 = [] glib_v0_14 = []
gtk_v0_5 = []
rustc_less_1_36 = []
# Dependencies which don't change based on build flags # Dependencies which don't change based on build flags
[dependencies.cairo-sys-rs]
version = "*"
[dependencies.glib-sys]
version = "*"
features = ["v2_44"]
[dependencies.gtk-sys]
version = "*"
features = ["v3_22"]
[dependencies] [dependencies]
maplit = "1.0.*" maplit = "1.0.*"
serde = { version = "1.0.*", features = ["derive"] } serde = { version = "1.0.*", features = ["derive"] }

View File

@ -1,4 +1,31 @@
/* Theme independent style */ /* Theme independent styles */
sq_view {
font-family: cantarell, sans-serif;
font-size: 1.5em;
}
sq_button {
border-radius: 4px;
margin: 2px;
}
sq_view.wide sq_button {
margin: 3px;
}
sq_button.latched,
sq_button.locked {
font-weight: bold;
}
sq_button.action {
font-size: 0.75em;
}
sq_button.small {
font-size: 0.5em;
}
sq_view.pin sq_button { sq_view.pin sq_button {
border-radius: 0px; border-radius: 0px;

89
data/keyboards/ro.yaml Normal file
View File

@ -0,0 +1,89 @@
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 62, height: 52 }
spaceline: { width: 99.67, height: 52 }
special: { width: 44, height: 52 }
views:
base:
- "q w e r t y u i o p"
- "a s d f g h j k l"
- "Shift_L z x c v b n m BackSpace"
- "show_numbers show_eschars preferences space . 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 show_eschars preferences space , Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # € % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters show_eschars preferences space . Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ $ ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters show_eschars preferences space , Return"
eschars:
- "ă â î ș ț á é í ó ü"
- "Ă Â Î Ș Ț Á É Í Ó Ü"
- "show_numbers_from_symbols „ ” « » ― { } BackSpace"
- "show_letters show_eschars preferences space . Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
show_eschars:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "ăĂ"
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
"\"":
keysym: "quotedbl"

View File

@ -0,0 +1,89 @@
---
outlines:
default: { width: 54, height: 42 }
altline: { width: 81, height: 42 }
wide: { width: 108, height: 42 }
spaceline: { width: 153, height: 42 }
special: { width: 54, height: 42 }
views:
base:
- "q w e r t y u i o p"
- "a s d f g h j k l"
- "Shift_L z x c v b n m BackSpace"
- "show_numbers show_eschars preferences space . 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 show_eschars preferences space , Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # € % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters show_eschars preferences space . Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ $ ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters show_eschars preferences space , Return"
eschars:
- "ă â î ș ț á é í ó ü"
- "Ă Â Î Ș Ț Á É Í Ó Ü"
- "show_numbers_from_symbols „ ” « » ― { } BackSpace"
- "show_letters show_eschars preferences space . Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
show_eschars:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "ăĂ"
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
"\"":
keysym: "quotedbl"

View File

@ -1,34 +1,22 @@
/* Adwaita-dark style keyboard */
sq_view { sq_view {
background-color: rgba(0, 0, 0, 255); background-color: rgba(0, 0, 0, 255);
color: #ffffff; color: #ffffff;
font-family: cantarell, sans-serif;
font-size: 25px;
} }
sq_view sq_button { sq_view sq_button {
color: #deddda; color: #deddda;
background: #464448; background: #464448;
border-style: solid;
border-width: 1px;
border-color: #5e5c64;
border-radius: 3px;
margin: 4px 2px 4px 2px;
}
sq_view.wide sq_button {
margin: 1px 1px 1px 1px;
} }
sq_button:active { sq_button:active {
background: #747077; background: #747077;
border-color: #96949d;
} }
sq_button.altline, sq_button.altline,
sq_button.special, sq_button.special,
sq_button.wide { sq_button.wide {
background: #2b292f; background: #2b292f;
border-color: #3e3a44;
} }
sq_button.latched { sq_button.latched {
@ -41,22 +29,12 @@ sq_button.locked {
color: #1c71d8; color: #1c71d8;
} }
sq_button.action {
font-size: 0.75em;
}
sq_button.small {
font-size: 0.5em;
}
#Return { #Return {
background: #1c71d8; background: #1c71d8;
border-color: #1a5fb4;
} }
#Return:active { #Return:active {
background: #1c71d8; background: #1c71d8;
border-color: #3584e4;
} }
@import url("resource:///sm/puri/squeekboard/common.css"); @import url("resource:///sm/puri/squeekboard/common.css");

View File

@ -1,65 +1,47 @@
/* Keyboard style */
sq_view { sq_view {
background-color: @theme_base_color; /*rgba(0, 0, 0, 255);*/ background-color: mix(@theme_base_color, @theme_fg_color, 0.1);
color: @theme_text_color; /*#ffffff;*/ box-shadow:inset 0 1px 0 0 mix(@borders, @theme_base_color, 0.8);
font-family: cantarell, sans-serif;
font-size: 25px;
} }
sq_view sq_button { sq_button {
color: @theme_fg_color; /*#deddda;*/ color: @theme_fg_color;
background: mix(@theme_bg_color, @theme_base_color, -0.5); /* #464448; */ background: alpha(@theme_fg_color, 0.07);
border-style: solid; box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
border-width: 1px;
border-color: @borders; /* #5e5c64;*/
border-radius: 3px;
margin: 4px 2px 4px 2px;
} }
sq_view.wide sq_button { sq_button:active {
margin: 1px 1px 1px 1px; background: alpha(@theme_fg_color, 0.11);
}
sq_button:active,
sq_button.altline:active,
sq_button.special:active,
sq_button.wide:active {
background: mix(@theme_bg_color, @theme_selected_bg_color, 0.4);/* #747077; */
border-color: mix(@borders, @theme_selected_fg_color, 0.5);/* #96949d; */
} }
sq_button.altline, sq_button.altline,
sq_button.special, sq_button.special {
sq_button.wide { background: alpha(@theme_fg_color, 0.15);
background: mix(@theme_bg_color, @theme_base_color, 0.5); /*#2b292f;*/ }
border-color: @borders; /* #3e3a44; */
sq_button.altline:active,
sq_button.special:active {
background: alpha(@theme_fg_color, 0.2);
} }
sq_button.latched { sq_button.latched {
background: @theme_fg_color; /*#ffffff;*/ background: alpha(@theme_fg_color, 0.2);
color: @theme_bg_color; /*#2b292f;*/ color: alpha(@theme_fg_color, 0.8);
} }
sq_button.locked { sq_button.locked {
background: @theme_fg_color; /*#ffffff;*/ background: alpha(@theme_fg_color, 0.5);
color: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#2b292f;*/ color: @theme_base_color;
}
sq_button.action {
font-size: 0.75em;
}
sq_button.small {
font-size: 0.5em;
} }
#Return { #Return {
background: @theme_selected_bg_color; /* #1c71d8; */ background: @theme_selected_bg_color;
border-color: @borders; /*#1a5fb4;*/ color: @theme_selected_fg_color;
} }
#Return:active { #Return:active {
background: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#1c71d8;*/ background: mix(@theme_selected_bg_color, black, 0.2);
border-color: @borders; /*#3584e4;*/ color: mix(@theme_selected_fg_color, black, 0.2);
} }
@import url("resource:///sm/puri/squeekboard/common.css"); @import url("resource:///sm/puri/squeekboard/common.css");

1
debian/cargo/config vendored
View File

@ -9,4 +9,3 @@ replace-with = 'vendored-sources'
[source.vendored-sources] [source.vendored-sources]
directory = '/usr/share/cargo/registry' directory = '/usr/share/cargo/registry'

111
debian/changelog vendored
View File

@ -1,3 +1,114 @@
squeekboard (1.17.1-1) experimental; urgency=medium
[ Dorota Czaplejewicz ]
* build: Replace missing crates.io dependency with Purism-hosted one
* ci: Allow failure on sid
* panel: Use scaling to set height
[ William Wold ]
* Do not reset pending state on zwp_input_method_v2.done
[ Arnaud Ferraris ]
* state: fix "wide mode" detection in portrait orientation
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 05 Apr 2022 13:32:53 +0000
squeekboard (1.17.0-1) experimental; urgency=medium
[ Dorota Czaplejewicz ]
* docs: Detail the release process better
* cleanup: Remove unused header lines
* docstrings: Clarify the purpose of Receiver
* wayland: Move initialization to the Rust side
* ffi: Remove unnecessary pointers to InputMethod
* outputs: Clean up for more Rust usage
* outputs: Notify the state manager about changes
* outputs: Handle removal
* Save outputs state
* Store preferred output
* deps: Vendor assert_matches
* Carry output information on visible command all the way to C
* Don't reach for globals to choose output
* visibility: Forward panel height information to window creation
* outputs: Remove ui manager
* output: Use new source of panel height information
* panel: Apply a hard limit of 1/2 height
* cargo: Update lockfile
[ Cosmin Humeniuc ]
* Add Romanian layout
[ Sam Hewitt ]
* data: Update stylesheet with upstream design
[ Tor ]
* Make compatible with latest cargo deps
[ Luís Fernando Stürmer da Rosa ]
* Update Brazilian Portuguese translation
[ Fran Dieguez ]
* Add Galician translation
[ William Wold ]
* Check if dbus handler is null before using
[ Yosef Or Boczko ]
* Add Hebrew translation
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 25 Jan 2022 11:24:04 +0000
squeekboard (1.16.0-1) experimental; urgency=medium
[ Dorota Czaplejewicz ]
* build: Remove regex crate
* ci: Use bookworm image
* build: Pin transitive dependencies
* cargo: Update Cargo.lock with pinned dependencies
* CI: Use byzantium as the base
* cargo: Bump dependencies
[ Guido Günther ]
* po: Fix ui file name
* entry: Mark as executable
* entry: Only activate purpose timer when focused
* entry: Add another input hint
* Add entry test using GTK4
[ Rafael Fontenelle ]
* Add Brazilian Portuguese translation
[ Yuri Chornoivan ]
* Add Ukrainian translation
[ Luna Jernberg ]
* Add Swedish Translation
* Update sv.po
* Update LINGUAS
[ Fabio Tomat ]
* Add Friulian translation
[ Daniel Șerbănescu ]
* Add Romanian translation
[ Matej Urbančič ]
* Add Slovenian translation
[ Nathan Follens ]
* Add Dutch translation
[ Jiri Grönroos ]
* Add Finnish translation
[ Danial Behzadi ]
* Add Persian translation
[ Jordi Mas i Hernandez ]
* Add Catalan translation
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 25 Jan 2022 11:24:04 +0000
squeekboard (1.15.0-1) experimental; urgency=medium squeekboard (1.15.0-1) experimental; urgency=medium
[ Khaled Eldoheiri ] [ Khaled Eldoheiri ]

1
debian/control vendored
View File

@ -20,7 +20,6 @@ Build-Depends:
librust-gtk+v3-22-dev (>= 0.5), librust-gtk+v3-22-dev (>= 0.5),
librust-gtk-sys-dev, librust-gtk-sys-dev,
librust-maplit-1-dev (>= 1.0), librust-maplit-1-dev (>= 1.0),
librust-regex-1-dev (>= 1.1),
librust-serde-derive-1-dev (>= 1.0), librust-serde-derive-1-dev (>= 1.0),
librust-serde-yaml-0.8-dev (>= 0.8), librust-serde-yaml-0.8-dev (>= 0.8),
librust-xkbcommon-0.4+wayland-dev (>= 0.4), librust-xkbcommon-0.4+wayland-dev (>= 0.4),

58
debian/control-newer vendored Normal file
View File

@ -0,0 +1,58 @@
Source: squeekboard
Section: x11
Priority: optional
Maintainer: Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm>
Build-Depends:
cargo,
debhelper-compat (= 13),
meson (>=0.51.0),
ninja-build,
pkg-config,
libglib2.0-dev,
libgnome-desktop-3-dev,
libgtk-3-dev,
libfeedback-dev,
librust-bitflags-dev (>= 1.0),
librust-clap-dev (>= 2.32),
librust-gio+v2-58-dev,
librust-glib+v2-58-dev,
librust-glib-sys-dev,
librust-gtk+v3-22-dev (>= 0.5),
librust-gtk-sys-dev,
librust-maplit-1-dev (>= 1.0),
librust-serde-derive-1-dev (>= 1.0),
librust-serde-yaml-0.8-dev (>= 0.8),
librust-xkbcommon-0.4+wayland-dev (>= 0.4),
libwayland-dev (>= 1.16),
lsb-release,
python3,
python3-ruamel.yaml,
rustc,
wayland-protocols (>= 1.14),
Standards-Version: 4.1.3
Homepage: https://source.puri.sm/Librem5/squeekboard
Package: squeekboard
Architecture: linux-any
Depends:
# for the Adwaita-dark theme
gnome-themes-extra-data,
${shlibs:Depends},
${misc:Depends},
Breaks:
librem5-base (<< 24),
Description: On-screen keyboard for Wayland
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.
Package: squeekboard-devel
Architecture: linux-any
Depends:
python3,
python3-gi,
${shlibs:Depends},
${misc:Depends},
Description: Resources for making Squeekboard layouts
Tools for creating and testing Squeekboard layouts:
.
* squeekboard-entry
* squeekboard-test-layout

8
debian/rules vendored
View File

@ -25,10 +25,10 @@ export RUSTFLAGS = --remap-path-prefix=$(CURDIR)=/remap-pwd $(xgot)
distrel := $(shell lsb_release --codename --short) distrel := $(shell lsb_release --codename --short)
ifneq (,$(filter $(distrel),buster amber)) ifneq (,$(filter $(distrel),sid))
legacy = true newer = true
else else
legacy = false newer = false
endif endif
%: %:
@ -38,6 +38,6 @@ endif
# causing Cargo to refuse to build with a crates.io copy # causing Cargo to refuse to build with a crates.io copy
override_dh_auto_configure: override_dh_auto_configure:
[ ! -f Cargo.lock ] || rm Cargo.lock [ ! -f Cargo.lock ] || rm Cargo.lock
dh_auto_configure -- -Dlegacy=$(legacy) dh_auto_configure -- -Dnewer=$(newer) -Donline=false
override_dh_autoreconf: override_dh_autoreconf:

View File

@ -205,6 +205,7 @@ While the file is not actually used, it's a good idea to save the config in case
``` ```
cd squeekboard-build cd squeekboard-build
.../squeekboard-source/cargo.sh update
ninja test ninja test
cp ./Cargo.lock .../squeekboard-source cp ./Cargo.lock .../squeekboard-source
``` ```

View File

@ -1,7 +1,7 @@
project( project(
'squeekboard', 'squeekboard',
'c', 'rust', 'c', 'rust',
version: '1.15.0', version: '1.17.1',
license: 'GPLv3', license: 'GPLv3',
meson_version: '>=0.51.0', meson_version: '>=0.51.0',
default_options: [ default_options: [
@ -96,19 +96,23 @@ cargo_toml_base = configure_file(
configuration: path_data, configuration: path_data,
) )
cargo_patch = []
if get_option('newer') == true
cargo_build_flags += ['--features', 'glib_v0_14']
cargo_deps = files('Cargo.deps.newer')
else
cargo_deps = files('Cargo.deps') cargo_deps = files('Cargo.deps')
if get_option('online') == true
if get_option('legacy') == true cargo_patch = [files('Cargo.deps.online')]
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5,rustc_less_1_36'] endif
cargo_deps = files('Cargo.deps.legacy')
endif endif
cat = find_program('cat') cat = find_program('cat')
cargo_toml = custom_target( cargo_toml = custom_target(
'Cargo.toml', 'Cargo.toml',
output: 'Cargo.toml', output: 'Cargo.toml',
command: [cat, cargo_toml_base, cargo_deps], command: [cat, cargo_toml_base, cargo_deps] + cargo_patch,
capture: true, capture: true,
) )

View File

@ -7,9 +7,13 @@ option('tests',
type: 'boolean', value: true, type: 'boolean', value: true,
description: 'Whether to compile unit tests') description: 'Whether to compile unit tests')
option('legacy', option('newer',
type: 'boolean', value: false, type: 'boolean', value: false,
description: 'Build with Deban Buster versions of dependencies') description: 'Build with dependencies newer than those of Byzantium')
option('online',
type: 'boolean', value: true,
description: 'Pull packages from the internet while building, as opposed to a local regstry.')
option('strict', option('strict',
type: 'boolean', value: true, type: 'boolean', value: true,

View File

@ -1 +1,13 @@
ca
de de
fa
fi
fur
gl
he
nl
pt_BR
ro
sl
uk
sv

View File

@ -1,2 +1,2 @@
data/popup.ui data/popover.ui
data/sm.puri.Squeekboard.desktop.in.in data/sm.puri.Squeekboard.desktop.in.in

45
po/ca.po Normal file
View File

@ -0,0 +1,45 @@
# Catalan translation for squeekboard.
# Copyright (C) 2022 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# maite <maite.guix@gmail.com>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2022-01-11 14:31+0000\n"
"PO-Revision-Date: 2022-01-20 10:53+0100\n"
"Last-Translator: maite guix <maite.guix@me.com>\n"
"Language-Team: Catalan <gnome@llistes.softcatala.org>\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Configuració del teclat"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Teclat virtual"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Teclat en pantalla"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Un teclat virtual en pantalla"

45
po/fa.po Normal file
View File

@ -0,0 +1,45 @@
# Persian translation for squeekboard.
# Copyright (C) 2022 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Danial Behzadi <dani.behzi@ubuntu.com>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-26 15:15+0000\n"
"PO-Revision-Date: 2022-01-11 18:01+0330\n"
"Language-Team: Persian <fa@li.org>\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: Danial Behzadi <dani.behzi@ubuntu.com>\n"
"X-Generator: Poedit 3.0\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "شکلک"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "پایانه"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "تنظیمات صفحه‌کلید"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "اسکوییک‌برد"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "صفحه‌کلید لمسی"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "یک صفحهٔ کلید لمسی مجازی"

46
po/fi.po Normal file
View File

@ -0,0 +1,46 @@
# Finnish translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Jiri Grönroos <jiri.gronroos@iki.fi>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-25 13:55+0000\n"
"PO-Revision-Date: 2021-12-26 17:15+0200\n"
"Last-Translator: Jiri Grönroos <jiri.gronroos+l10n@iki.fi>\n"
"Language-Team: Finnish <lokalisointi-lista@googlegroups.com>\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Pääte"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Näppäimistön asetukset"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Näyttönäppäimistö"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Virtuaalinen näyttönäppäimistö"

45
po/fur.po Normal file
View File

@ -0,0 +1,45 @@
# Friulian translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Fabio Tomat <f.t.public@gmail.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-22 13:33+0000\n"
"PO-Revision-Date: 2021-12-22 15:06+0100\n"
"Last-Translator: Fabio Tomat <f.t.public@gmail.com>\n"
"Language-Team: Friulian <fur@li.org>\n"
"Language: fur\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminâl"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Impostazions tastiere"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Tastiere a visôr"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Une tastiere virtuâl a visôr"

52
po/gl.po Normal file
View File

@ -0,0 +1,52 @@
# Galician translation for squeekboard.
# Copyright (C) 2022 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Fran Diéguez <frandieguez@gnome.org>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2022-02-02 17:41+0000\n"
"PO-Revision-Date: 2022-02-04 16:18+0100\n"
"Last-Translator: Fran Diéguez <frandieguez@gnome.org>\n"
"Language-Team: Galician <proxecto@trasno.gal>\n"
"Language: gl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-DL-Team: gl\n"
"X-DL-Module: squeekboard\n"
"X-DL-Branch: master\n"
"X-DL-Domain: po\n"
"X-DL-State: Translating\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"X-Generator: Gtranslator 41.0\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoticono"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Preferencias de teclado"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Teclado en pantalla"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Un teclado en pantalla virtual"

46
po/he.po Normal file
View File

@ -0,0 +1,46 @@
# Hebrew translation for squeekboard.
# Copyright (C) 2022 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Yosef Or Boczko <yoseforb@gmail.com>, 2022.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2022-02-04 15:22+0000\n"
"PO-Revision-Date: 2022-02-14 18:05+0200\n"
"Last-Translator: Yosef Or Boczko <yoseforb@gmail.com>\n"
"Language-Team: Hebrew <>\n"
"Language: he\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Gtranslator 40.0\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "רגשון"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "מסוף"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "הגדרות מקלדת"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "מקלדת על המסך"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "מקלדת מדומה על המסך"

48
po/nl.po Normal file
View File

@ -0,0 +1,48 @@
# Dutch translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Jan Jasper de Kroon <jajadekroon@gmail.com>, 2021.
# Nathan Follens <nfollens@gnome.org>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-23 15:18+0000\n"
"PO-Revision-Date: 2021-12-25 14:04+0100\n"
"Last-Translator: Nathan Follens <nfollens@gnome.org>\n"
"Language-Team: Dutch <gnome-nl-list@gnome.org>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Toetsenbordinstellingen"
# Dit is de naam van de applicatie
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Schermtoetsenbord"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Een virtueel schermtoetsenbord"

47
po/pt_BR.po Normal file
View File

@ -0,0 +1,47 @@
# Brazilian Portuguese translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Rafael Fontenelle <rafaelff@gnome.org>, 2021.
# Luís Fernando Stürmer da Rosa <luisfsr@dismail.de>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-22 12:39+0000\n"
"PO-Revision-Date: 2022-01-30 12:34-0300\n"
"Last-Translator: Luís Fernando Stürmer da Rosa <luisfsr@dismail.de>\n"
"Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 3.0\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Configurações do teclado"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Teclado virtual"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Um teclado virtual"

47
po/ro.po Normal file
View File

@ -0,0 +1,47 @@
# Romanian translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# libre <eposta1@pm.me>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-22 14:45+0000\n"
"PO-Revision-Date: 2021-12-22 20:05+0100\n"
"Last-Translator: libre <eposta1@pm.me>\n"
"Language-Team: Romanian <gnomero-list@lists.sourceforge.net>\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < "
"20)) ? 1 : 2);;\n"
"X-Generator: Gtranslator 3.30.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Opțiuni tastatură"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Tastatură pe ecran"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "O tastatură virtuală pe ecran"

49
po/sl.po Normal file
View File

@ -0,0 +1,49 @@
# Slovenian translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
#
# Matej Urbančič <mateju@src.gnome.org>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-22 19:14+0000\n"
"PO-Revision-Date: 2021-12-23 16:17+0100\n"
"Last-Translator: Matej Urbančič <mateju@svn.gnome.org>\n"
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
"Language: sl_SI\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n"
"%100==4 ? 3 : 0);\n"
"X-Poedit-SourceCharset: utf-8\n"
"X-Generator: Poedit 3.0.1\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Izrazne ikone"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Nastavitve tipkovnice"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Cvilkovnica"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Zaslonska tipkovnica"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Navidezna zaslonska tipkovnica"

48
po/sv.po Normal file
View File

@ -0,0 +1,48 @@
# Swedish translations for squeekboard package.
# Copyright (C) 2021 THE squeekboard'S COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
# Automatically generated, 2021.
#
# Luna Jernberg <droidbittin@gmail.com>, 2021.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/"
"issues\n"
"POT-Creation-Date: 2021-12-22 12:47+0000\n"
"PO-Revision-Date: 2021-12-22 14:15+0100\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0\n"
"Last-Translator: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: sv\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr "Emoji"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr "Terminal"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr "Tangentbordsinställningar"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr "Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr "Skärmtangentbord"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr "Ett virtuellt skärmtangentbord"

51
po/uk.po Normal file
View File

@ -0,0 +1,51 @@
# Ukrainian translation for squeekboard.
# Copyright (C) 2021 squeekboard's COPYRIGHT HOLDER
# This file is distributed under the same license as the squeekboard package.
#
# Yuri Chornoivan <yurchor@ukr.net>, 2021.
msgid ""
msgstr ""
"Project-Id-Version: squeekboard master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/Phosh/squeekboard/issues\n"
"POT-Creation-Date: 2021-12-22 10:36+0000\n"
"PO-Revision-Date: 2021-12-22 14:46+0200\n"
"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Lokalize 20.12.0\n"
#. translators: This is a emmoji keyboard layout
#: data/popover.ui:6
msgid "Emoji"
msgstr ""
"Емодзі"
#. translators: This is a terminal keyboard layout
#: data/popover.ui:12
msgid "Terminal"
msgstr ""
"Термінал"
#: data/popover.ui:18
msgid "Keyboard Settings"
msgstr ""
"Параметри клавіатури"
#: data/sm.puri.Squeekboard.desktop.in.in:3
msgid "Squeekboard"
msgstr ""
"Squeekboard"
#: data/sm.puri.Squeekboard.desktop.in.in:4
msgid "On Screen Keyboard"
msgstr ""
"Екранна клавіатура"
#: data/sm.puri.Squeekboard.desktop.in.in:5
msgid "An on screen virtual keyboard"
msgstr ""
"Екранна віртуальна клавіатура"

View File

@ -6,12 +6,18 @@
use std::time::Duration; use std::time::Duration;
use crate::main::PixelSize;
use crate::outputs::OutputId;
/// The keyboard should hide after this has elapsed to prevent flickering. /// The keyboard should hide after this has elapsed to prevent flickering.
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
/// The outwardly visible state of visibility /// The outwardly visible state of visibility
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum Outcome { pub enum Outcome {
Visible, Visible {
output: OutputId,
height: PixelSize,
},
Hidden, Hidden,
} }

358
src/assert_matches.rs Normal file
View File

@ -0,0 +1,358 @@
/* Taken from https://github.com/murarth/assert_matches
*
* git commit: 26b8b40a12823c068a829ba475d0eccc13dfc221
*
* assert_matches is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
*
Copyright (c) 2016 Murarth
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
//! Provides a macro, `assert_matches!`, which tests whether a value
//! matches a given pattern, causing a panic if the match fails.
//!
//! See the macro [`assert_matches!`] documentation for more information.
//!
//! Also provides a debug-only counterpart, [`debug_assert_matches!`].
//!
//! See the macro [`debug_assert_matches!`] documentation for more information
//! about this macro.
//!
//! [`assert_matches!`]: macro.assert_matches.html
//! [`debug_assert_matches!`]: macro.debug_assert_matches.html
#![deny(missing_docs)]
#![cfg_attr(not(test), no_std)]
/// Asserts that an expression matches a given pattern.
///
/// A guard expression may be supplied to add further restrictions to the
/// expected value of the expression.
///
/// A `match` arm may be supplied to perform additional assertions or to yield
/// a value from the macro invocation.
///
/// # Examples
///
/// ```
/// #[macro_use] extern crate assert_matches;
///
/// #[derive(Debug)]
/// enum Foo {
/// A(i32),
/// B(&'static str),
/// }
///
/// # fn main() {
/// let a = Foo::A(1);
///
/// // Assert that `a` matches the pattern `Foo::A(_)`.
/// assert_matches!(a, Foo::A(_));
///
/// // Assert that `a` matches the pattern and
/// // that the contained value meets the condition `i > 0`.
/// assert_matches!(a, Foo::A(i) if i > 0);
///
/// let b = Foo::B("foobar");
///
/// // Assert that `b` matches the pattern `Foo::B(_)`.
/// assert_matches!(b, Foo::B(s) => {
/// // Perform additional assertions on the variable binding `s`.
/// assert!(s.starts_with("foo"));
/// assert!(s.ends_with("bar"));
/// });
///
/// // Assert that `b` matches the pattern and yield the string `s`.
/// let s = assert_matches!(b, Foo::B(s) => s);
///
/// // Perform an assertion on the value `s`.
/// assert_eq!(s, "foobar");
/// # }
/// ```
#[macro_export]
macro_rules! assert_matches {
( $e:expr , $($pat:pat)|+ ) => {
match $e {
$($pat)|+ => (),
ref e => panic!("assertion failed: `{:?}` does not match `{}`",
e, stringify!($($pat)|+))
}
};
( $e:expr , $($pat:pat)|+ if $cond:expr ) => {
match $e {
$($pat)|+ if $cond => (),
ref e => panic!("assertion failed: `{:?}` does not match `{}`",
e, stringify!($($pat)|+ if $cond))
}
};
( $e:expr , $($pat:pat)|+ => $arm:expr ) => {
match $e {
$($pat)|+ => $arm,
ref e => panic!("assertion failed: `{:?}` does not match `{}`",
e, stringify!($($pat)|+))
}
};
( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr ) => {
match $e {
$($pat)|+ if $cond => $arm,
ref e => panic!("assertion failed: `{:?}` does not match `{}`",
e, stringify!($($pat)|+ if $cond))
}
};
( $e:expr , $($pat:pat)|+ , $($arg:tt)* ) => {
match $e {
$($pat)|+ => (),
ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
e, stringify!($($pat)|+), format_args!($($arg)*))
}
};
( $e:expr , $($pat:pat)|+ if $cond:expr , $($arg:tt)* ) => {
match $e {
$($pat)|+ if $cond => (),
ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
e, stringify!($($pat)|+ if $cond), format_args!($($arg)*))
}
};
( $e:expr , $($pat:pat)|+ => $arm:expr , $($arg:tt)* ) => {
match $e {
$($pat)|+ => $arm,
ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
e, stringify!($($pat)|+), format_args!($($arg)*))
}
};
( $e:expr , $($pat:pat)|+ if $cond:expr => $arm:expr , $($arg:tt)* ) => {
match $e {
$($pat)|+ if $cond => $arm,
ref e => panic!("assertion failed: `{:?}` does not match `{}`: {}",
e, stringify!($($pat)|+ if $cond), format_args!($($arg)*))
}
};
}
/// Asserts that an expression matches a given pattern.
///
/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only enabled
/// in non-optimized builds by default. An optimized build will omit all
/// `debug_assert_matches!` statements unless `-C debug-assertions` is passed
/// to the compiler.
///
/// See the macro [`assert_matches!`] documentation for more information.
///
/// [`assert_matches!`]: macro.assert_matches.html
#[macro_export(local_inner_macros)]
macro_rules! debug_assert_matches {
( $($tt:tt)* ) => { {
if _assert_matches_cfg!(debug_assertions) {
assert_matches!($($tt)*);
}
} }
}
#[doc(hidden)]
#[macro_export]
macro_rules! _assert_matches_cfg {
( $($tt:tt)* ) => { cfg!($($tt)*) }
}
#[cfg(test)]
mod test {
use std::panic::{catch_unwind, UnwindSafe};
#[derive(Debug)]
enum Foo {
A(i32),
B(&'static str),
C(&'static str),
}
#[test]
fn test_assert_succeed() {
let a = Foo::A(123);
assert_matches!(a, Foo::A(_));
assert_matches!(a, Foo::A(123));
assert_matches!(a, Foo::A(i) if i == 123);
assert_matches!(a, Foo::A(42) | Foo::A(123));
let b = Foo::B("foo");
assert_matches!(b, Foo::B(_));
assert_matches!(b, Foo::B("foo"));
assert_matches!(b, Foo::B(s) if s == "foo");
assert_matches!(b, Foo::B(s) => assert_eq!(s, "foo"));
assert_matches!(b, Foo::B(s) => { assert_eq!(s, "foo"); assert!(true) });
assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "foo"));
assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) });
let c = Foo::C("foo");
assert_matches!(c, Foo::B(_) | Foo::C(_));
assert_matches!(c, Foo::B("foo") | Foo::C("foo"));
assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo");
assert_matches!(c, Foo::B(s) | Foo::C(s) => assert_eq!(s, "foo"));
assert_matches!(c, Foo::B(s) | Foo::C(s) => { assert_eq!(s, "foo"); assert!(true) });
assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => assert_eq!(s, "foo"));
assert_matches!(c, Foo::B(s) | Foo::C(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(true) });
}
#[test]
#[should_panic]
fn test_assert_panic_0() {
let a = Foo::A(123);
assert_matches!(a, Foo::B(_));
}
#[test]
#[should_panic]
fn test_assert_panic_1() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B("bar"));
}
#[test]
#[should_panic]
fn test_assert_panic_2() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B(s) if s == "bar");
}
#[test]
#[should_panic]
fn test_assert_panic_3() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B(s) => assert_eq!(s, "bar"));
}
#[test]
#[should_panic]
fn test_assert_panic_4() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B(s) if s == "bar" => assert_eq!(s, "foo"));
}
#[test]
#[should_panic]
fn test_assert_panic_5() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B(s) if s == "foo" => assert_eq!(s, "bar"));
}
#[test]
#[should_panic]
fn test_assert_panic_6() {
let b = Foo::B("foo");
assert_matches!(b, Foo::B(s) if s == "foo" => { assert_eq!(s, "foo"); assert!(false) });
}
#[test]
fn test_assert_no_move() {
let b = &mut Foo::A(0);
assert_matches!(*b, Foo::A(0));
}
#[test]
fn assert_with_message() {
let a = Foo::A(0);
assert_matches!(a, Foo::A(_), "o noes");
assert_matches!(a, Foo::A(n) if n == 0, "o noes");
assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes");
assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes");
assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes");
assert_matches!(a, Foo::A(n) if n == 0 => { assert_eq!(n, 0); assert!(n < 1) }, "o noes");
assert_matches!(a, Foo::A(_), "o noes {:?}", a);
assert_matches!(a, Foo::A(n) if n == 0, "o noes {:?}", a);
assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {:?}", a);
assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {:?}", a);
assert_matches!(a, Foo::A(_), "o noes {value:?}", value=a);
assert_matches!(a, Foo::A(n) if n == 0, "o noes {value:?}", value=a);
assert_matches!(a, Foo::A(n) => assert_eq!(n, 0), "o noes {value:?}", value=a);
assert_matches!(a, Foo::A(n) => { assert_eq!(n, 0); assert!(n < 1) }, "o noes {value:?}", value=a);
assert_matches!(a, Foo::A(n) if n == 0 => assert_eq!(n, 0), "o noes {value:?}", value=a);
}
fn panic_message<F>(f: F) -> String
where F: FnOnce() + UnwindSafe {
let err = catch_unwind(f)
.expect_err("function did not panic");
*err.downcast::<String>()
.expect("function panicked with non-String value")
}
#[test]
fn test_panic_message() {
let a = Foo::A(1);
// expr, pat
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(_));
}), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#);
// expr, pat if cond
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(s) if s == "foo");
}), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#);
// expr, pat => arm
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(_) => {});
}), r#"assertion failed: `A(1)` does not match `Foo::B(_)`"#);
// expr, pat if cond => arm
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(s) if s == "foo" => {});
}), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`"#);
// expr, pat, args
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(_), "msg");
}), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#);
// expr, pat if cond, args
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(s) if s == "foo", "msg");
}), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#);
// expr, pat => arm, args
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(_) => {}, "msg");
}), r#"assertion failed: `A(1)` does not match `Foo::B(_)`: msg"#);
// expr, pat if cond => arm, args
assert_eq!(panic_message(|| {
assert_matches!(a, Foo::B(s) if s == "foo" => {}, "msg");
}), r#"assertion failed: `A(1)` does not match `Foo::B(s) if s == "foo"`: msg"#);
}
}

View File

@ -10,7 +10,7 @@ use ::layout::c::{ Bounds, EekGtkKeyboard, Point };
use ::submission::c::Submission as CSubmission; use ::submission::c::Submission as CSubmission;
use glib::translate::FromGlibPtrNone; use glib::translate::FromGlibPtrNone;
use gtk::WidgetExt; use gtk::prelude::WidgetExt;
use std::collections::HashSet; use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;

View File

@ -29,7 +29,9 @@ use std::time::Instant;
use crate::logging::Warn; use crate::logging::Warn;
/// Type of the sender that waits for external events
type Sender = mpsc::Sender<Event>; type Sender = mpsc::Sender<Event>;
/// Type of the sender that waits for internal state changes
type UISender = glib::Sender<Commands>; type UISender = glib::Sender<Commands>;
/// This loop driver spawns a new thread which updates the state in a loop, /// This loop driver spawns a new thread which updates the state in a loop,

View File

@ -153,6 +153,7 @@ mod test {
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::PanelCommand; use crate::main::PanelCommand;
use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
use crate::state::test::application_with_fake_output;
fn imdetails_new() -> InputMethodDetails { fn imdetails_new() -> InputMethodDetails {
InputMethodDetails { InputMethodDetails {
@ -170,11 +171,12 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
let l = State::new(state, now); let l = State::new(state, now);
let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now); let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
assert_eq!(commands.panel_visibility, Some(PanelCommand::Show)); assert_matches!(commands.panel_visibility, Some(PanelCommand::Show{..}));
assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT)); assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
now += animation::HIDING_TIMEOUT; now += animation::HIDING_TIMEOUT;

View File

@ -26,21 +26,32 @@ pub mod c {
use super::*; use super::*;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::ptr;
// The following defined in C // The following defined in C
/// struct zwp_input_method_v2* /// struct zwp_input_method_v2*
#[repr(transparent)] #[repr(transparent)]
#[derive(PartialEq, Clone, Copy)]
pub struct InputMethod(*const c_void); pub struct InputMethod(*const c_void);
impl InputMethod {
pub fn is_null(&self) -> bool {
self.0.is_null()
}
pub fn null() -> Self {
Self(ptr::null())
}
}
extern "C" { extern "C" {
fn imservice_destroy_im(im: *mut c::InputMethod); fn imservice_destroy_im(im: InputMethod);
#[allow(improper_ctypes)] // IMService will never be dereferenced in C #[allow(improper_ctypes)] // IMService will never be dereferenced in C
pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService); pub fn imservice_connect_listeners(im: InputMethod, imservice: *const IMService);
pub fn eek_input_method_commit_string(im: *mut InputMethod, text: *const c_char); pub fn eek_input_method_commit_string(im: InputMethod, text: *const c_char);
pub fn eek_input_method_delete_surrounding_text(im: *mut InputMethod, before: u32, after: u32); pub fn eek_input_method_delete_surrounding_text(im: InputMethod, before: u32, after: u32);
pub fn eek_input_method_commit(im: *mut InputMethod, serial: u32); pub fn eek_input_method_commit(im: InputMethod, serial: u32);
} }
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
@ -49,7 +60,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_input_method_activate(imservice: *mut IMService, fn imservice_handle_input_method_activate(imservice: *mut IMService,
im: *const InputMethod) im: InputMethod)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
imservice.preedit_string = String::new(); imservice.preedit_string = String::new();
@ -62,7 +73,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_input_method_deactivate(imservice: *mut IMService, fn imservice_handle_input_method_deactivate(imservice: *mut IMService,
im: *const InputMethod) im: InputMethod)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
imservice.pending = IMProtocolState { imservice.pending = IMProtocolState {
@ -74,7 +85,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_surrounding_text(imservice: *mut IMService, fn imservice_handle_surrounding_text(imservice: *mut IMService,
im: *const InputMethod, im: InputMethod,
text: *const c_char, cursor: u32, _anchor: u32) text: *const c_char, cursor: u32, _anchor: u32)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
@ -90,7 +101,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_content_type(imservice: *mut IMService, fn imservice_handle_content_type(imservice: *mut IMService,
im: *const InputMethod, im: InputMethod,
hint: u32, purpose: u32) hint: u32, purpose: u32)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
@ -118,7 +129,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_text_change_cause(imservice: *mut IMService, fn imservice_handle_text_change_cause(imservice: *mut IMService,
im: *const InputMethod, im: InputMethod,
cause: u32) cause: u32)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
@ -138,16 +149,11 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_done(imservice: *mut IMService, fn imservice_handle_done(imservice: *mut IMService,
im: *const InputMethod) im: InputMethod)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
imservice.current = imservice.pending.clone(); imservice.current = imservice.pending.clone();
imservice.pending = IMProtocolState {
active: imservice.current.active,
..IMProtocolState::default()
};
imservice.serial += Wrapping(1u32); imservice.serial += Wrapping(1u32);
imservice.send_event(); imservice.send_event();
} }
@ -156,7 +162,7 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn imservice_handle_unavailable(imservice: *mut IMService, fn imservice_handle_unavailable(imservice: *mut IMService,
im: *mut InputMethod) im: InputMethod)
{ {
let imservice = check_imservice(imservice, im).unwrap(); let imservice = check_imservice(imservice, im).unwrap();
unsafe { imservice_destroy_im(im); } unsafe { imservice_destroy_im(im); }
@ -181,7 +187,7 @@ pub mod c {
/// Care must be take /// Care must be take
/// not to exceed the lifetime of the pointer with the reference, /// not to exceed the lifetime of the pointer with the reference,
/// especially not to store it. /// especially not to store it.
fn check_imservice(imservice: *mut IMService, im: *const InputMethod) fn check_imservice(imservice: *mut IMService, im: InputMethod)
-> Result<&'static mut IMService, &'static str> -> Result<&'static mut IMService, &'static str>
{ {
if imservice.is_null() { if imservice.is_null() {
@ -315,7 +321,7 @@ impl Default for IMProtocolState {
pub struct IMService { pub struct IMService {
/// Owned reference (still created and destroyed in C) /// Owned reference (still created and destroyed in C)
pub im: *mut c::InputMethod, pub im: c::InputMethod,
sender: driver::Threaded, sender: driver::Threaded,
pending: IMProtocolState, pending: IMProtocolState,
@ -331,7 +337,7 @@ pub enum SubmitError {
impl IMService { impl IMService {
pub fn new( pub fn new(
im: *mut c::InputMethod, im: c::InputMethod,
sender: driver::Threaded, sender: driver::Threaded,
) -> Box<IMService> { ) -> Box<IMService> {
// IMService will be referenced to by C, // IMService will be referenced to by C,

View File

@ -11,10 +11,12 @@ extern crate gtk_sys;
#[allow(unused_imports)] #[allow(unused_imports)]
#[macro_use] // only for tests #[macro_use] // only for tests
extern crate maplit; extern crate maplit;
extern crate regex;
extern crate serde; extern crate serde;
extern crate xkbcommon; extern crate xkbcommon;
#[cfg(test)]
#[macro_use]
mod assert_matches;
#[macro_use] #[macro_use]
mod logging; mod logging;
@ -38,6 +40,5 @@ mod style;
mod submission; mod submission;
pub mod tests; pub mod tests;
pub mod util; pub mod util;
mod ui_manager;
mod vkeyboard; mod vkeyboard;
mod xdg; mod xdg;

View File

@ -21,11 +21,12 @@ struct rsobjects {
struct receiver *receiver; struct receiver *receiver;
struct squeek_state_manager *state_manager; struct squeek_state_manager *state_manager;
struct submission *submission; struct submission *submission;
struct squeek_wayland *wayland;
}; };
void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler); void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler);
struct rsobjects squeek_rsobjects_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk); struct rsobjects squeek_init(void);
void squeek_state_send_force_visible(struct squeek_state_manager *state); void squeek_state_send_force_visible(struct squeek_state_manager *state);
void squeek_state_send_force_hidden(struct squeek_state_manager *state); void squeek_state_send_force_hidden(struct squeek_state_manager *state);

View File

@ -1,9 +1,9 @@
/* Copyright (C) 2020 Purism SPC /* Copyright (C) 2020,2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+ * SPDX-License-Identifier: GPL-3.0+
*/ */
/*! Glue for the main loop. */ /*! Glue for the main loop. */
use crate::outputs::OutputId;
use crate::state; use crate::state;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
@ -11,12 +11,15 @@ use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
mod c { mod c {
use super::*; use super::*;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::ptr;
use std::rc::Rc; use std::rc::Rc;
use std::time::Instant; use std::time::Instant;
use crate::event_loop::driver; use crate::event_loop::driver;
use crate::imservice::IMService; use crate::imservice::IMService;
use crate::imservice::c::InputMethod; use crate::imservice::c::InputMethod;
use crate::outputs::Outputs;
use crate::outputs::c::WlOutput;
use crate::state; use crate::state;
use crate::submission::Submission; use crate::submission::Submission;
use crate::util::c::Wrapped; use crate::util::c::Wrapped;
@ -33,13 +36,46 @@ mod c {
/// Holds the Rust structures that are interesting from C. /// Holds the Rust structures that are interesting from C.
#[repr(C)] #[repr(C)]
pub struct RsObjects { pub struct RsObjects {
/// The handle to which Commands should be sent
/// for processing in the main loop.
receiver: Wrapped<Receiver<Commands>>, receiver: Wrapped<Receiver<Commands>>,
state_manager: Wrapped<driver::Threaded>, state_manager: Wrapped<driver::Threaded>,
submission: Wrapped<Submission>, submission: Wrapped<Submission>,
/// Not wrapped, because C needs to access this.
wayland: *mut Wayland,
}
/// Corresponds to wayland.h::squeek_wayland.
/// Fields unused by Rust are marked as generic data types.
#[repr(C)]
pub struct Wayland {
layer_shell: *const c_void,
virtual_keyboard_manager: *const c_void,
input_method_manager: *const c_void,
outputs: Wrapped<Outputs>,
seat: *const c_void,
input_method: InputMethod,
virtual_keyboard: ZwpVirtualKeyboardV1,
}
impl Wayland {
fn new(outputs_manager: Outputs) -> Self {
Wayland {
layer_shell: ptr::null(),
virtual_keyboard_manager: ptr::null(),
input_method_manager: ptr::null(),
outputs: Wrapped::new(outputs_manager),
seat: ptr::null(),
input_method: InputMethod::null(),
virtual_keyboard: ZwpVirtualKeyboardV1::null(),
}
}
} }
extern "C" { extern "C" {
fn server_context_service_real_show_keyboard(service: *const UIManager); #[allow(improper_ctypes)]
fn init_wayland(wayland: *mut Wayland);
fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, scaled_height: u32);
fn server_context_service_real_hide_keyboard(service: *const UIManager); fn server_context_service_real_hide_keyboard(service: *const UIManager);
fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32); fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32);
// This should probably only get called from the gtk main loop, // This should probably only get called from the gtk main loop,
@ -52,19 +88,23 @@ mod c {
/// and that leads to suffering. /// and that leads to suffering.
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn squeek_rsobjects_new( fn squeek_init() -> RsObjects {
im: *mut InputMethod, // Set up channels
vk: ZwpVirtualKeyboardV1,
) -> RsObjects {
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT); let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
let now = Instant::now(); let now = Instant::now();
let state_manager = driver::Threaded::new(sender, state::Application::new(now)); let state_manager = driver::Threaded::new(sender, state::Application::new(now));
let imservice = if im.is_null() { let outputs = Outputs::new(state_manager.clone());
let mut wayland = Box::new(Wayland::new(outputs));
let wayland_raw = &mut *wayland as *mut _;
unsafe { init_wayland(wayland_raw); }
let vk = wayland.virtual_keyboard;
let imservice = if wayland.input_method.is_null() {
None None
} else { } else {
Some(IMService::new(im, state_manager.clone())) Some(IMService::new(wayland.input_method, state_manager.clone()))
}; };
let submission = Submission::new(vk, imservice); let submission = Submission::new(vk, imservice);
@ -72,6 +112,7 @@ mod c {
submission: Wrapped::new(submission), submission: Wrapped::new(submission),
state_manager: Wrapped::new(state_manager), state_manager: Wrapped::new(state_manager),
receiver: Wrapped::new(receiver), receiver: Wrapped::new(receiver),
wayland: Box::into_raw(wayland),
} }
} }
@ -87,7 +128,7 @@ mod c {
let receiver = Rc::try_unwrap(receiver).expect("References still present"); let receiver = Rc::try_unwrap(receiver).expect("References still present");
let receiver = receiver.into_inner(); let receiver = receiver.into_inner();
let ctx = MainContext::default(); let ctx = MainContext::default();
ctx.acquire(); let _acqu = ctx.acquire();
receiver.attach( receiver.attach(
Some(&ctx), Some(&ctx),
move |msg| { move |msg| {
@ -95,6 +136,7 @@ mod c {
Continue(true) Continue(true)
}, },
); );
#[cfg(not(feature = "glib_v0_14"))]
ctx.release(); ctx.release();
} }
@ -108,8 +150,8 @@ mod c {
dbus_handler: *const DBusHandler, dbus_handler: *const DBusHandler,
) { ) {
match msg.panel_visibility { match msg.panel_visibility {
Some(PanelCommand::Show) => unsafe { Some(PanelCommand::Show { output, height }) => unsafe {
server_context_service_real_show_keyboard(ui_manager); server_context_service_update_keyboard(ui_manager, output.0, height.as_scaled_ceiling());
}, },
Some(PanelCommand::Hide) => unsafe { Some(PanelCommand::Hide) => unsafe {
server_context_service_real_hide_keyboard(ui_manager); server_context_service_real_hide_keyboard(ui_manager);
@ -118,8 +160,10 @@ mod c {
}; };
if let Some(visible) = msg.dbus_visible_set { if let Some(visible) = msg.dbus_visible_set {
if dbus_handler != std::ptr::null() {
unsafe { dbus_handler_set_visible(dbus_handler, visible as u8) }; unsafe { dbus_handler_set_visible(dbus_handler, visible as u8) };
} }
}
if let Some(hints) = msg.layout_hint_set { if let Some(hints) = msg.layout_hint_set {
unsafe { unsafe {
@ -133,9 +177,34 @@ mod c {
} }
} }
/// Size in pixels that is aware of scaling
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PixelSize {
pub pixels: u32,
pub scale_factor: u32,
}
fn div_ceil(a: u32, b: u32) -> u32 {
// Given that it's for pixels on a screen, an overflow is unlikely.
(a + b - 1) / b
}
impl PixelSize {
pub fn as_scaled_floor(&self) -> u32 {
self.pixels / self.scale_factor
}
pub fn as_scaled_ceiling(&self) -> u32 {
div_ceil(self.pixels, self.scale_factor)
}
}
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum PanelCommand { pub enum PanelCommand {
Show, Show {
output: OutputId,
height: PixelSize,
},
Hide, Hide,
} }

View File

@ -11,7 +11,8 @@ struct squeek_output_handle {
struct squeek_outputs *squeek_outputs_new(void); struct squeek_outputs *squeek_outputs_new(void);
void squeek_outputs_free(struct squeek_outputs*); void squeek_outputs_free(struct squeek_outputs*);
void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output); void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output, uint32_t id);
struct wl_output *squeek_outputs_try_unregister(struct squeek_outputs*, uint32_t id);
struct squeek_output_handle squeek_outputs_get_current(struct squeek_outputs*); struct squeek_output_handle squeek_outputs_get_current(struct squeek_outputs*);
int32_t squeek_outputs_get_perceptual_width(struct squeek_outputs*, struct wl_output *output); int32_t squeek_outputs_get_perceptual_width(struct squeek_outputs*, struct wl_output *output);
#endif #endif

View File

@ -1,6 +1,11 @@
/* Copyright (C) 2019-2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Managing Wayland outputs */ /*! Managing Wayland outputs */
use std::vec::Vec; use std::vec::Vec;
use crate::event_loop;
use ::logging; use ::logging;
// traits // traits
@ -11,15 +16,22 @@ pub mod c {
use super::*; use super::*;
use std::os::raw::{ c_char, c_void }; use std::os::raw::{ c_char, c_void };
use std::ptr;
use ::util::c::COpaquePtr; use ::util::c::{COpaquePtr, Wrapped};
// Defined in C // Defined in C
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, PartialEq, Copy)] #[derive(Clone, PartialEq, Copy, Debug, Eq, Hash)]
pub struct WlOutput(*const c_void); pub struct WlOutput(*const c_void);
impl WlOutput {
fn null() -> Self {
Self(ptr::null())
}
}
#[repr(C)] #[repr(C)]
struct WlOutputListener<T: COpaquePtr> { struct WlOutputListener<T: COpaquePtr> {
geometry: extern fn( geometry: extern fn(
@ -63,7 +75,7 @@ pub mod c {
} }
/// Map to `wl_output.transform` values /// Map to `wl_output.transform` values
#[derive(Clone)] #[derive(Clone, Copy, Debug)]
pub enum Transform { pub enum Transform {
Normal = 0, Normal = 0,
Rotated90 = 1, Rotated90 = 1,
@ -103,28 +115,13 @@ pub mod c {
) -> i32; ) -> i32;
} }
type COutputs = ::util::c::Wrapped<Outputs>; /// Wrapping Outputs is required for calling its methods from C
type COutputs = Wrapped<Outputs>;
/// A stable reference to an output.
#[derive(Clone)]
#[repr(C)]
pub struct OutputHandle {
wl_output: WlOutput,
outputs: COutputs,
}
impl OutputHandle {
// Cannot return an Output reference
// because COutputs is too deeply wrapped
pub fn get_state(&self) -> Option<OutputState> {
let outputs = self.outputs.clone_ref();
let outputs = outputs.borrow();
find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone())
}
}
// Defined in Rust // Defined in Rust
// Callbacks from the output listener follow
extern fn outputs_handle_geometry( extern fn outputs_handle_geometry(
outputs: COutputs, outputs: COutputs,
wl_output: WlOutput, wl_output: WlOutput,
@ -143,7 +140,8 @@ pub mod c {
let outputs = outputs.clone_ref(); let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut(); let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState> let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output) = collection
.find_output_mut(wl_output)
.map(|o| &mut o.pending); .map(|o| &mut o.pending);
match output_state { match output_state {
Some(state) => { state.transform = Some(transform) }, Some(state) => { state.transform = Some(transform) },
@ -171,7 +169,8 @@ pub mod c {
let outputs = outputs.clone_ref(); let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut(); let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState> let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output) = collection
.find_output_mut(wl_output)
.map(|o| &mut o.pending); .map(|o| &mut o.pending);
match output_state { match output_state {
Some(state) => { Some(state) => {
@ -192,14 +191,27 @@ pub mod c {
) { ) {
let outputs = outputs.clone_ref(); let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut(); let mut collection = outputs.borrow_mut();
let output = find_output_mut(&mut collection, wl_output); let output = collection
match output { .find_output_mut(wl_output);
Some(output) => { output.current = output.pending.clone(); } let event = match output {
None => log_print!( Some(output) => {
output.current = output.pending.clone();
Some(Event {
output: OutputId(wl_output),
change: ChangeType::Altered(output.current),
})
},
None => {
log_print!(
logging::Level::Warning, logging::Level::Warning,
"Got done on unknown output", "Got done on unknown output",
), );
None
}
}; };
if let Some(event) = event {
collection.send_event(event);
}
} }
extern fn outputs_handle_scale( extern fn outputs_handle_scale(
@ -210,7 +222,8 @@ pub mod c {
let outputs = outputs.clone_ref(); let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut(); let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState> let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output) = collection
.find_output_mut(wl_output)
.map(|o| &mut o.pending); .map(|o| &mut o.pending);
match output_state { match output_state {
Some(state) => { state.scale = factor; } Some(state) => { state.scale = factor; }
@ -221,11 +234,7 @@ pub mod c {
}; };
} }
#[no_mangle] // End callbacks
pub extern "C"
fn squeek_outputs_new() -> COutputs {
COutputs::new(Outputs { outputs: Vec::new() })
}
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
@ -235,14 +244,17 @@ pub mod c {
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput) { fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput, id: u32) {
let collection = raw_collection.clone_ref(); let collection = raw_collection.clone_ref();
let mut collection = collection.borrow_mut(); let mut collection = collection.borrow_mut();
collection.outputs.push(Output { collection.outputs.push((
Output {
output: output.clone(), output: output.clone(),
pending: OutputState::uninitialized(), pending: OutputState::uninitialized(),
current: OutputState::uninitialized(), current: OutputState::uninitialized(),
}); },
id,
));
unsafe { squeek_output_add_listener( unsafe { squeek_output_add_listener(
output, output,
@ -256,40 +268,22 @@ pub mod c {
)}; )};
} }
/// This will try to unregister the output, if the id matches a registered one.
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle { fn squeek_outputs_try_unregister(raw_collection: COutputs, id: u32) -> WlOutput {
let collection = raw_collection.clone_ref(); let collection = raw_collection.clone_ref();
let collection = collection.borrow(); let mut collection = collection.borrow_mut();
OutputHandle { collection.remove_output_by_global(id)
wl_output: collection.outputs[0].output.clone(), .map_err(|e| log_print!(
outputs: raw_collection.clone(), logging::Level::Debug,
} "Tried to remove global {:x} but it is not registered as an output: {:?}",
id, e,
))
.unwrap_or(WlOutput::null())
} }
// TODO: handle unregistration // TODO: handle unregistration
fn find_output(
collection: &Outputs,
wl_output: WlOutput,
) -> Option<&Output> {
collection.outputs
.iter()
.find_map(|o|
if o.output == wl_output { Some(o) } else { None }
)
}
fn find_output_mut(
collection: &mut Outputs,
wl_output: WlOutput,
) -> Option<&mut Output> {
collection.outputs
.iter_mut()
.find_map(|o|
if o.output == wl_output { Some(o) } else { None }
)
}
} }
/// Generic size /// Generic size
@ -300,16 +294,16 @@ pub struct Size {
} }
/// wl_output mode /// wl_output mode
#[derive(Clone)] #[derive(Clone, Copy, Debug)]
struct Mode { pub struct Mode {
width: i32, width: i32,
height: i32, height: i32,
} }
#[derive(Clone)] #[derive(Clone, Copy, Debug)]
pub struct OutputState { pub struct OutputState {
current_mode: Option<Mode>, pub current_mode: Option<Mode>,
transform: Option<c::Transform>, pub transform: Option<c::Transform>,
pub scale: i32, pub scale: i32,
} }
@ -355,12 +349,83 @@ impl OutputState {
} }
} }
pub struct Output { /// Not guaranteed to exist,
/// but can be used to look up state.
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash)]
pub struct OutputId(pub c::WlOutput);
// WlOutput is a pointer,
// but in the public interface,
// we're only using it as a lookup key.
unsafe impl Send for OutputId {}
struct Output {
output: c::WlOutput, output: c::WlOutput,
pending: OutputState, pending: OutputState,
current: OutputState, current: OutputState,
} }
#[derive(Debug)]
struct NotFound;
/// Wayland global ID type
type GlobalId = u32;
/// The outputs manager
pub struct Outputs { pub struct Outputs {
outputs: Vec<Output>, outputs: Vec<(Output, GlobalId)>,
sender: event_loop::driver::Threaded,
}
impl Outputs {
pub fn new(sender: event_loop::driver::Threaded) -> Outputs {
Outputs {
outputs: Vec::new(),
sender,
}
}
fn send_event(&self, event: Event) {
self.sender.send(event.into()).unwrap()
}
fn remove_output_by_global(&mut self, id: GlobalId)
-> Result<c::WlOutput, NotFound>
{
let index = self.outputs.iter()
.position(|(_o, global_id)| *global_id == id);
if let Some(index) = index {
let (output, _id) = self.outputs.remove(index);
self.send_event(Event {
change: ChangeType::Removed,
output: OutputId(output.output),
});
Ok(output.output)
} else {
Err(NotFound)
}
}
fn find_output_mut(&mut self, wl_output: c::WlOutput)
-> Option<&mut Output>
{
self.outputs
.iter_mut()
.find_map(|(o, _global)|
if o.output == wl_output { Some(o) } else { None }
)
}
}
#[derive(Clone, Copy, Debug)]
pub enum ChangeType {
/// Added or changed
Altered(OutputState),
Removed,
}
#[derive(Clone, Copy, Debug)]
pub struct Event {
pub output: OutputId,
pub change: ChangeType,
} }

View File

@ -11,16 +11,11 @@ use ::manager;
use ::resources; use ::resources;
// Traits // Traits
use gio::ActionMapExt; use gio::prelude::ActionMapExt;
use gio::SettingsExt; use gio::prelude::SettingsExt;
#[cfg(feature = "gio_v0_5")]
use gio::SimpleActionExt;
use glib::translate::FromGlibPtrNone; use glib::translate::FromGlibPtrNone;
use glib::variant::ToVariant; use glib::variant::ToVariant;
#[cfg(not(feature = "gtk_v0_5"))]
use gtk::prelude::*; use gtk::prelude::*;
use gtk::PopoverExt;
use gtk::WidgetExt;
use ::logging::Warn; use ::logging::Warn;
mod c { mod c {
@ -110,8 +105,13 @@ mod variants {
fn get_settings(schema_name: &str) -> Option<gio::Settings> { fn get_settings(schema_name: &str) -> Option<gio::Settings> {
let mut error_handler = logging::Print{}; let mut error_handler = logging::Print{};
gio::SettingsSchemaSource::get_default()
.or_warn( #[cfg(feature = "glib_v0_14")]
let ss = gio::SettingsSchemaSource::default();
#[cfg(not(feature = "glib_v0_14"))]
let ss = gio::SettingsSchemaSource::get_default();
ss.or_warn(
&mut error_handler, &mut error_handler,
logging::Problem::Surprise, logging::Problem::Surprise,
"No gsettings schemas installed.", "No gsettings schemas installed.",
@ -130,7 +130,11 @@ fn get_settings(schema_name: &str) -> Option<gio::Settings> {
fn set_layout(kind: String, name: String) { fn set_layout(kind: String, name: String) {
let settings = get_settings("org.gnome.desktop.input-sources"); let settings = get_settings("org.gnome.desktop.input-sources");
if let Some(settings) = settings { if let Some(settings) = settings {
#[cfg(feature = "glib_v0_14")]
let inputs = settings.value("sources");
#[cfg(not(feature = "glib_v0_14"))]
let inputs = settings.get_value("sources").unwrap(); let inputs = settings.get_value("sources").unwrap();
let current = (kind.clone(), name.clone()); let current = (kind.clone(), name.clone());
let inputs = variants::get_tuples(inputs).into_iter() let inputs = variants::get_tuples(inputs).into_iter()
.filter(|t| t != &current); .filter(|t| t != &current);
@ -254,7 +258,11 @@ pub fn show(
let settings = get_settings("org.gnome.desktop.input-sources"); let settings = get_settings("org.gnome.desktop.input-sources");
let inputs = settings let inputs = settings
.map(|settings| { .map(|settings| {
#[cfg(feature = "glib_v0_14")]
let inputs = settings.value("sources");
#[cfg(not(feature = "glib_v0_14"))]
let inputs = settings.get_value("sources").unwrap(); let inputs = settings.get_value("sources").unwrap();
variants::get_tuples(inputs) variants::get_tuples(inputs)
}) })
.unwrap_or_else(|| Vec::new()); .unwrap_or_else(|| Vec::new());
@ -285,8 +293,18 @@ pub fn show(
} }
}); });
let model: gio::Menu = {
#[cfg(feature = "glib_v0_14")]
{
let builder = gtk::Builder::from_resource("/sm/puri/squeekboard/popover.ui");
builder.object("app-menu").unwrap()
}
#[cfg(not(feature = "glib_v0_14"))]
{
let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui"); let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui");
let model: gio::Menu = builder.get_object("app-menu").unwrap(); builder.get_object("app-menu").unwrap()
}
};
for (tr, l) in human_names.iter().rev() { for (tr, l) in human_names.iter().rev() {
let detailed_action = format!("layout::{}", l.get_name()); let detailed_action = format!("layout::{}", l.get_name());
@ -294,7 +312,11 @@ pub fn show(
model.prepend_item (&item); model.prepend_item (&item);
} }
#[cfg(feature = "glib_v0_14")]
let menu = gtk::Popover::from_model(Some(&window), &model);
#[cfg(not(feature = "glib_v0_14"))]
let menu = gtk::Popover::new_from_model(Some(&window), &model); let menu = gtk::Popover::new_from_model(Some(&window), &model);
menu.set_pointing_to(&gtk::Rectangle { menu.set_pointing_to(&gtk::Rectangle {
x: position.x.ceil() as i32, x: position.x.ceil() as i32,
y: position.y.ceil() as i32, y: position.y.ceil() as i32,

View File

@ -71,6 +71,9 @@ static KEYBOARDS: &[(&'static str, &'static str)] = &[
("pl", include_str!("../data/keyboards/pl.yaml")), ("pl", include_str!("../data/keyboards/pl.yaml")),
("pl_wide", include_str!("../data/keyboards/pl_wide.yaml")), ("pl_wide", include_str!("../data/keyboards/pl_wide.yaml")),
("ro", include_str!("../data/keyboards/ro.yaml")),
("ro_wide", include_str!("../data/keyboards/ro_wide.yaml")),
("ru", include_str!("../data/keyboards/ru.yaml")), ("ru", include_str!("../data/keyboards/ru.yaml")),
("se", include_str!("../data/keyboards/se.yaml")), ("se", include_str!("../data/keyboards/se.yaml")),

View File

@ -27,6 +27,7 @@
#include "submission.h" #include "submission.h"
#include "wayland.h" #include "wayland.h"
#include "server-context-service.h" #include "server-context-service.h"
#include "wayland-client-protocol.h"
enum { enum {
PROP_0, PROP_0,
@ -41,11 +42,12 @@ struct _ServerContextService {
/// Needed for instantiating the widget /// Needed for instantiating the widget
struct submission *submission; // unowned struct submission *submission; // unowned
struct squeek_layout_state *layout; struct squeek_layout_state *layout;
struct ui_manager *manager; // unowned
struct squeek_state_manager *state_manager; // shared reference struct squeek_state_manager *state_manager; // shared reference
PhoshLayerSurface *window; PhoshLayerSurface *window;
GtkWidget *widget; // nullable GtkWidget *widget; // nullable
struct wl_output *current_output;
guint last_requested_height; guint last_requested_height;
}; };
@ -64,96 +66,17 @@ on_destroy (ServerContextService *self, GtkWidget *widget)
//eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context)); //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
} }
static uint32_t
calculate_height(int32_t width, GdkRectangle *geometry)
{
uint32_t height;
if (geometry->width > geometry->height) {
// 1:5 ratio works fine on lanscape mode, and makes sure there's
// room left for the app window
height = width / 5;
} else {
if (width < 540 && width > 0) {
height = ((unsigned)width * 7 / 12); // to match 360×210
} else {
// Here we switch to wide layout, less height needed
height = ((unsigned)width * 7 / 22);
}
}
return height;
}
static void static void
on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface) make_window (ServerContextService *self, struct wl_output *output, uint32_t height)
{
GdkDisplay *display = NULL;
GdkWindow *window = NULL;
GdkMonitor *monitor = NULL;
GdkRectangle geometry;
gint width;
gint height;
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
g_return_if_fail (PHOSH_IS_LAYER_SURFACE (surface));
g_object_get(G_OBJECT(surface),
"configured-width", &width,
"configured-height", &height,
NULL);
// In order to improve height calculation, we need the monitor geometry so
// we can use different algorithms for portrait and landscape mode.
// Note: this is a temporary fix until the size manager is complete.
display = gdk_display_get_default ();
if (display) {
window = gtk_widget_get_window (GTK_WIDGET (surface));
}
if (window) {
monitor = gdk_display_get_monitor_at_window (display, window);
}
if (monitor) {
gdk_monitor_get_geometry (monitor, &geometry);
} else {
geometry.width = geometry.height = 0;
}
// When the geometry event comes after surface.configure,
// this entire height calculation does nothing.
// guint desired_height = squeek_uiman_get_perceptual_height(context->manager);
// Temporarily use old method, until the size manager is complete.
guint desired_height = calculate_height(width, &geometry);
guint configured_height = (guint)height;
// if height was already requested once but a different one was given
// (for the same set of surrounding properties),
// then it's probably not reasonable to ask for it again,
// as it's likely to create pointless loops
// of request->reject->request_again->...
if (desired_height != configured_height
&& self->last_requested_height != desired_height) {
self->last_requested_height = desired_height;
phosh_layer_surface_set_size(surface, 0,
(gint)desired_height);
phosh_layer_surface_set_exclusive_zone(surface, (gint)desired_height);
phosh_layer_surface_wl_surface_commit (surface);
}
}
static void
make_window (ServerContextService *self)
{ {
if (self->window) { if (self->window) {
g_error("Window already present"); g_error("Window already present");
} }
struct squeek_output_handle output = squeek_outputs_get_current(squeek_wayland->outputs);
squeek_uiman_set_output(self->manager, output);
uint32_t height = squeek_uiman_get_perceptual_height(self->manager);
self->window = g_object_new ( self->window = g_object_new (
PHOSH_TYPE_LAYER_SURFACE, PHOSH_TYPE_LAYER_SURFACE,
"layer-shell", squeek_wayland->layer_shell, "layer-shell", squeek_wayland->layer_shell,
"wl-output", output.output, "wl-output", output,
"height", height, "height", height,
"anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
@ -167,7 +90,7 @@ make_window (ServerContextService *self)
g_object_connect (self->window, g_object_connect (self->window,
"swapped-signal::destroy", G_CALLBACK(on_destroy), self, "swapped-signal::destroy", G_CALLBACK(on_destroy), self,
"swapped-signal::configured", G_CALLBACK(on_surface_configure), self, //"swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
NULL); NULL);
// The properties below are just to make hacking easier. // The properties below are just to make hacking easier.
@ -204,11 +127,50 @@ make_widget (ServerContextService *self)
} }
// Called from rust // Called from rust
/// Updates the type of hiddenness
void void
server_context_service_real_show_keyboard (ServerContextService *self) server_context_service_real_hide_keyboard (ServerContextService *self)
{ {
//self->desired_height = 0;
self->current_output = NULL;
if (self->window) {
gtk_widget_hide (GTK_WIDGET(self->window));
}
}
// Called from rust
/// Updates the type of visibility.
/// Height is in scaled units.
void
server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t scaled_height)
{
if (output != self->current_output) {
// Recreate on a new output
server_context_service_real_hide_keyboard(self);
} else {
gint h;
PhoshLayerSurface *surface = self->window;
g_object_get(G_OBJECT(surface),
"configured-height", &h,
NULL);
if ((uint32_t)h != scaled_height) {
//TODO: make sure that redrawing happens in the correct place (it doesn't now).
phosh_layer_surface_set_size(self->window, 0, scaled_height);
phosh_layer_surface_set_exclusive_zone(self->window, scaled_height);
phosh_layer_surface_wl_surface_commit(self->window);
self->current_output = output;
return;
}
}
self->current_output = output;
if (!self->window) { if (!self->window) {
make_window (self); make_window (self, output, scaled_height);
} }
if (!self->widget) { if (!self->widget) {
make_widget (self); make_widget (self);
@ -216,14 +178,6 @@ server_context_service_real_show_keyboard (ServerContextService *self)
gtk_widget_show (GTK_WIDGET(self->window)); gtk_widget_show (GTK_WIDGET(self->window));
} }
// Called from rust
void
server_context_service_real_hide_keyboard (ServerContextService *self)
{
if (self->window) {
gtk_widget_hide (GTK_WIDGET(self->window));
}
}
static void static void
server_context_service_set_property (GObject *object, server_context_service_set_property (GObject *object,
@ -318,13 +272,12 @@ init (ServerContextService *self) {
} }
ServerContextService * ServerContextService *
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager) server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager)
{ {
ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL); ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
ui->submission = submission; ui->submission = submission;
ui->state = self; ui->state = self;
ui->layout = layout; ui->layout = layout;
ui->manager = uiman;
ui->state_manager = state_manager; ui->state_manager = state_manager;
init(ui); init(ui);
return ui; return ui;

View File

@ -20,7 +20,6 @@
#include "src/layout.h" #include "src/layout.h"
#include "src/submission.h" #include "src/submission.h"
#include "ui_manager.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -29,10 +28,8 @@ G_BEGIN_DECLS
/** Manages the lifecycle of the window displaying layouts. */ /** Manages the lifecycle of the window displaying layouts. */
G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject) G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject)
ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager); ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager);
enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *); enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *);
void server_context_service_force_show_keyboard (ServerContextService *self);
void server_context_service_hide_keyboard (ServerContextService *self);
G_END_DECLS G_END_DECLS
#endif /* SERVER_CONTEXT_SERVICE_H */ #endif /* SERVER_CONTEXT_SERVICE_H */

View File

@ -33,7 +33,6 @@
#include "outputs.h" #include "outputs.h"
#include "submission.h" #include "submission.h"
#include "server-context-service.h" #include "server-context-service.h"
#include "ui_manager.h"
#include "wayland.h" #include "wayland.h"
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
@ -56,8 +55,6 @@ struct squeekboard {
ServerContextService *ui_context; // mess, includes the entire UI ServerContextService *ui_context; // mess, includes the entire UI
/// Currently wanted layout. TODO: merge into state::Application /// Currently wanted layout. TODO: merge into state::Application
struct squeek_layout_state layout_choice; struct squeek_layout_state layout_choice;
/// UI shape tracker/chooser. TODO: merge into state::Application
struct ui_manager *ui_manager;
}; };
@ -112,34 +109,38 @@ registry_handle_global (void *data,
// Even when lower version would be served, it would not be supported, // Even when lower version would be served, it would not be supported,
// causing a hard exit // causing a hard exit
(void)version; (void)version;
struct squeekboard *instance = data; struct squeek_wayland *wayland = data;
if (!strcmp (interface, zwlr_layer_shell_v1_interface.name)) { if (!strcmp (interface, zwlr_layer_shell_v1_interface.name)) {
instance->wayland.layer_shell = wl_registry_bind (registry, name, wayland->layer_shell = wl_registry_bind (registry, name,
&zwlr_layer_shell_v1_interface, 1); &zwlr_layer_shell_v1_interface, 1);
} else if (!strcmp (interface, zwp_virtual_keyboard_manager_v1_interface.name)) { } else if (!strcmp (interface, zwp_virtual_keyboard_manager_v1_interface.name)) {
instance->wayland.virtual_keyboard_manager = wl_registry_bind(registry, name, wayland->virtual_keyboard_manager = wl_registry_bind(registry, name,
&zwp_virtual_keyboard_manager_v1_interface, 1); &zwp_virtual_keyboard_manager_v1_interface, 1);
} else if (!strcmp (interface, zwp_input_method_manager_v2_interface.name)) { } else if (!strcmp (interface, zwp_input_method_manager_v2_interface.name)) {
instance->wayland.input_method_manager = wl_registry_bind(registry, name, wayland->input_method_manager = wl_registry_bind(registry, name,
&zwp_input_method_manager_v2_interface, 1); &zwp_input_method_manager_v2_interface, 1);
} else if (!strcmp (interface, "wl_output")) { } else if (!strcmp (interface, "wl_output")) {
struct wl_output *output = wl_registry_bind (registry, name, struct wl_output *output = wl_registry_bind (registry, name,
&wl_output_interface, 2); &wl_output_interface, 2);
squeek_outputs_register(instance->wayland.outputs, output); squeek_outputs_register(wayland->outputs, output, name);
} else if (!strcmp(interface, "wl_seat")) { } else if (!strcmp(interface, "wl_seat")) {
instance->wayland.seat = wl_registry_bind(registry, name, wayland->seat = wl_registry_bind(registry, name,
&wl_seat_interface, 1); &wl_seat_interface, 1);
} }
} }
static void static void
registry_handle_global_remove (void *data, registry_handle_global_remove (void *data,
struct wl_registry *registry, struct wl_registry *registry,
uint32_t name) uint32_t name)
{ {
// TODO (void)registry;
struct squeek_wayland *wayland = data;
struct wl_output *output = squeek_outputs_try_unregister(wayland->outputs, name);
if (output) {
wl_output_destroy(output);
}
} }
static const struct wl_registry_listener registry_listener = { static const struct wl_registry_listener registry_listener = {
@ -147,6 +148,54 @@ static const struct wl_registry_listener registry_listener = {
registry_handle_global_remove registry_handle_global_remove
}; };
void init_wayland(struct squeek_wayland *wayland) {
// Set up Wayland
gdk_set_allowed_backends ("wayland");
GdkDisplay *gdk_display = gdk_display_get_default ();
struct wl_display *display = gdk_wayland_display_get_wl_display (gdk_display);
if (display == NULL) {
g_error ("Failed to get display: %m\n");
exit(1);
}
struct wl_registry *registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, wayland);
wl_display_roundtrip(display); // wait until the registry is actually populated
if (!wayland->seat) {
g_error("No seat Wayland global available.");
exit(1);
}
if (!wayland->virtual_keyboard_manager) {
g_error("No virtual keyboard manager Wayland global available.");
exit(1);
}
if (!wayland->layer_shell) {
g_error("No layer shell global available.");
exit(1);
}
if (!wayland->input_method_manager) {
g_warning("Wayland input method interface not available");
}
if (wayland->input_method_manager) {
wayland->input_method = zwp_input_method_manager_v2_get_input_method(
wayland->input_method_manager,
wayland->seat);
}
if (wayland->virtual_keyboard_manager) {
wayland->virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
wayland->virtual_keyboard_manager,
wayland->seat);
}
// initialize global
squeek_wayland = wayland;
}
#define SESSION_NAME "sm.puri.OSK0" #define SESSION_NAME "sm.puri.OSK0"
GDBusProxy *_proxy = NULL; GDBusProxy *_proxy = NULL;
@ -284,22 +333,6 @@ phosh_theme_init (void)
g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL); g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL);
} }
/// Create Rust objects in one go,
/// to avoid crossing the language barrier and losing type information
static struct rsobjects create_rsobjects(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat) {
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 squeek_rsobjects_new(im, vk);
}
static GDebugKey debug_keys[] = static GDebugKey debug_keys[] =
{ {
{ .key = "force-show", { .key = "force-show",
@ -359,46 +392,10 @@ main (int argc, char **argv)
phosh_theme_init (); phosh_theme_init ();
// Set up Wayland
gdk_set_allowed_backends ("wayland");
GdkDisplay *gdk_display = gdk_display_get_default ();
struct wl_display *display = gdk_wayland_display_get_wl_display (gdk_display);
if (display == NULL) {
g_error ("Failed to get display: %m\n");
exit(1);
}
struct squeekboard instance = {0}; struct squeekboard instance = {0};
squeek_wayland_init (&instance.wayland);
struct wl_registry *registry = wl_display_get_registry (display);
wl_registry_add_listener (registry, &registry_listener, &instance);
wl_display_roundtrip(display); // wait until the registry is actually populated
squeek_wayland_set_global(&instance.wayland);
if (!instance.wayland.seat) { // Also initializes wayland
g_error("No seat Wayland global available."); struct rsobjects rsobjects = squeek_init();
exit(1);
}
if (!instance.wayland.virtual_keyboard_manager) {
g_error("No virtual keyboard manager Wayland global available.");
exit(1);
}
if (!instance.wayland.layer_shell) {
g_error("No layer shell global available.");
exit(1);
}
if (!instance.wayland.input_method_manager) {
g_warning("Wayland input method interface not available");
}
struct rsobjects rsobjects = create_rsobjects(instance.wayland.input_method_manager,
instance.wayland.virtual_keyboard_manager,
instance.wayland.seat);
instance.ui_manager = squeek_uiman_new();
instance.settings_context = eekboard_context_service_new(&instance.layout_choice); instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
@ -444,7 +441,6 @@ main (int argc, char **argv)
instance.settings_context, instance.settings_context,
rsobjects.submission, rsobjects.submission,
&instance.layout_choice, &instance.layout_choice,
instance.ui_manager,
rsobjects.state_manager); rsobjects.state_manager);
if (!ui_context) { if (!ui_context) {
g_error("Could not initialize GUI"); g_error("Could not initialize GUI");
@ -477,6 +473,5 @@ main (int argc, char **argv)
} }
g_main_loop_unref (loop); g_main_loop_unref (loop);
squeek_wayland_deinit (&instance.wayland);
return 0; return 0;
} }

View File

@ -7,10 +7,13 @@
use crate::animation; use crate::animation;
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::{ Commands, PanelCommand }; use crate::main::{ Commands, PanelCommand, PixelSize };
use crate::outputs;
use crate::outputs::{OutputId, OutputState};
use std::cmp;
use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Presence { pub enum Presence {
Present, Present,
@ -29,12 +32,14 @@ pub enum InputMethod {
InactiveSince(Instant), InactiveSince(Instant),
} }
/// Incoming events /// Incoming events.
/// This contains events that cause a change to the internal state.
#[derive(Clone)] #[derive(Clone)]
pub enum Event { pub enum Event {
InputMethod(InputMethod), InputMethod(InputMethod),
Visibility(visibility::Event), Visibility(visibility::Event),
PhysicalKeyboard(Presence), PhysicalKeyboard(Presence),
Output(outputs::Event),
/// Event triggered because a moment in time passed. /// Event triggered because a moment in time passed.
/// Use to animate state transitions. /// Use to animate state transitions.
/// The value is the ideal arrival time. /// The value is the ideal arrival time.
@ -47,6 +52,12 @@ impl From<InputMethod> for Event {
} }
} }
impl From<outputs::Event> for Event {
fn from(ev: outputs::Event) -> Self {
Self::Output(ev)
}
}
pub mod visibility { pub mod visibility {
#[derive(Clone)] #[derive(Clone)]
pub enum Event { pub enum Event {
@ -83,12 +94,12 @@ impl Outcome {
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
let layout_hint_set = match new_state { let layout_hint_set = match new_state {
Outcome { Outcome {
visibility: animation::Outcome::Visible, visibility: animation::Outcome::Visible{..},
im: InputMethod::Active(hints), im: InputMethod::Active(hints),
} => Some(hints.clone()), } => Some(hints.clone()),
Outcome { Outcome {
visibility: animation::Outcome::Visible, visibility: animation::Outcome::Visible{..},
im: InputMethod::InactiveSince(_), im: InputMethod::InactiveSince(_),
} => Some(InputMethodDetails { } => Some(InputMethodDetails {
hint: ContentHint::NONE, hint: ContentHint::NONE,
@ -100,9 +111,10 @@ impl Outcome {
.. ..
} => None, } => None,
}; };
// FIXME: handle switching outputs
let (dbus_visible_set, panel_visibility) = match new_state.visibility { let (dbus_visible_set, panel_visibility) = match new_state.visibility {
animation::Outcome::Visible => (Some(true), Some(PanelCommand::Show)), animation::Outcome::Visible{output, height}
=> (Some(true), Some(PanelCommand::Show{output, height})),
animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)), animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
}; };
@ -130,6 +142,13 @@ pub struct Application {
pub im: InputMethod, pub im: InputMethod,
pub visibility_override: visibility::State, pub visibility_override: visibility::State,
pub physical_keyboard: Presence, pub physical_keyboard: Presence,
/// The output on which the panel should appear.
/// This is stored as part of the state
/// because it's not clear how to derive the output from the rest of the state.
/// It should probably follow the focused input,
/// but not sure about being allowed on non-touch displays.
pub preferred_output: Option<OutputId>,
pub outputs: HashMap<OutputId, OutputState>,
} }
impl Application { impl Application {
@ -144,6 +163,8 @@ impl Application {
im: InputMethod::InactiveSince(now), im: InputMethod::InactiveSince(now),
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
preferred_output: None,
outputs: Default::default(),
} }
} }
@ -164,6 +185,25 @@ impl Application {
..self ..self
}, },
Event::Output(outputs::Event { output, change }) => {
let mut app = self;
match change {
outputs::ChangeType::Altered(state) => {
app.outputs.insert(output, state);
app.preferred_output = app.preferred_output.or(Some(output));
},
outputs::ChangeType::Removed => {
app.outputs.remove(&output);
if app.preferred_output == Some(output) {
// There's currently no policy to choose one output over another,
// so just take whichever comes first.
app.preferred_output = app.outputs.keys().next().map(|output| *output);
}
},
};
app
},
Event::InputMethod(new_im) => match (self.im.clone(), new_im) { Event::InputMethod(new_im) => match (self.im.clone(), new_im) {
(InputMethod::Active(_old), InputMethod::Active(new_im)) (InputMethod::Active(_old), InputMethod::Active(new_im))
=> Self { => Self {
@ -198,20 +238,60 @@ impl Application {
} }
} }
fn get_preferred_height(output: &OutputState) -> Option<PixelSize> {
output.get_pixel_size()
.map(|px_size| {
let height = {
if px_size.width > px_size.height {
px_size.width / 5
} else {
let abstract_width
= PixelSize {
scale_factor: output.scale as u32,
pixels: px_size.width,
}
.as_scaled_ceiling();
if (abstract_width < 540) && (px_size.width > 0) {
px_size.width * 7 / 12 // to match 360×210
} else {
// Here we switch to wide layout, less height needed
px_size.width * 7 / 22
}
}
};
PixelSize {
scale_factor: output.scale as u32,
pixels: cmp::min(height, px_size.height / 2),
}
})
}
pub fn get_outcome(&self, now: Instant) -> Outcome { pub fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence // FIXME: include physical keyboard presence
Outcome { Outcome {
visibility: match (self.physical_keyboard, self.visibility_override) { visibility: match self.preferred_output {
None => animation::Outcome::Hidden,
Some(output) => {
// Hoping that this will get optimized out on branches not using `visible`.
let height = Self::get_preferred_height(self.outputs.get(&output).unwrap())
.unwrap_or(PixelSize{pixels: 0, scale_factor: 1});
// TODO: Instead of setting size to 0 when the output is invalid,
// simply go invisible.
let visible = animation::Outcome::Visible{ output, height };
match (self.physical_keyboard, self.visibility_override) {
(_, visibility::State::ForcedHidden) => animation::Outcome::Hidden, (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
(_, visibility::State::ForcedVisible) => animation::Outcome::Visible, (_, visibility::State::ForcedVisible) => visible,
(Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden, (Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
(Presence::Missing, visibility::State::NotForced) => match self.im { (Presence::Missing, visibility::State::NotForced) => match self.im {
InputMethod::Active(_) => animation::Outcome::Visible, InputMethod::Active(_) => visible,
InputMethod::InactiveSince(since) => { InputMethod::InactiveSince(since) => {
if now < since + animation::HIDING_TIMEOUT { animation::Outcome::Visible } if now < since + animation::HIDING_TIMEOUT { visible }
else { animation::Outcome::Hidden } else { animation::Outcome::Hidden }
}, },
}, },
}
}
}, },
im: self.im.clone(), im: self.im.clone(),
} }
@ -236,9 +316,9 @@ impl Application {
#[cfg(test)] #[cfg(test)]
mod test { pub mod test {
use super::*; use super::*;
use crate::outputs::c::WlOutput;
use std::time::Duration; use std::time::Duration;
fn imdetails_new() -> InputMethodDetails { fn imdetails_new() -> InputMethodDetails {
@ -248,6 +328,30 @@ mod test {
} }
} }
fn fake_output_id(id: usize) -> OutputId {
OutputId(unsafe {
std::mem::transmute::<_, WlOutput>(id)
})
}
pub fn application_with_fake_output(start: Instant) -> Application {
let id = fake_output_id(1);
let mut outputs = HashMap::new();
outputs.insert(
id,
OutputState {
current_mode: None,
transform: None,
scale: 1,
},
);
Application {
preferred_output: Some(id),
outputs,
..Application::new(start)
}
}
/// Test the original delay scenario: no flicker on quick switches. /// Test the original delay scenario: no flicker on quick switches.
#[test] #[test]
fn avoid_hide() { fn avoid_hide() {
@ -257,15 +361,16 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
// Check 100ms at 1ms intervals. It should remain visible. // Check 100ms at 1ms intervals. It should remain visible.
for _i in 0..100 { for _i in 0..100 {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible{..},
"Hidden when it should remain visible: {:?}", "Hidden when it should remain visible: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
) )
@ -273,7 +378,7 @@ mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
assert_eq!(state.get_outcome(now).visibility, animation::Outcome::Visible); assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible{..});
} }
/// Make sure that hiding works when input method goes away /// Make sure that hiding works when input method goes away
@ -285,11 +390,12 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible = state.get_outcome(now).visibility { while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -309,6 +415,7 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
// This reflects the sequence from Wayland: // This reflects the sequence from Wayland:
// disable, disable, enable, disable // disable, disable, enable, disable
@ -318,7 +425,7 @@ mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible = state.get_outcome(now).visibility { while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -347,13 +454,14 @@ mod test {
im: InputMethod::InactiveSince(now), im: InputMethod::InactiveSince(now),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
now += Duration::from_secs(1); now += Duration::from_secs(1);
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now); let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible{..},
"Failed to show: {:?}", "Failed to show: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
); );
@ -380,6 +488,7 @@ mod test {
im: InputMethod::Active(imdetails_new()), im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing, physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced, visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
}; };
now += Duration::from_secs(1); now += Duration::from_secs(1);
@ -406,9 +515,9 @@ mod test {
now += Duration::from_secs(1); now += Duration::from_secs(1);
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now); let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
assert_eq!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).visibility,
animation::Outcome::Visible, animation::Outcome::Visible{..},
"Failed to appear: {:?}", "Failed to appear: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
); );

View File

@ -21,7 +21,7 @@
use std::env; use std::env;
use ::logging; use ::logging;
use glib::object::ObjectExt; use glib::prelude::ObjectExt;
use logging::Warn; use logging::Warn;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
@ -31,7 +31,7 @@ pub mod c {
use gtk; use gtk;
use gtk_sys; use gtk_sys;
use gtk::CssProviderExt; use gtk::prelude::CssProviderExt;
use glib::translate::ToGlibPtr; use glib::translate::ToGlibPtr;
/// Loads the layout style based on current theme /// Loads the layout style based on current theme
@ -40,8 +40,13 @@ pub mod c {
pub extern "C" pub extern "C"
fn squeek_load_style() -> *const gtk_sys::GtkCssProvider { fn squeek_load_style() -> *const gtk_sys::GtkCssProvider {
unsafe { gtk::set_initialized() }; unsafe { gtk::set_initialized() };
let theme = gtk::Settings::get_default()
.map(|settings| get_theme_name(&settings)); #[cfg(feature = "glib_v0_14")]
let theme = gtk::Settings::default();
#[cfg(not(feature = "glib_v0_14"))]
let theme = gtk::Settings::get_default();
let theme = theme.map(|settings| get_theme_name(&settings));
let css_name = path_from_theme(theme); let css_name = path_from_theme(theme);
@ -93,19 +98,31 @@ fn get_theme_name(settings: &gtk::Settings) -> GtkTheme {
e e
}).ok(); }).ok();
#[cfg(feature = "glib_v0_14")]
let prop = |s: &gtk::Settings, name| s.property(name);
#[cfg(not(feature = "glib_v0_14"))]
let prop = |s: &gtk::Settings, name| s.get_property(name);
#[cfg(feature = "glib_v0_14")]
fn check<T, E: std::fmt::Display>(v: Result<T, E>) -> Option<T> {
v.or_print(logging::Problem::Surprise, "Key not of expected type")
}
#[cfg(not(feature = "glib_v0_14"))]
fn check<T>(v: Option<T>) -> Option<T> { v }
match env_theme { match env_theme {
Some(theme) => theme, Some(theme) => theme,
None => GtkTheme { None => GtkTheme {
name: { name: {
settings.get_property("gtk-theme-name") prop(settings, "gtk-theme-name")
.or_print(logging::Problem::Surprise, "No theme name") .or_print(logging::Problem::Surprise, "No theme name")
.and_then(|value| value.get::<String>()) .and_then(|value| check(value.get::<String>()))
.unwrap_or(DEFAULT_THEME_NAME.into()) .unwrap_or(DEFAULT_THEME_NAME.into())
}, },
variant: { variant: {
settings.get_property("gtk-application-prefer-dark-theme") prop(settings, "gtk-application-prefer-dark-theme")
.or_print(logging::Problem::Surprise, "No settings key") .or_print(logging::Problem::Surprise, "No settings key")
.and_then(|value| value.get::<bool>()) .and_then(|value| check(value.get::<bool>()))
.and_then(|dark_preferred| match dark_preferred { .and_then(|dark_preferred| match dark_preferred {
true => Some("dark".into()), true => Some("dark".into()),
false => None, false => None,

View File

@ -5,7 +5,6 @@
#include "virtual-keyboard-unstable-v1-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "eek/eek-types.h" #include "eek/eek-types.h"
#include "main.h" #include "main.h"
#include "src/ui_manager.h"
struct squeek_layout; struct squeek_layout;

View File

@ -1,19 +0,0 @@
#ifndef UI_MANAGER__
#define UI_MANAGER__
#include <inttypes.h>
#include "eek/eek-types.h"
#include "outputs.h"
#include "main.h"
struct ui_manager;
struct ui_manager *squeek_uiman_new(void);
void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output);
uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
struct vis_manager;
struct vis_manager *squeek_visman_new(struct squeek_state_manager *state_manager);
#endif

View File

@ -1,82 +0,0 @@
/* Copyright (C) 2020, 2021 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Centrally manages the shape of the UI widgets, and the choice of layout.
*
* Coordinates this based on information collated from all possible sources.
*/
use std::cmp::min;
use ::outputs::c::OutputHandle;
pub mod c {
use super::*;
use ::util::c::Wrapped;
#[no_mangle]
pub extern "C"
fn squeek_uiman_new() -> Wrapped<Manager> {
Wrapped::new(Manager { output: None })
}
/// Used to size the layer surface containing all the OSK widgets.
#[no_mangle]
pub extern "C"
fn squeek_uiman_get_perceptual_height(
uiman: Wrapped<Manager>,
) -> u32 {
let uiman = uiman.clone_ref();
let uiman = uiman.borrow();
// TODO: what to do when there's no output?
uiman.get_perceptual_height().unwrap_or(0)
}
#[no_mangle]
pub extern "C"
fn squeek_uiman_set_output(
uiman: Wrapped<Manager>,
output: OutputHandle,
) {
let uiman = uiman.clone_ref();
let mut uiman = uiman.borrow_mut();
uiman.output = Some(output);
}
}
/// Stores current state of all things influencing what the UI should look like.
pub struct Manager {
/// Shared output handle, current state updated whenever it's needed.
// TODO: Stop assuming that the output never changes.
// (There's no way for the output manager to update the ui manager.)
// FIXME: Turn into an OutputState and apply relevant connections elsewhere.
// Otherwise testability and predictablity is low.
output: Option<OutputHandle>,
//// Pixel size of the surface. Needs explicit updating.
//surface_size: Option<Size>,
}
impl Manager {
fn get_perceptual_height(&self) -> Option<u32> {
let output_info = (&self.output).as_ref()
.and_then(|o| o.get_state())
.map(|os| (os.scale as u32, os.get_pixel_size()));
match output_info {
Some((scale, Some(px_size))) => Some({
let height = if (px_size.width < 720) & (px_size.width > 0) {
px_size.width * 7 / 12 // to match 360×210
} else if px_size.width < 1080 {
360 + (1080 - px_size.width) * 60 / 360 // smooth transition
} else {
360
};
// Don't exceed half the display size
min(height, px_size.height / 2) / scale
}),
Some((scale, None)) => Some(360 / scale),
None => None,
}
}
}

View File

@ -10,11 +10,18 @@ type KeyCode = u32;
pub mod c { pub mod c {
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{ c_char, c_void }; use std::os::raw::{ c_char, c_void };
use std::ptr;
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct ZwpVirtualKeyboardV1(*const c_void); pub struct ZwpVirtualKeyboardV1(*const c_void);
impl ZwpVirtualKeyboardV1 {
pub fn null() -> Self {
Self(ptr::null())
}
}
#[repr(C)] #[repr(C)]
pub struct KeyMap { pub struct KeyMap {
fd: u32, fd: u32,

View File

@ -4,6 +4,12 @@
struct squeek_wayland *squeek_wayland = NULL; struct squeek_wayland *squeek_wayland = NULL;
void squeek_wayland_init_global(struct squeek_outputs *outputs) {
struct squeek_wayland *wayland = {0};
wayland->outputs = outputs;
squeek_wayland = wayland;
}
// The following functions only exist // The following functions only exist
// to create linkable symbols out of inline functions, // to create linkable symbols out of inline functions,
// because those are not directly callable from Rust // because those are not directly callable from Rust

View File

@ -10,27 +10,18 @@
#include "outputs.h" #include "outputs.h"
struct squeek_wayland { struct squeek_wayland {
// globals
struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_layer_shell_v1 *layer_shell;
struct zwp_virtual_keyboard_manager_v1 *virtual_keyboard_manager; struct zwp_virtual_keyboard_manager_v1 *virtual_keyboard_manager;
struct zwp_input_method_manager_v2 *input_method_manager; struct zwp_input_method_manager_v2 *input_method_manager;
struct squeek_outputs *outputs; struct squeek_outputs *outputs;
struct wl_seat *seat; struct wl_seat *seat;
// objects
struct zwp_input_method_v2 *input_method;
struct zwp_virtual_keyboard_v1 *virtual_keyboard;
}; };
extern struct squeek_wayland *squeek_wayland; extern struct squeek_wayland *squeek_wayland;
static inline void squeek_wayland_init(struct squeek_wayland *wayland) {
wayland->outputs = squeek_outputs_new();
}
static inline void squeek_wayland_set_global(struct squeek_wayland *wayland) {
squeek_wayland = wayland;
}
static inline void squeek_wayland_deinit(struct squeek_wayland *wayland) {
squeek_outputs_free(wayland->outputs);
}
#endif // WAYLAND_H #endif // WAYLAND_H

View File

@ -84,6 +84,7 @@ foreach layout : [
'jp+kana','jp+kana_wide', 'jp+kana','jp+kana_wide',
'no', 'no',
'pl', 'pl_wide', 'pl', 'pl_wide',
'ro', 'ro_wide',
'ru', 'ru',
'se', 'se',
'th', 'th_wide', 'th', 'th_wide',
@ -107,15 +108,8 @@ foreach layout : [
extra += ['allow_missing_return'] extra += ['allow_missing_return']
endif endif
# Older Cargo seens to be sensitive to something
# about the RUST_FLAGS env var, and rebuilds all tests when it's set,
# increasing test time by 2 orders of magnitude.
# Let it have its way.
if get_option('legacy') == true
timeout = 300
else
timeout = 30 timeout = 30
endif
test( test(
'test_layout_' + layout, 'test_layout_' + layout,
cargo_script, cargo_script,

11
tools/entry.py Normal file → Executable file
View File

@ -46,9 +46,12 @@ class App(Gtk.Application):
] + terminal ] + terminal
hints = [ hints = [
("OSK provided", Gtk.InputHints.INHIBIT_OSK) ("OSK provided", Gtk.InputHints.INHIBIT_OSK),
("Uppercase chars", Gtk.InputHints.UPPERCASE_CHARS),
] ]
purpose_timer = 0;
def on_purpose_toggled(self, btn, entry): def on_purpose_toggled(self, btn, entry):
purpose = Gtk.InputPurpose.PIN if btn.get_active() else Gtk.InputPurpose.PASSWORD purpose = Gtk.InputPurpose.PIN if btn.get_active() else Gtk.InputPurpose.PASSWORD
entry.set_input_purpose(purpose) entry.set_input_purpose(purpose)
@ -60,13 +63,17 @@ class App(Gtk.Application):
e.set_input_purpose(purpose) e.set_input_purpose(purpose)
return True return True
def on_is_focus_changed(self, e, *args):
if not self.purpose_timer and e.props.is_focus:
GLib.timeout_add_seconds (3, self.on_timeout, e)
def add_random (self, grid): def add_random (self, grid):
l = Gtk.Label(label="Random") l = Gtk.Label(label="Random")
e = Gtk.Entry(hexpand=True) e = Gtk.Entry(hexpand=True)
e.connect("notify::is-focus", self.on_is_focus_changed)
e.set_input_purpose(Gtk.InputPurpose.FREE_FORM) e.set_input_purpose(Gtk.InputPurpose.FREE_FORM)
grid.attach(l, 0, len(self.purposes), 1, 1) grid.attach(l, 0, len(self.purposes), 1, 1)
grid.attach(e, 1, len(self.purposes), 1, 1) grid.attach(e, 1, len(self.purposes), 1, 1)
GLib.timeout_add_seconds (3, self.on_timeout, e)
def do_activate(self): def do_activate(self):
w = Gtk.ApplicationWindow(application=self) w = Gtk.ApplicationWindow(application=self)

104
tools/entry4.py Executable file
View File

@ -0,0 +1,104 @@
#!/usr/bin/env python3
import gi
import random
import sys
gi.require_version('Gtk', '4.0')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk
from gi.repository import GLib
def new_grid(items, set_type):
grid = Gtk.Grid(orientation='vertical', column_spacing=8, row_spacing=8)
i = 0
for text, value in items:
label = Gtk.Label(label=text)
label.props.margin_top = 6
label.props.margin_start = 6
entry = Gtk.Entry(hexpand=True)
entry.props.margin_top = 6
entry.props.margin_end = 6
set_type(entry, value)
grid.attach(label, 0, i, 1, 1)
grid.attach(entry, 1, i, 1, 1)
i += 1
return grid
class App(Gtk.Application):
purposes = [
("Free form", Gtk.InputPurpose.FREE_FORM),
("Alphabetical", Gtk.InputPurpose.ALPHA),
("Digits", Gtk.InputPurpose.DIGITS),
("Number", Gtk.InputPurpose.NUMBER),
("Phone", Gtk.InputPurpose.PHONE),
("URL", Gtk.InputPurpose.URL),
("E-mail", Gtk.InputPurpose.EMAIL),
("Name", Gtk.InputPurpose.NAME),
("Password", Gtk.InputPurpose.PASSWORD),
("PIN", Gtk.InputPurpose.PIN),
("Terminal", Gtk.InputPurpose.TERMINAL),
]
hints = [
("OSK provided", Gtk.InputHints.INHIBIT_OSK)
]
purpose_tick_id = 0
def on_purpose_toggled(self, btn, entry):
purpose = Gtk.InputPurpose.PIN if btn.get_active() else Gtk.InputPurpose.PASSWORD
entry.set_input_purpose(purpose)
def on_timeout(self, e):
r = random.randint(0, len(self.purposes) - 1)
(_, purpose) = self.purposes[r]
print(f"Setting {purpose}")
e.set_input_purpose(purpose)
return True
def on_random_enter(self, controller, entry):
self.purpose_tick_id = GLib.timeout_add_seconds(3, self.on_timeout, entry)
def on_random_leave(self, controller, entry):
GLib.source_remove(self.purpose_tick_id)
def add_random(self, grid):
label = Gtk.Label(label="Random")
entry = Gtk.Entry(hexpand=True)
entry.set_input_purpose(Gtk.InputPurpose.FREE_FORM)
grid.attach(label, 0, len(self.purposes), 1, 1)
grid.attach(entry, 1, len(self.purposes), 1, 1)
focus_controller = Gtk.EventControllerFocus()
entry.add_controller(focus_controller)
focus_controller.connect("enter", self.on_random_enter, entry)
focus_controller.connect("leave", self.on_random_leave, entry)
def do_activate(self):
w = Gtk.ApplicationWindow(application=self)
w.set_default_size(300, 500)
notebook = Gtk.Notebook()
def add_purpose(entry, purpose):
entry.set_input_purpose(purpose)
def add_hint(entry, hint):
entry.set_input_hints(hint)
purpose_grid = new_grid(self.purposes, add_purpose)
self.add_random(purpose_grid)
hint_grid = new_grid(self.hints, add_hint)
purpose_scroll = Gtk.ScrolledWindow()
purpose_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
purpose_scroll.set_child(purpose_grid)
notebook.append_page(purpose_scroll, Gtk.Label(label="Purposes"))
notebook.append_page(hint_grid, Gtk.Label(label="Hints"))
w.set_child(notebook)
w.present()
app = App()
app.run(sys.argv)