Compare commits

..

1 Commits

109 changed files with 2162 additions and 4726 deletions

3
.gitignore vendored
View File

@ -1,6 +1,5 @@
.zanata-cache/
_build
po/squeekboard.pot
po/*.mo
TAGS
tags
vgdump

View File

@ -1,27 +1,34 @@
image: pureos/byzantium
image: debian:bullseye
stages:
- build
- test
.tags: &tags
tags:
- librem5
before_script:
- apt-get -y update
- apt-get -y install ca-certificates
- apt-get -y install wget ca-certificates gnupg
- 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:
<<: *tags
stage: build
artifacts:
paths:
- _build
script:
- apt-get -y install python3-pip python3-sphinx
- pip3 install recommonmark
- ./doc/build.sh _build
except:
variables:
- $PKG_ONLY == "1"
- apt-get -y install python3-pip python3-sphinx
- pip3 install recommonmark
- ./doc/build.sh _build
build_meson:
tags:
- librem5
stage: build
artifacts:
paths:
@ -31,113 +38,115 @@ build_meson:
- apt-get -y build-dep .
- meson . _build/ -Ddepdatadir=/usr/share --werror
- ninja -C _build install
except:
variables:
- $PKG_ONLY == "1"
build_deb:
stage: build
artifacts:
paths:
- '*.deb'
script:
- rm -f ../*.deb
- 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 .
tags:
- librem5
stage: build
artifacts:
paths:
- "*.deb"
script:
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
build_deb:amber:
image: pureos/amber
tags:
- librem5
stage: build
artifacts:
paths:
- "*.deb"
script:
- echo "deb http://ci.puri.sm/ scratch librem5" > /etc/apt/sources.list.d/ci.list
- apt-get -y update
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
build_deb:buster:
image: "debian:buster"
tags:
- librem5
stage: build
artifacts:
paths:
- "*.deb"
script:
- echo "deb http://ci.puri.sm/ scratch librem5" > /etc/apt/sources.list.d/ci.list
- apt-get -y update
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
build_deb:arm64:
image: pureos/byzantium
tags:
- aarch64
stage: build
artifacts:
paths:
- '*.deb'
script:
- rm -f ../*.deb
- 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 .
tags:
- librem5:arm64
stage: build
artifacts:
paths:
- "*.deb"
script:
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
build_deb:future:
image: debian:sid
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 .
build_deb:arm64_buster:
image: "debian:buster"
tags:
- librem5:arm64
stage: build
artifacts:
paths:
- "*.deb"
script:
- echo "deb http://ci.puri.sm/ scratch librem5" > /etc/apt/sources.list.d/ci.list
- apt-get -y update
- rm -f ../*.deb
- apt-get -y build-dep .
- apt-get -y install devscripts
- debuild -i -us -uc -b
- cp ../*.deb .
test_lintian:
stage: test
needs:
- job: build_deb
artifacts: true
script:
- apt-get -y install lintian
- lintian *.deb
except:
variables:
- $PKG_ONLY == "1"
<<: *tags
stage: test
dependencies:
- build_deb
script:
- apt-get -y install lintian
- lintian *.deb
test:
tags:
- librem5
stage: test
needs:
- job: build_meson
artifacts: true
- build_meson
script:
- apt-get -y build-dep .
- apt-get -y install clang-tidy
- ninja -C _build test
- tools/style-check_build _build
except:
variables:
- $PKG_ONLY == "1"
test_style:
stage: test
needs: []
script:
- apt-get -y build-dep .
- tools/style-check_source
except:
variables:
- $PKG_ONLY == "1"
- cd _build
- clang-tidy --checks=-clang-diagnostic-missing-braces,readability-braces-around-statements, --warnings-as-errors=readability-braces-around-statements -extra-arg=-Wno-unknown-warning-option ../src/*.c ../eek/*.c ../eekboard/*.c
check_release:
<<: *tags
stage: test
needs: []
only:
refs:
- master
script:
- apt-get -y install git python3
- (head -n 1 ./debian/changelog && git tag) | ./debian/check_release.py
except:
variables:
- $PKG_ONLY == "1"

View File

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

22
Cargo.deps.legacy Normal file
View File

@ -0,0 +1,22 @@
# 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"]

View File

@ -1,33 +0,0 @@
# 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"]

100
Cargo.lock generated
View File

@ -1,7 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atk"
version = "0.7.0"
@ -28,12 +26,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -67,21 +59,27 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.73"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "clap"
version = "2.33.4"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826bf7bc84f9435630275cb8e802a4a0ec792b615969934bd16d42ffed10f207"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "fragile"
version = "0.3.0"
@ -259,22 +257,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "indexmap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -283,9 +265,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "linked-hash-map"
@ -338,28 +320,43 @@ dependencies = [
[[package]]
name = "pkg-config"
version = "0.3.24"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "proc-macro2"
version = "1.0.36"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.15"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"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]]
name = "rs"
version = "0.1.0"
@ -375,31 +372,26 @@ dependencies = [
"gtk",
"gtk-sys",
"maplit",
"regex",
"serde",
"serde_yaml",
"xkbcommon",
]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "serde"
version = "1.0.136"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
@ -408,21 +400,21 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.8.23"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"indexmap",
"ryu",
"dtoa",
"linked-hash-map",
"serde",
"yaml-rust",
]
[[package]]
name = "syn"
version = "1.0.86"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
@ -440,9 +432,9 @@ dependencies = [
[[package]]
name = "unicode-width"
version = "0.1.9"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"

View File

@ -17,9 +17,22 @@ name = "test_layout"
path = "@path@/examples/test_layout.rs"
[features]
glib_v0_14 = []
gio_v0_5 = []
gtk_v0_5 = []
rustc_less_1_36 = []
# 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]
maplit = "1.0.*"
serde = { version = "1.0.*", features = ["derive"] }

View File

@ -1,7 +1,7 @@
*squeekboard* - a Wayland on-screen keyboard
*squeekboard* - a Wayland virtual keyboard
========================================
*Squeekboard* is a keyboard-shaped input method supporting Wayland, built primarily for the *Librem 5* phone.
*Squeekboard* is a virtual keyboard supporting Wayland, built primarily for the *Librem 5* phone.
It squeaks because some Rust got inside.
@ -11,24 +11,20 @@ Features
### Present
- GTK3
- Custom keyboard layouts defined in yaml
- Input purpose dependent keyboard layouts
- Custom yaml-defined keyboards
- DBus interface to show and hide
- Use Wayland input method protocol to submit text
- Use Wayland input method protocol to show and hide
- Use Wayland virtual keyboard protocol
### Temporarily dropped
- A settings interface
### TODO
- Text prediction/correction
- Use preedit
- Submit actions like "next field" using a future Wayland protocol
- Use Wayland input method protocol
- Pick up DBus interface files from /usr/share
Creating layouts
-------------------
If you want to work on layouts, check out the [guide](doc/tutorial.md).
Building
--------
@ -39,7 +35,7 @@ See `.gitlab-ci.yml` or run `apt-get build-dep .`
### Build from git repo
```bash
$ git clone https://gitlab.gnome.org/World/Phosh/squeekboard.git
$ git clone https://source.puri.sm/Librem5/squeekboard.git
$ cd squeekboard
$ mkdir _build
$ meson _build/
@ -58,31 +54,18 @@ $ cd ../build/
$ src/squeekboard
```
Squeekboard's panel will appear whenever a compatible application requests an input method. Click a text field in any GTK application, like `python3 ./tools/entry.py`.
Squeekboard honors the gnome "screen-keyboard-enabled" setting. Either enable this through gnome-settings under accessibility or run:
```bash
$ gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true
```
Alternatively, force panel visibility manually with:
To make the keyboard show you can use either an application that does so automatically, like a text editor or `python3 ./tests/entry.py`, or you can manually trigger it with:
```bash
busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b true
```
### What the compositor has to support
A compatible compositor has to support the protocols:
- layer-shell
- virtual-keyboard-v1
It's strongly recommended to support:
- input-method-v2
Developing
----------

View File

@ -1,33 +0,0 @@
/* 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 {
border-radius: 0px;
margin: 1px 1px 1px 1px;
}

View File

@ -1,84 +0,0 @@
# Armenian layout created by Norayr Chilingarian
# Yerevan
# Oct 2021
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 32, height: 32 }
spaceline: { width: 142, height: 52 }
special: { width: 44, height: 52 }
views:
base:
- "՝ է թ փ ձ ջ ւ և ռ չ ճ ֊ ժ"
- "ք ո ե ր տ ը ւ ի օ պ խ ծ շ"
- "ա ս դ ֆ գ հ յ կ լ "
- "Shift_L զ ղ ց վ բ ն մ ՛ BackSpace"
- "show_numbers preferences space period Return"
upper:
- "՝ Է Թ Փ Ձ Ջ Ւ և Ռ Չ Ճ — Ժ"
- "Ք Ո Ե Ր Տ Ը Ւ Ի Օ Պ Խ Ծ Շ"
- Ս Դ Ֆ Գ Հ Յ Կ Լ ։"
- "Shift_L Զ Ղ Ց Վ Բ Ն Մ ՞ BackSpace"
- "show_numbers preferences space period Return"
numbers:
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "ﬓ ﬔ ﬕ ﬖ ﬗ ՟ և"
- "1 2 3 4 5 6 7 8 9 0"
- "show_letters preferences space period Return"
symbols:
- "show_numbers_from_symbols \\ % < > = [ ] BackSpace"
- "* # $ / & - _ + ( )"
- "© ® £ € ¥ ^ ° @ { }"
- "~ ` | · √ π τ ÷ × ¶"
- "show_letters preferences space period Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "wide"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "ԱԲԳ"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
period:
outline: "special"
text: "."
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"

View File

@ -1,84 +0,0 @@
# Armenian layout created by Norayr Chilingarian
# Yerevan
# Oct 2021
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 32, height: 32 }
spaceline: { width: 142, height: 52 }
special: { width: 44, height: 52 }
views:
base:
- "՝ ֆ ձ ֊ , ։ ՞ ՛ ) օ է ղ"
- "ճ փ բ ս մ ո ւ կ ը թ ծ ց »"
- "ջ վ գ ե ա ն ի տ հ պ ր"
- "Shift_L ժ դ չ յ զ լ ք խ շ ռ BackSpace"
- "show_numbers preferences space period Return"
upper:
- "՜ Ֆ Ձ — $ … ՟ և ՚ ( Օ Է Ղ"
- "Ճ Փ Բ Ս Մ Ո Ւ Կ Ը Թ Ծ Ց «"
- "Ջ Վ Գ Ե Ա Ն Ի Տ Հ Պ Պ Ր"
- "Shift_L Ժ Դ Չ Յ Զ Լ Ք Խ Շ Ռ BackSpace"
- "show_numbers preferences space period Return"
numbers:
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "ﬓ ﬔ ﬕ ﬖ ﬗ ՟ և"
- "1 2 3 4 5 6 7 8 9 0"
- "show_letters preferences space period Return"
symbols:
- "show_numbers_from_symbols \\ % < > = [ ] BackSpace"
- "* # $ / & - _ + ( )"
- "© ® £ € ¥ ^ ° @ { }"
- "~ ` | · √ π τ ÷ × ¶"
- "show_letters preferences space period Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "wide"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "ԱԲԳ"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
period:
outline: "special"
text: "."
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"

View File

@ -1,73 +0,0 @@
# Maintained by Patrick Jörg <patrickjoerg@gmx.ch>. No Copyright, enjoy!
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 48, height: 52 }
wide: { width: 59, height: 52 }
spaceline: { width: 70, height: 52 }
special: { width: 28, height: 52 }
views:
base:
- "q w e r t z u i o p ü"
- "a s d f g h j k l ö ä"
- "Shift_L y x c v b n m BackSpace"
- "show_numbers ? ! preferences ' space , . Return"
upper:
- "Q W E R T Z U I O P Ü"
- "A S D F G H J K L Ö Ä"
- "Shift_L Y X C V B N M BackSpace"
- "show_numbers - _ preferences \" space , . Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ * + - = ( ) ~ < >"
- "show_symbols # & / \\ √ ; : BackSpace"
- "show_letters ? ! preferences _ space , . Return"
symbols:
- "€ $ £ ¥ % | § µ [ ]"
- "© ® § ` ^ { } · ¡ ¿"
- "show_numbers « » ÷ × “ ” „ BackSpace"
- "show_letters 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_letters:
action:
set_view: "base"
outline: "altline"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
space:
outline: "spaceline"
label: " "
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
"\"":
keysym: "quotedbl"

View File

@ -1,93 +0,0 @@
# Maintained by: Jordy Bossy <jordi@bossy.space>
# and Patrick Jörg <patrickjoerg@gmx.ch>. No Copyright, enjoy!
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 48, height: 52 }
wide: { width: 59, height: 52 }
spaceline: { width: 70, height: 52 }
special: { width: 28, height: 52 }
views:
base:
- "q w e r t z u i o p ü"
- "a s d f g h j k l ö ä"
- "Shift_L y x c v b n m BackSpace"
- "show_numbers show_eschars preferences ' space , . Return"
upper:
- "Q W E R T Z U I O P Ü"
- "A S D F G H J K L Ö Ä"
- "Shift_L Y 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 # & / \\ √ ; : BackSpace"
- "show_letters show_eschars preferences _ space , . Return"
symbols:
- "€ $ £ ¥ % | § µ [ ]"
- "© ® § ` ^ { } · ¡ ¿"
- "show_numbers « » ÷ × “ ” „ BackSpace"
- "show_letters show_eschars preferences - space , . Return"
eschars:
- "à â ç é è ê î ô ù û"
- "À Â Ç É È Ê Î Ô Ù Û"
- "show_numbers æ œ ä ë ï ö ü BackSpace"
- "show_letters_from_accents 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_letters:
action:
set_view: "base"
outline: "altline"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
show_eschars:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "âÂ"
show_letters_from_accents:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "âÂ"
space:
outline: "spaceline"
label: " "
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
"\"":
keysym: "quotedbl"

View File

@ -1,93 +0,0 @@
# Maintained by: Jordy Bossy <jordy@bossy.space>
# and Patrick Jörg <patrickjoerg@gmx.ch>. No Copyright, enjoy!
---
outlines:
default: { width: 48, height: 42 }
altline: { width: 81, height: 42 }
wide: { width: 108, height: 42 }
spaceline: { width: 216, height: 42 }
special: { width: 48, height: 42 }
views:
base:
- "q w e r t z u i o p ü"
- "a s d f g h j k l ö ä"
- "Shift_L y x c v b n m BackSpace"
- "show_numbers show_eschars preferences ' space , . Return"
upper:
- "Q W E R T Z U I O P Ü"
- "A S D F G H J K L Ö Ä"
- "Shift_L Y 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 # & / \\ √ ; : BackSpace"
- "show_letters show_eschars preferences _ space , . Return"
symbols:
- "€ $ £ ¥ % | § µ [ ]"
- "© ® § ` ^ { } · ¡ ¿"
- "show_numbers « » ÷ × “ ” „ BackSpace"
- "show_letters show_eschars preferences - space , . Return"
eschars:
- "à â ç é è ê î ô ù û"
- "À Â Ç É È Ê Î Ô Ù Û"
- "show_numbers æ œ ä ë ï ö ü BackSpace"
- "show_letters_from_accents 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_letters:
action:
set_view: "base"
outline: "altline"
label: "abc"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
show_eschars:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "äÄ"
show_letters_from_accents:
action:
locking:
lock_view: "eschars"
unlock_view: "base"
outline: "altline"
label: "âÂ"
space:
outline: "spaceline"
label: " "
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
"\"":
keysym: "quotedbl"

View File

@ -1,81 +0,0 @@
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 62, height: 52 }
spaceline: { width: 106.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 preferences space at period Return"
upper:
- "Q W E R T Y U I O P"
- "A S D F G H J K L"
- "Shift_L Z X C V B N M BackSpace"
- "show_numbers preferences space at period Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # $ % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space at period Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ € ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters preferences space at period 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
at:
outline: "special"
text: "@"
preferences:
action: show_prefs
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "wide"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "ABC"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
period:
outline: "special"
text: "."
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"

View File

@ -1,20 +0,0 @@
---
margins: { top: 4, side: 0, bottom: 4 }
outlines:
default: { width: 120, height: 52 }
views:
base:
- "1 2 3"
- "4 5 6"
- "7 8 9"
- "BackSpace 0 Return"
buttons:
BackSpace:
icon: "edit-clear-symbolic"
action: erase
Return:
icon: "key-enter"
keysym: "Return"

View File

@ -1,89 +0,0 @@
---
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

@ -1,89 +0,0 @@
---
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,81 +0,0 @@
---
outlines:
default: { width: 35.33, height: 52 }
altline: { width: 52.67, height: 52 }
wide: { width: 62, height: 52 }
spaceline: { width: 106.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 preferences space slash period Return"
upper:
- "Q W E R T Y U I O P"
- "A S D F G H J K L"
- "Shift_L Z X C V B N M BackSpace"
- "show_numbers preferences space slash period Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # $ % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space slash period Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ € ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters preferences space slash period Return"
buttons:
Shift_L:
action:
locking:
lock_view: "upper"
unlock_view: "base"
outline: "altline"
icon: "key-shift"
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
action: erase
preferences:
action: show_prefs
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:
action:
set_view: "numbers"
outline: "wide"
label: "123"
show_numbers_from_symbols:
action:
set_view: "numbers"
outline: "altline"
label: "123"
show_letters:
action:
set_view: "base"
outline: "wide"
label: "ABC"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
period:
outline: "special"
text: "."
slash:
outline: "special"
text: "/"
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"

22
data/langs/bg-BG.txt Normal file
View File

@ -0,0 +1,22 @@
ara Арабски
be Белгийски
bg Български
br Бразилски
cz Чешки
de Немски
dk Датски
es Испански
emoji Емоджи
fi Фински
fr Френски
gr Гръцки
it Италиански
jp Японски
no Норвежки
pl Полски
ru Руски
se Шведски
th Тайски
ua Украински
terminal Терминал
us Английски (САЩ)

21
data/langs/cs-CZ.txt Normal file
View File

@ -0,0 +1,21 @@
be Belgická
cz Česká
cz+qwerty Česká (QWERTY)
de Německá
dk Dánská
emoji Emoji
es Španělská
fi Finská
fr Francouzská
gr Řecká
it Italská
jp Japonská
jp+kana Japonská (Kana)
no Norská
pl Polská
ru Ruská
se Švédská
terminal Terminál
th Thajská
ua Ukrajinská
us Anglická (USA)

0
data/langs/de-DE.txt Normal file
View File

2
data/langs/en-US.txt Normal file
View File

@ -0,0 +1,2 @@
emoji Emoji
terminal Terminal

7
data/langs/es-ES.txt Normal file
View File

@ -0,0 +1,7 @@
us Inglés (EE.UU.)
de Alemán
el Griego
es Español
it Italiano
jp+kana Japonés (Kana)
no Noruego

2
data/langs/fa-IR.txt Normal file
View File

@ -0,0 +1,2 @@
emoji ایموجی
terminal ترمینال

18
data/langs/fur-IT.txt Normal file
View File

@ -0,0 +1,18 @@
be Belgjic
br Brasilian
de Todesc
dk Danês
es Spagnûl
fi Finlandês
fr Francês
it+fur Furlan
gr Grêc
it Talian
jp+kana Gjaponês (Kana)
no Norvegjês
pl Polac
ru Rus
se Svedês
terminal Terminâl
ua Ucrain
us American (USA)

19
data/langs/he-IL.txt Normal file
View File

@ -0,0 +1,19 @@
be בלגית
br פורטוגזית (ברזיל)
cz צ'כית
de גרמנית
dk דנית
es ספרדית
emoji אימוג'י
fi פינית
fr צרפתית
gr יוונית
il עברית
it איטלקית
no נורווגית
pl פולנית
ru רוסית
se שוודית
terminal טרמינל
ua אוקראינית
us אנגלית (ארה"ב)

0
data/langs/ja-JP.txt Normal file
View File

2
data/langs/pl-PL.txt Normal file
View File

@ -0,0 +1,2 @@
emoji emoji
terminal terminal

11
data/langs/ru-RU.txt Normal file
View File

@ -0,0 +1,11 @@
de Немецкий
es Испанский
fi Финский
gr Греческий
it Итальянский
no Норвежский
pl Польский
ru Русский
se Шведский
terminal Терминал
us Английский (США)

View File

@ -12,7 +12,7 @@ desktopconf.set('bindir', bindir)
desktop_file = 'sm.puri.Squeekboard.desktop'
i18n.merge_file(
i18n.merge_file('desktop',
input: configure_file(
input: desktop_file + '.in.in',
output: desktop_file + '.in',

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="app-menu">
<item>
<!-- translators: This is a emmoji keyboard layout -->
<attribute name="label" translatable="yes">Emoji</attribute>
<attribute name="action">layout</attribute>
<attribute name="target">emoji</attribute>
</item>
<item>
<!-- translators: This is a terminal keyboard layout -->
<attribute name="label" translatable="yes">Terminal</attribute>
<attribute name="action">layout</attribute>
<attribute name="target">terminal</attribute>
</item>
<section>
<item>
<attribute name="label" translatable="yes">Keyboard Settings</attribute>
<attribute name="action">settings</attribute>
</item>
</section>
</menu>
</interface>

19
data/popup.ui Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkPopoverMenu" id="main_menu">
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
</object>
<packing>
<property name="submenu">main</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -1,7 +1,7 @@
[Desktop Entry]
Name=Squeekboard
GenericName=On Screen Keyboard
Comment=An on screen virtual keyboard
GenericName=Squeekboard Virtual Keyboard
Comment=Virtual Keyboard
Exec=@bindir@/squeekboard
Terminal=false
Type=Application

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/sm/puri/squeekboard">
<file compressed="true">common.css</file>
<file compressed="true">style.css</file>
<file compressed="true">style-Adwaita:dark.css</file>
<file compressed="true" preprocess="xml-stripblanks">popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks">popup.ui</file>
<file>icons/key-enter.svg</file>
<file>icons/key-shift.svg</file>
<file>icons/keyboard-mode-symbolic.svg</file>

View File

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

View File

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

227
debian/changelog vendored
View File

@ -1,230 +1,3 @@
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
[ Khaled Eldoheiri ]
* Introduce Arabic keyboard layout
[ Dorota Czaplejewicz ]
* Docs: Release procedure
* build: Fix "any" dependency versioning
* readme: Mention the layout guide
* dbus: Hint that maybe squeekboard is running
* readme: Change self-reference to repo to gnome
* docs: Move env vars section to debugging
* readme: Clarify basic running steps
* readme: Put emphasis on being an input method
* readme: Update features
* ci: Use cached artifacts in the test
* ci: Move release test to the start
* ci: Start lintian test right after deb build
* ci: Add git revision and CI pipeline number to .deb artifacts
* ci: Use bookworm image
* ci: Reformat yaml file
* ci: Include pre-build style check
* popover: Fix reentrancy problem
* submission: Wrap the structure in a safe wrapper
* util: Add ArcWrapped
* animation: Prototype a way of handling state and applying it separately
* state: Connect the animation state machine to the rest
* event_loop: Separate and use for physical keyboard presence
* Revert "util: Add ArcWrapped"
* Revert "ci: Use bookworm image"
* ci: Fix formatting
* ci: Make indentation close to original again
* cargo: version bump
[ Jordi ]
* Introduce Swiss French keyboard layout
* improve accents layout behavior and code cleaning
[ Plamen Stoev ]
* Rename bg to bg+phonetic
* Add 'bg' layout
* Translate more layout names in Bulgarian
[ William Wold ]
* Show error when Layer Shell is not supported
* Update entry.py file path in readme
* Update zwp_text_input_v3 (comment changes only)
* Update zwp_input_method_v2
[ Patrick Jörg ]
* Introduce Swiss German keyboard layout
* Introducing ch+de layout and modified ch.yaml fallback
* Added ch_wide
[ ZenWalker ]
* layersurface: avoid duplicate assignment
[ T. Zack Crawford ]
* Update tutorial.md to clarify steps in creating a custom layout
[ Guido Günther ]
* gitlab-ci: Adjust CI tags
* gitlab-ci: Drop build for outdated distributions
* data: Fix build with meson 0.60.0
* main: Remove trailing whitespace
* main: Honor --help and -h
* eek-renderer: Add log domain
* eek-renderer: Fix indentation
* eek-renderer: Honor theme changes (Closes: #296)
* main: Drop broken support G_BUS_TYPE_SYSTEM
* main: Avoid two error variables in the same function
* main: Use dark theme when run in a Phosh session (Closes: #242)
* gtk-keyboard: Don't set variable to NULL twice in a row
* renderer: Use `g_debug ()`
* main: Add debug flag to always show squeekboard on start
* renderer: Disconnect theme change signal handler
* main: Add debug flag to show GTK inspector
* README: Document SQUEEKBOARD_DEBUG environment variable
* Move style-check to separate script
* Honor input-purpose PIN
* entry: Use a scrolled window
* entry: Set a margin on the grids
* entry: Add a random text entry field
* imservice: Invoke eekboard_context_service_set_hint_purpose unconditionally
(Closes: #311)
* langs: Don't use empty translation file (Closes: #313)
* Initialize gettext
* Reuse the unused popover ui file for i18n (Closes: #315)
* po: Add German translation
* gresources: Drop popup.ui
* Revert "gresources: Drop popup.ui"
* gitlab-ci: Add PKG_ONLY
* layout: Drop trailing whitespace
* Use special pin keyboard
* layout: Keep content purpose around
* renderer: Set style class based on input purpose
* pin: Use less margin
* debian: Install translations
* debian: Switch to dh 13
* debian: Install desktop file
* eekboard-context-service: Don't translate property names
* server-context-servide: Don't translate application name
* data: Make generic name truly generic
* po: Add desktop file to translatable files
* Add URL and EMail keyboard variants for us (Closes: #65)
* gitignore: Drop zanata dir
* gitignore: Ignore generated po files
* popover: Move Emoji and Terminal to ui file
* popover: Add translator notes
* popover: Make the ui file match the code file name
* Remove emoji and terminal from translations
* popover: Don't complain about missing translations
* Drop custom translation handling
* Drop locale_config
* Remove custom translations
[ PhilProg ]
* Add documentation about compositors
[ Norayr Chilingarian ]
* armenian typewriter and phonetic keyboards.
* armenian layout also added to meson.build etc.
[ Arnaud Ferraris ]
* resources: add wide FR terminal keyboard
[ Sebastian Krzyszkowiak ]
* renderer: Take context scale into account when drawing icons (Closes: #139)
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sun, 19 Dec 2021 14:11:06 +0000
squeekboard (1.14.0pureos0~amber0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
10

4
debian/control vendored
View File

@ -4,7 +4,7 @@ Priority: optional
Maintainer: Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm>
Build-Depends:
cargo,
debhelper-compat (= 13),
debhelper (>= 10),
meson (>=0.51.0),
ninja-build,
pkg-config,
@ -20,13 +20,13 @@ Build-Depends:
librust-gtk+v3-22-dev (>= 0.5),
librust-gtk-sys-dev,
librust-maplit-1-dev (>= 1.0),
librust-regex-1-dev (>= 1.1),
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

58
debian/control-newer vendored
View File

@ -1,58 +0,0 @@
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)
ifneq (,$(filter $(distrel),sid))
newer = true
ifneq (,$(filter $(distrel),buster amber))
legacy = true
else
newer = false
legacy = false
endif
%:
@ -38,6 +38,6 @@ endif
# causing Cargo to refuse to build with a crates.io copy
override_dh_auto_configure:
[ ! -f Cargo.lock ] || rm Cargo.lock
dh_auto_configure -- -Dnewer=$(newer)
dh_auto_configure -- -Dlegacy=$(legacy)
override_dh_autoreconf:

View File

@ -1,4 +1,2 @@
tools/squeekboard-restyled usr/bin
usr/bin/squeekboard /usr/bin
usr/share/applications/
usr/share/locale/

View File

@ -90,15 +90,6 @@ Layouts can be selected using the GNOME Settings application.
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'de')]"
```
### Environment Variables
Besides the environment variables supported by GTK and [GLib](https://docs.gtk.org/glib/running.html) applications
squeekboard honors the `SQUEEKBOARD_DEBUG` environment variable which can
contain a comma separated list of:
- `force-show` : Show squeekboard on startup independent of any gsettings or compositor requests
- `gtk-inspector`: Spawn [gtk-inspector](https://wiki.gnome.org/Projects/GTK/Inspector)
Coding
------
@ -120,16 +111,6 @@ User interface modules should:
### Style
Note that some portions, like the .gitlab-ci.yml file have accummulated enough style/whitespace conflicts that an enforced style checker is now applied.
To fix your contributions before submitting a change, use:
```
./tools/style-check_source --apply
```
* * *
Code submitted should roughly match the style of surrounding code. Things that will *not* be accepted are ones that often lead to errors:
- skipping brackets `{}` after every `if()`, `else`, and similar ([SCI CERT C: EXP19-C](https://wiki.sei.cmu.edu/confluence/display/c/EXP19-C.+Use+braces+for+the+body+of+an+if%2C+for%2C+or+while+statement))
@ -194,74 +175,14 @@ All Cargo dependencies must be selected in the version available in PureOS, and
Dependencies must be specified in `Cargo.toml` with 2 numbers: "major.minor". Since bugfix version number is meant to not affect the interface, this allows for safe updates.
Releases
----------
Squeekboard should get a new release every time something interesting comes in. Preferably when there are no known bugs too. People will rely on theose releases, after all.
### 1. Update `Cargo.toml`.
While the file is not actually used, it's a good idea to save the config in case some rare bug appears in dependencies.
`Cargo.lock` is used for remembering the revisions of all Rust dependencies. It must correspond to the default dependency configuration: without flags to use older or newer versions of dependencies. It should be updated often, preferably with each bugfix revision, and in a commit on its own:
```
cd squeekboard-build
.../squeekboard-source/cargo.sh update
cd build_dir
ninja ./Cargo.toml
sh /source_path/cargo.sh update
ninja test
cp ./Cargo.lock .../squeekboard-source
cp ./Cargo.lock /source_path/
```
Then commit the updated `Cargo.lock`.
### 2. Choose the version number
Squeekboard tries to use semantic versioning. It's 3 numbers separated by dots: "a.b.c". Releases which only fix bugs and nothing else are "a.b.c+1". Releases which add user-visible features in addition to bug fixes are "a.b+1.0". Releases which, in addition to the previous, change *the user contract* in incompatible ways are "a+1.0.0". "The user contract" means plugin APIs that are deemed stable, or the way language switching works, etc. In other words, incompatible changes to developers, or big changes to users bump "a" to the next natural number.
### 3. Update the number in `meson.build`
It's in the `project(version: xxx)` statement.
### 4. Update packaging
Packaging is in the `debian/` directory, and creates builds that can be quickly tested.
```
cd squeekboard-source
gbp dch --multimaint-merge --ignore-branch
```
Inspect `debian/changelog`, and make sure the first line contains the correct version number and suite. For example:
```
squeekboard (1.13.0pureos0~amber0) amber-phone; urgency=medium
```
Commit the updated `debian/changelog`. The commit message should contain the release version and a description of changes.
> Release 1.13.0 "Externality"
>
> Changes:
>
> - A system for latching and locking views
> ...
### 5. Create a signed tag for downstreams
The tag should be the version number with "v" in front of it. The tag message should be "squeekboard" and the tag name. Push it to the upstream repository:
```
git tag -s -u my_address@example.com v1.13.0 -m "squeekboard v1.13.0"
git push v1.13.0
```
### 5. Create a signed tag for packaging
Similar to the above, but format it for the PureOS downstream.
```
git tag -s -u my_address@example.com 'pureos/1.13.0pureos0_amber0' -m 'squeekboard 1.13.0pureos0_amber0'
git push 'pureos/1.13.0pureos0_amber0'
```
### 6. Rejoice
You released a new version of Squeekboard, and made it available on PureOS. Congratulations.
Since version 1.9.3, `Cargo.lock` is not actually used by the build system, due to `Cargo.toml` being generated at every build.

View File

@ -21,8 +21,7 @@ Creating a layout is easy. You don't need to recompile things, just edit and tes
### Creating the keyboard layout
* To be written: For the time being, take a look at [Using non-latin language on Librem 5](https://forums.puri.sm/t/using-non-latin-language-on-librem-5/7103/5)
* Select and enable the input source you would like to change from the Region & Language section of the device settings. Perhaps use "A user-defined custom layout" listed under Other.
* Find the correct name of the .yaml file associated with that input source. This can be found with the command
* The correct name of the .yaml file can be found with the command
```
gsettings get org.gnome.desktop.input-sources sources
@ -30,14 +29,12 @@ gsettings get org.gnome.desktop.input-sources sources
The output should be something like this: `[('xkb', 'us'), ('xkb', 'de')]`
So for example “de.yaml” would be the correct name for the German keyboard layout.
If the name of your layout is not translated correctly in the list, you can fix it by adding it and recompiling Squeekboard.
There is also associated files for that layout in landscape, terminal, number, emoji mode. They can be found at something analogous to `us_wide.yaml`, `terminal/us.yaml`, `number/us.yaml`, `emoji/us.yaml`, respectively.
If the name of your layout is not translated correctly in the list, you can fix it by adding it and recompiling Squeekboard.
### Testing the layout
Copy your yaml file to `~/.local/share/squeekboard/keyboards/` for testing purposes. From there it should get picked up by squeekboard automatically.
The yaml file will overwrite the default settings for that layout. If you want to go back to default, simply remove the file.
You can also use the `test_layout` tool from the -devel package to check it for errors:

View File

@ -316,6 +316,7 @@ eek_gtk_keyboard_dispose (GObject *object)
if (priv->renderer) {
eek_renderer_free(priv->renderer);
priv->renderer = NULL;
priv->renderer = NULL;
}
if (priv->keyboard) {

View File

@ -18,8 +18,6 @@
* 02110-1301 USA
*/
#define G_LOG_DOMAIN "squeekboard-eek-renderer"
#include <math.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
@ -56,13 +54,6 @@ render_outline (cairo_t *cr,
position.x, position.y, position.width, position.height);
}
float get_scale(cairo_t *cr) {
double width = 1;
double height = 1;
cairo_user_to_device_distance (cr, &width, &height);
return width;
}
/// Rust interface
void eek_render_button_in_context(uint32_t scale_factor,
cairo_t *cr,
@ -79,17 +70,16 @@ void eek_render_button_in_context(uint32_t scale_factor,
/* render icon (if any) */
if (icon_name) {
int context_scale = ceil (get_scale (cr));
cairo_surface_t *icon_surface =
eek_renderer_get_icon_surface (icon_name, 16, scale_factor * context_scale);
eek_renderer_get_icon_surface (icon_name, 16, scale_factor);
if (icon_surface) {
double width = cairo_image_surface_get_width (icon_surface);
double height = cairo_image_surface_get_height (icon_surface);
gint width = cairo_image_surface_get_width (icon_surface);
gint height = cairo_image_surface_get_height (icon_surface);
cairo_save (cr);
cairo_translate (cr,
(bounds.width - width / (scale_factor * context_scale)) / 2,
(bounds.height - height / (scale_factor * context_scale)) / 2);
(bounds.width - (double)width / scale_factor) / 2,
(bounds.height - (double)height / scale_factor) / 2);
cairo_rectangle (cr, 0, 0, width, height);
cairo_clip (cr);
/* Draw the shape of the icon using the foreground color */
@ -122,7 +112,7 @@ eek_get_style_context_for_button (EekRenderer *self,
const char *name,
const char *outline_name,
const char *locked_class,
uint64_t pressed)
uint64_t pressed)
{
GtkStyleContext *ctx = self->button_context;
/* Set the name of the button on the widget path, using the name obtained
@ -236,8 +226,6 @@ eek_renderer_free (EekRenderer *self)
g_object_unref(self->css_provider);
g_object_unref(self->view_context);
g_object_unref(self->button_context);
g_clear_signal_handler (&self->theme_name_id, gtk_settings_get_default());
// this is where renderer-specific surfaces would be released
free(self);
@ -269,49 +257,12 @@ static GType button_type(void) {
return type;
}
static void
on_gtk_theme_name_changed (GtkSettings *settings, gpointer foo, EekRenderer *self)
{
g_autofree char *name = NULL;
g_object_get (settings, "gtk-theme-name", &name, NULL);
g_debug ("GTK theme: %s", name);
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (self->css_provider));
gtk_style_context_remove_provider (self->button_context,
GTK_STYLE_PROVIDER(self->css_provider));
gtk_style_context_remove_provider (self->view_context,
GTK_STYLE_PROVIDER(self->css_provider));
g_set_object (&self->css_provider, squeek_load_style());
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER (self->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_style_context_add_provider (self->button_context,
GTK_STYLE_PROVIDER(self->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
gtk_style_context_add_provider (self->view_context,
GTK_STYLE_PROVIDER(self->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
static void
renderer_init (EekRenderer *self)
{
self->pcontext = NULL;
self->scale_factor = 1;
GtkSettings *gtk_settings;
gtk_settings = gtk_settings_get_default ();
self->theme_name_id = g_signal_connect (gtk_settings, "notify::gtk-theme-name",
G_CALLBACK (on_gtk_theme_name_changed), self);
self->css_provider = squeek_load_style();
}
@ -323,7 +274,6 @@ eek_renderer_new (LevelKeyboard *keyboard,
renderer_init(renderer);
renderer->pcontext = pcontext;
g_object_ref (renderer->pcontext);
const char *purpose_class = "normal";
/* Create a style context for the layout */
GtkWidgetPath *path = gtk_widget_path_new();
@ -345,55 +295,6 @@ eek_renderer_new (LevelKeyboard *keyboard,
if (squeek_layout_get_kind(keyboard->layout) == ARRANGEMENT_KIND_WIDE) {
gtk_widget_path_iter_add_class(path, -1, "wide");
}
/* Add style classes based on purpose */
switch (squeek_layout_get_purpose (keyboard->layout)) {
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL:
purpose_class = "normal";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_ALPHA:
purpose_class = "alpha";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DIGITS:
purpose_class = "digits";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER:
purpose_class = "number";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE:
purpose_class = "phone";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_URL:
purpose_class = "url";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_EMAIL:
purpose_class = "email";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NAME:
purpose_class = "name";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PASSWORD:
purpose_class = "password";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PIN:
purpose_class = "pin";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATE:
purpose_class = "date";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TIME:
purpose_class = "time";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_DATETIME:
purpose_class = "datetime";
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL:
purpose_class = "terminal";
break;
default:
g_warning ("Unknown input purpose %d", squeek_layout_get_purpose(keyboard->layout));
}
gtk_widget_path_iter_add_class(path, -1, purpose_class);
gtk_widget_path_append_type(path, button_type());
renderer->button_context = gtk_style_context_new ();
gtk_style_context_set_path(renderer->button_context, path);

View File

@ -39,8 +39,6 @@ typedef struct EekRenderer
GtkStyleContext *button_context; // TODO: maybe move a copy to each button
/// Style class for rendering the view and button CSS.
gchar *extra_style; // owned
// Theme name change signal handler id
gulong theme_name_id;
// Mutable state
gint scale_factor; /* the outputs scale factor */

View File

@ -217,7 +217,7 @@ eekboard_context_service_class_init (EekboardContextServiceClass *klass)
* Emitted when @context is destroyed.
*/
signals[DESTROYED] =
g_signal_new ("destroyed",
g_signal_new (I_("destroyed"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
0,

View File

@ -1,7 +1,7 @@
project(
'squeekboard',
'c', 'rust',
version: '1.17.0',
version: '1.14.0',
license: 'GPLv3',
meson_version: '>=0.51.0',
default_options: [
@ -36,6 +36,8 @@ add_project_arguments(
i18n = import('i18n')
conf_data = configuration_data()
if get_option('buildtype').startswith('debug')
add_project_arguments('-DDEBUG=1', language : 'c')
endif
@ -61,7 +63,6 @@ endif
prefix = get_option('prefix')
bindir = join_paths(prefix, get_option('bindir'))
datadir = join_paths(prefix, get_option('datadir'))
localedir = join_paths(prefix, get_option('localedir'))
desktopdir = join_paths(datadir, 'applications')
pkgdatadir = join_paths(datadir, meson.project_name())
if get_option('depdatadir') == ''
@ -71,10 +72,6 @@ else
endif
dbusdir = join_paths(depdatadir, 'dbus-1/interfaces')
conf_data = configuration_data()
conf_data.set_quoted('GETTEXT_PACKAGE', 'squeekboard')
conf_data.set_quoted('LOCALEDIR', localedir)
summary = [
'',
'------------------',
@ -99,9 +96,9 @@ cargo_toml_base = configure_file(
cargo_deps = files('Cargo.deps')
if get_option('newer') == true
cargo_build_flags += ['--features', 'glib_v0_14']
cargo_deps = files('Cargo.deps.newer')
if get_option('legacy') == true
cargo_build_flags += ['--features', 'gtk_v0_5,gio_v0_5,rustc_less_1_36']
cargo_deps = files('Cargo.deps.legacy')
endif
cat = find_program('cat')
@ -117,7 +114,6 @@ cargo_script = find_program('cargo.sh')
cargo_build = find_program('cargo_build.py')
subdir('data')
subdir('po')
subdir('protocols')
subdir('src')
subdir('tools')

View File

@ -7,9 +7,9 @@ option('tests',
type: 'boolean', value: true,
description: 'Whether to compile unit tests')
option('newer',
option('legacy',
type: 'boolean', value: false,
description: 'Build with dependencies newer than those of Byzantium')
description: 'Build with Deban Buster versions of dependencies')
option('strict',
type: 'boolean', value: true,

View File

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

View File

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

View File

@ -1,45 +0,0 @@
# 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"

View File

@ -1,22 +0,0 @@
# German 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.
#
msgid ""
msgstr ""
"Project-Id-Version: squeekboard\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-03 18:41+0100\n"
"PO-Revision-Date: 2021-12-03 18:41+0100\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: data/popup.ui:15
msgid "Keyboard Settings"
msgstr "Tastatureinstellungen"

View File

@ -1,45 +0,0 @@
# 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 "یک صفحهٔ کلید لمسی مجازی"

View File

@ -1,46 +0,0 @@
# 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ö"

View File

@ -1,45 +0,0 @@
# 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"

View File

@ -1,52 +0,0 @@
# 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"

View File

@ -1,46 +0,0 @@
# 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 "מקלדת מדומה על המסך"

View File

@ -1,2 +0,0 @@
i18n = import('i18n')
i18n.gettext('squeekboard', preset : 'glib')

View File

@ -1,48 +0,0 @@
# 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"

View File

@ -1,47 +0,0 @@
# 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"

View File

@ -1,47 +0,0 @@
# 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"

View File

@ -1,49 +0,0 @@
# 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"

View File

@ -1,48 +0,0 @@
# 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"

View File

@ -1,51 +0,0 @@
# 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

@ -2,9 +2,6 @@
<protocol name="input_method_unstable_v2">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Copyright © 2012, 2013 Intel Corporation
Copyright © 2015, 2016 Jan Arne Petersen
Copyright © 2017, 2018 Red Hat, Inc.
@ -32,14 +29,14 @@
<description summary="Protocol for creating input methods">
This protocol allows applications to act as input methods for compositors.
An input method context is used to manage the state of the input method.
Text strings are UTF-8 encoded, their indices and lengths are in bytes.
This document adheres to the RFC 2119 when using words like "must",
"should", "may", etc.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
@ -75,19 +72,18 @@
Notification that a text input focused on this seat requested the input
method to be activated.
This event serves the purpose of providing the compositor with an
active input method.
This request must be issued every time a text input requests an input
method.
This event resets all state associated with previous enable, disable,
surrounding_text, text_change_cause, and content_type events, as well
as the state associated with set_preedit_string, commit_string, and
delete_surrounding_text requests. In addition, it marks the
zwp_input_method_v2 object as active, and makes any existing
zwp_input_popup_surface_v2 objects visible.
This request resets all state associated with previous enable, disable,
set_surrounding_text, set_text_change_cause, set_content_type, and
set_cursor_rectangle requests, as well as the state associated with
preedit_string, commit_string, and delete_surrounding_text events. In
addition, it marks the input method object as active.
The surrounding_text, and content_type events must follow before the
next done event if the text input supports the respective
functionality.
The set_surrounding_text, set_content_type and set_cursor_rectangle
requests must follow before the next done event if the text input
supports the respective functionality.
State set with this event is double-buffered. It will get applied on
the next zwp_input_method_v2.done event, and stay valid until changed.
@ -96,12 +92,13 @@
<event name="deactivate">
<description summary="deactivate event">
Notification that no focused text input currently needs an active
input method on this seat.
Notification that this seat's current text input requested the input
method to be deactivated.
This event marks the zwp_input_method_v2 object as inactive. The
compositor must make all existing zwp_input_popup_surface_v2 objects
invisible until the next activate event.
This event mrks the zwp_input_method_v2 object as inactive.
When the seat has the keyboard capability the text-input focus follows
the keyboard focus.
State set with this event is double-buffered. It will get applied on
the next zwp_input_method_v2.done event, and stay valid until changed.
@ -110,7 +107,7 @@
<event name="surrounding_text">
<description summary="surrounding text event">
Updates the surrounding plain text around the cursor, excluding the
Sets the surrounding plain text around the cursor, excluding the
preedit text.
If any preedit text is present, it is replaced with the cursor for the
@ -128,7 +125,7 @@
buffer. If there is no selected text, anchor must be the same as
cursor.
If this event does not arrive before the first done event, the input
If this request does not arrive before the first done event, the input
method may assume that the text input does not support this
functionality and ignore following surrounding_text events.
@ -169,7 +166,7 @@
<event name="content_type">
<description summary="content purpose and hint">
Indicates the content type and hint for the current
zwp_input_method_v2 instance.
input_method_context instance.
Values set with this event are double-buffered. They will get applied
on the next zwp_input_method_v2.done event.
@ -216,14 +213,14 @@
4000 bytes.
Values set with this event are double-buffered. They must be applied
and reset to initial on the next zwp_text_input_v3.commit request.
and reset to initial on the next zwp_text_input_v3.done event.
The initial value of text is an empty string.
</description>
<arg name="text" type="string"/>
</request>
<request name="set_preedit_string">
<request name="preedit_string">
<description summary="pre-edit string">
Send the pre-edit string text to the application text input.
@ -278,7 +275,7 @@
<request name="commit">
<description summary="apply state">
Apply state changes from commit_string, set_preedit_string and
Apply state changes from commit_string, preedit_string and
delete_surrounding_text requests.
The state relating to these events is double-buffered, and each one
@ -297,10 +294,11 @@
The serial number reflects the last state of the zwp_input_method_v2
object known to the client. The value of the serial argument must be
equal to the number of done events already issued by that object. When
the compositor receives a commit request with a serial different than
the number of past done events, it must proceed as normal, except it
should not change the current state of the zwp_input_method_v2 object.
equal to the number of done events already issued on that object.
When the compositor receives a commit request with a serial different than
the number of past commit requests, it must proceed as normal, except
it should not change the current state of the zwp_input_method_v2
object.
</description>
<arg name="serial" type="uint"/>
</request>
@ -309,10 +307,6 @@
<description summary="create popup surface">
Creates a new zwp_input_popup_surface_v2 object wrapping a given
surface.
The surface gets assigned the "input_popup" role. If the surface
already has an assigned role, the compositor must issue a protocol
error.
</description>
<arg name="id" type="new_id" interface="zwp_input_popup_surface_v2"/>
<arg name="surface" type="object" interface="wl_surface"/>
@ -333,10 +327,9 @@
Releasing the resulting wl_keyboard object releases the grab.
</description>
<arg name="keyboard" type="new_id"
interface="zwp_input_method_keyboard_grab_v2"/>
<arg name="keyboard" type="new_id" interface="wl_keyboard"/>
</request>
<event name="unavailable">
<description summary="input method unavailable">
The input method ceased to be available.
@ -354,25 +347,15 @@
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the text input">
Destroys the zwp_text_input_v2 object and any associated child
objects, i.e. zwp_input_popup_surface_v2 and
zwp_input_method_keyboard_grab_v2.
</description>
</request>
<request name="destroy" type="destructor"/>
</interface>
<interface name="zwp_input_popup_surface_v2" version="1">
<description summary="popup surface">
This interface marks a surface as a popup for interacting with an input
method.
This surface is a popup for interacting with an input method.
The compositor should place it near the active text input area. It must
be visible if and only if the input method is in the active state.
The client must not destroy the underlying wl_surface while the
zwp_input_popup_surface_v2 object exists.
</description>
<event name="text_input_rectangle">
@ -392,75 +375,6 @@
<request name="destroy" type="destructor"/>
</interface>
<interface name="zwp_input_method_keyboard_grab_v2" version="1">
<!-- Closely follows wl_keyboard version 6 -->
<description summary="keyboard grab">
The zwp_input_method_keyboard_grab_v2 interface represents an exclusive
grab of the wl_keyboard interface associated with the seat.
</description>
<event name="keymap">
<description summary="keyboard mapping">
This event provides a file descriptor to the client which can be
memory-mapped to provide a keyboard mapping description.
</description>
<arg name="format" type="uint" enum="wl_keyboard.keymap_format"
summary="keymap format"/>
<arg name="fd" type="fd" summary="keymap file descriptor"/>
<arg name="size" type="uint" summary="keymap size, in bytes"/>
</event>
<event name="key">
<description summary="key event">
A key was pressed or released.
The time argument is a timestamp with millisecond granularity, with an
undefined base.
</description>
<arg name="serial" type="uint" summary="serial number of the key event"/>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="key" type="uint" summary="key that produced the event"/>
<arg name="state" type="uint" enum="wl_keyboard.key_state"
summary="physical state of the key"/>
</event>
<event name="modifiers">
<description summary="modifier and group state">
Notifies clients that the modifier and/or group state has changed, and
it should update its local state.
</description>
<arg name="serial" type="uint" summary="serial number of the modifiers event"/>
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
<arg name="group" type="uint" summary="keyboard layout"/>
</event>
<request name="release" type="destructor">
<description summary="release the grab object"/>
</request>
<event name="repeat_info">
<description summary="repeat rate and delay">
Informs the client about the keyboard's repeat rate and delay.
This event is sent as soon as the zwp_input_method_keyboard_grab_v2
object has been created, and is guaranteed to be received by the
client before any key press event.
Negative values for either rate or delay are illegal. A rate of zero
will disable any repeating (regardless of the value of delay).
This event can be sent later on as well with a new value if necessary,
so clients should continue listening for the event past the creation
of zwp_input_method_keyboard_grab_v2.
</description>
<arg name="rate" type="int"
summary="the rate of repeating keys in characters per second"/>
<arg name="delay" type="int"
summary="delay in milliseconds since key down until repeating starts"/>
</event>
</interface>
<interface name="zwp_input_method_manager_v2" version="1">
<description summary="input method manager">
The input method manager allows the client to become the input method on

View File

@ -94,12 +94,6 @@
zwp_text_input_v3.disable when there is no longer any input focus on
the current surface.
Clients must not enable more than one text input on the single seat
and should disable the current text input before enabling the new one.
At most one instance of text input may be in enabled state per instance,
Requests to enable the another text input when some text input is active
must be ignored by compositor.
This request resets all state associated with previous enable, disable,
set_surrounding_text, set_text_change_cause, set_content_type, and
set_cursor_rectangle requests, as well as the state associated with
@ -313,9 +307,6 @@
<description summary="enter event">
Notification that this seat's text-input focus is on a certain surface.
If client has created multiple text input objects, compositor must send
this event to all of them.
When the seat has the keyboard capability the text-input focus follows
the keyboard focus. This event sets the current surface for the
text-input object.
@ -330,9 +321,7 @@
set.
The leave notification clears the current surface. It is sent before
the enter notification for the new focus. After leave event, compositor
must ignore requests from any text input instances until next enter
event.
the enter notification for the new focus.
When the seat has the keyboard capability the text-input focus follows
the keyboard focus.

View File

@ -1,22 +0,0 @@
/* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Animation details */
use std::time::Duration;
use crate::outputs::OutputId;
/// The keyboard should hide after this has elapsed to prevent flickering.
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
/// The outwardly visible state of visibility
#[derive(PartialEq, Debug, Clone)]
pub enum Outcome {
Visible {
output: OutputId,
height: u32,
},
Hidden,
}

View File

@ -1,358 +0,0 @@
/* 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

@ -2,9 +2,3 @@
* Autogenerated by the Meson build system.
* Do not edit, your changes will be lost.
*/
#pragma once
#mesondefine GETTEXT_PACKAGE
#mesondefine LOCALEDIR

View File

@ -61,7 +61,7 @@ pub mod c {
};
let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
let layout = ::layout::Layout::new(layout, kind, variant);
let layout = ::layout::Layout::new(layout, kind);
Box::into_raw(Box::new(layout))
}
}
@ -166,13 +166,10 @@ fn get_directory_string(
let layout_purpose = match overlay {
None => match content_purpose {
ContentPurpose::Email => Special("email"),
ContentPurpose::Digits => Special("number"),
ContentPurpose::Number => Special("number"),
ContentPurpose::Digits => Special("number"),
ContentPurpose::Phone => Special("number"),
ContentPurpose::Pin => Special("pin"),
ContentPurpose::Terminal => Special("terminal"),
ContentPurpose::Url => Special("url"),
_ => Default,
},
Some(overlay) => Special(overlay),

View File

@ -19,9 +19,7 @@
#include "config.h"
#include "dbus.h"
#include "main.h"
#include <inttypes.h>
#include <stdio.h>
#include <gio/gio.h>
@ -46,6 +44,11 @@ dbus_handler_destroy(DBusHandler *service)
service->introspection_data = NULL;
}
if (service->context) {
g_signal_handlers_disconnect_by_data (service->context, service);
service->context = NULL;
}
free(service);
}
@ -54,25 +57,38 @@ handle_set_visible(SmPuriOSK0 *object, GDBusMethodInvocation *invocation,
gboolean arg_visible, gpointer user_data) {
DBusHandler *service = user_data;
if (arg_visible) {
squeek_state_send_force_visible (service->state_manager);
} else {
squeek_state_send_force_hidden(service->state_manager);
if (service->context) {
if (arg_visible) {
server_context_service_force_show_keyboard (service->context);
} else {
server_context_service_hide_keyboard (service->context);
}
}
sm_puri_osk0_complete_set_visible(object, invocation);
return TRUE;
}
static void on_visible(DBusHandler *service,
GParamSpec *pspec,
ServerContextService *context)
{
(void)pspec;
gboolean visible;
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (context));
g_object_get (context, "visible", &visible, NULL);
sm_puri_osk0_set_visible(service->dbus_interface, visible);
}
DBusHandler *
dbus_handler_new (GDBusConnection *connection,
const gchar *object_path,
struct squeek_state_manager *state_manager)
const gchar *object_path)
{
DBusHandler *self = calloc(1, sizeof(DBusHandler));
self->object_path = g_strdup(object_path);
self->connection = connection;
self->state_manager = state_manager;
self->dbus_interface = sm_puri_osk0_skeleton_new();
g_signal_connect(self->dbus_interface, "handle-set-visible",
@ -93,9 +109,16 @@ dbus_handler_new (GDBusConnection *connection,
return self;
}
// Exported to Rust
void dbus_handler_set_visible(DBusHandler *service,
uint8_t visible)
void
dbus_handler_set_ui_context(DBusHandler *service,
ServerContextService *context)
{
sm_puri_osk0_set_visible(service->dbus_interface, visible);
g_return_if_fail (!service->context);
service->context = context;
g_signal_connect_swapped (service->context,
"notify::visible",
G_CALLBACK(on_visible),
service);
}

View File

@ -19,20 +19,15 @@
#ifndef DBUS_H_
#define DBUS_H_ 1
#include "sm.puri.OSK0.h"
#include "server-context-service.h"
// From main.h
struct squeek_state_manager;
#include "sm.puri.OSK0.h"
G_BEGIN_DECLS
#define DBUS_SERVICE_PATH "/sm/puri/OSK0"
#define DBUS_SERVICE_INTERFACE "sm.puri.OSK0"
/// Two jobs: accept events, forwarding them to the visibility manager,
/// and get updated from inside to show internal state.
/// Updates are handled in the same loop as the UI.
/// See main.rs
typedef struct _DBusHandler
{
GDBusConnection *connection;
@ -41,14 +36,13 @@ typedef struct _DBusHandler
guint registration_id;
char *object_path;
/// Forward incoming events there
struct squeek_state_manager *state_manager; // shared reference
ServerContextService *context; // unowned reference
} DBusHandler;
DBusHandler * dbus_handler_new (GDBusConnection *connection,
const gchar *object_path,
struct squeek_state_manager *state_manager);
const gchar *object_path);
void dbus_handler_set_ui_context(DBusHandler *service,
ServerContextService *context);
void dbus_handler_destroy(DBusHandler*);
G_END_DECLS
#endif /* DBUS_H_ */

View File

@ -7,10 +7,10 @@ use ::action::{ Action, Modifier };
use ::keyboard;
use ::layout::{ Button, Label, LatchedState, Layout };
use ::layout::c::{ Bounds, EekGtkKeyboard, Point };
use ::submission::c::Submission as CSubmission;
use ::submission::Submission;
use glib::translate::FromGlibPtrNone;
use gtk::prelude::WidgetExt;
use gtk::WidgetExt;
use std::collections::HashSet;
use std::ffi::CStr;
@ -76,11 +76,10 @@ mod c {
layout: *mut Layout,
renderer: EekRenderer,
cr: *mut cairo_sys::cairo_t,
submission: CSubmission,
submission: *const Submission,
) {
let layout = unsafe { &mut *layout };
let submission = submission.clone_ref();
let submission = submission.borrow();
let submission = unsafe { &*submission };
let cr = unsafe { cairo::Context::from_raw_none(cr) };
let active_modifiers = submission.get_active_modifiers();

View File

@ -1,143 +0,0 @@
/* Copyright (C) 2021 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! This drives the loop from the `loop` module.
*
* The tracker loop needs to be driven somehow,
* and connected to the external world,
* both on the side of receiving and sending events.
*
* That's going to be implementation-dependent,
* connecting to some external mechanisms
* for time, messages, and threading/callbacks.
*
* This is the "imperative shell" part of the software,
* and no longer unit-testable.
*/
use crate::event_loop;
use crate::logging;
use crate::main::Commands;
use crate::state::{ Application, Event };
use glib;
use std::sync::mpsc;
use std::thread;
use std::time::Instant;
// Traits
use crate::logging::Warn;
/// Type of the sender that waits for external events
type Sender = mpsc::Sender<Event>;
/// Type of the sender that waits for internal state changes
type UISender = glib::Sender<Commands>;
/// This loop driver spawns a new thread which updates the state in a loop,
/// in response to incoming events.
/// It sends outcomes to the glib main loop using a channel.
/// The outcomes are applied by the UI end of the channel in the `main` module.
// This could still be reasonably tested,
// by creating a glib::Sender and checking what messages it receives.
#[derive(Clone)]
pub struct Threaded {
thread: Sender,
}
impl Threaded {
pub fn new(ui: UISender, initial_state: Application) -> Self {
let (sender, receiver) = mpsc::channel();
let saved_sender = sender.clone();
thread::spawn(move || {
let mut state = event_loop::State::new(initial_state, Instant::now());
loop {
match receiver.recv() {
Ok(event) => {
state = Self::handle_loop_event(&sender, state, event, &ui);
},
Err(e) => {
logging::print(logging::Level::Bug, &format!("Senders hung up, aborting: {}", e));
return;
},
};
}
});
Self {
thread: saved_sender,
}
}
pub fn send(&self, event: Event) -> Result<(), mpsc::SendError<Event>> {
self.thread.send(event)
}
fn handle_loop_event(loop_sender: &Sender, state: event_loop::State, event: Event, ui: &UISender)
-> event_loop::State
{
let now = Instant::now();
let (new_state, commands) = event_loop::handle_event(state.clone(), event, now);
ui.send(commands)
.or_warn(&mut logging::Print, logging::Problem::Bug, "Can't send to UI");
if new_state.scheduled_wakeup != state.scheduled_wakeup {
if let Some(when) = new_state.scheduled_wakeup {
Self::schedule_timeout_wake(loop_sender, when);
}
}
new_state
}
fn schedule_timeout_wake(loop_sender: &Sender, when: Instant) {
let sender = loop_sender.clone();
thread::spawn(move || {
let now = Instant::now();
thread::sleep(when - now);
sender.send(Event::TimeoutReached(when))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't wake visibility manager");
});
}
}
/// For calling in only
mod c {
use super::*;
use crate::state::Presence;
use crate::state::visibility;
use crate::util::c::Wrapped;
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_visible(mgr: Wrapped<Threaded>) {
let sender = mgr.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceVisible))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_force_hidden(sender: Wrapped<Threaded>) {
let sender = sender.clone_ref();
let sender = sender.borrow();
sender.send(Event::Visibility(visibility::Event::ForceHidden))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
#[no_mangle]
pub extern "C"
fn squeek_state_send_keyboard_present(sender: Wrapped<Threaded>, present: u32) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let state =
if present == 0 { Presence::Missing }
else { Presence::Present };
sender.send(Event::PhysicalKeyboard(state))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
}

View File

@ -1,188 +0,0 @@
/* Copyright (C) 2021 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! The loop abstraction for driving state changes.
* It binds to the state tracker in `state::Application`,
* and actually gets driven by a driver in the `driver` module.
*
* * * *
*
* If we performed updates in a tight loop,
* the state tracker would have been all we need.
*
* ``
* loop {
* event = current_event()
* outcome = update_state(event)
* io.apply(outcome)
* }
* ``
*
* This is enough to process all events,
* and keep the window always in sync with the current state.
*
* However, we're trying to be conservative,
* and not waste time performing updates that don't change state,
* so we have to react to events that end up influencing the state.
*
* One complication from that is that animation steps
* are not a response to events coming from the owner of the loop,
* but are needed by the loop itself.
*
* This is where the rest of bugs hide:
* too few scheduled wakeups mean missed updates and wrong visible state.
* Too many wakeups can slow down the process, or make animation jittery.
* The loop iteration is kept as a pure function to stay testable.
*/
pub mod driver;
// This module is tightly coupled to the shape of data passed around in this project.
// That's not a problem as long as there's only one loop.
// They can still be abstracted into Traits,
// and the loop parametrized over them.
use crate::main::Commands;
use crate::state;
use crate::state::Event;
use std::cmp;
use std::time::{ Duration, Instant };
/// This keeps the state of the tracker loop between iterations
#[derive(Clone)]
struct State {
state: state::Application,
scheduled_wakeup: Option<Instant>,
last_update: Instant,
}
impl State {
fn new(initial_state: state::Application, now: Instant) -> Self {
Self {
state: initial_state,
scheduled_wakeup: None,
last_update: now,
}
}
}
/// A single iteration of the loop, updating its persistent state.
/// - updates tracker state,
/// - determines outcome,
/// - determines next scheduled animation wakeup,
/// and because this is a pure function, it's easily testable.
/// It returns the new state, and the message to send onwards.
fn handle_event(
mut loop_state: State,
event: Event,
now: Instant,
) -> (State, Commands) {
// Calculate changes to send to the consumer,
// based on publicly visible state.
// The internal state may change more often than the publicly visible one,
// so the resulting changes may be no-ops.
let old_state = loop_state.state.clone();
let last_update = loop_state.last_update;
loop_state.state = loop_state.state.apply_event(event.clone(), now);
loop_state.last_update = now;
let new_outcome = loop_state.state.get_outcome(now);
let commands = old_state.get_outcome(last_update)
.get_commands_to_reach(&new_outcome);
// Timeout events are special: they affect the scheduled timeout.
loop_state.scheduled_wakeup = match event {
Event::TimeoutReached(when) => {
if when > now {
// Special handling for scheduled events coming in early.
// Wait at least 10 ms to avoid Zeno's paradox.
// This is probably not needed though,
// if the `now` contains the desired time of the event.
// But then what about time "reversing"?
Some(cmp::max(
when,
now + Duration::from_millis(10),
))
} else {
// There's only one timeout in flight, and it's this one.
// It's about to complete, and then the tracker can be cleared.
// I'm not sure if this is strictly necessary.
None
}
},
_ => loop_state.scheduled_wakeup.clone(),
};
// Reschedule timeout if the new state calls for it.
let scheduled = &loop_state.scheduled_wakeup;
let desired = loop_state.state.get_next_wake(now);
loop_state.scheduled_wakeup = match (scheduled, desired) {
(&Some(scheduled), Some(next)) => {
if scheduled > next {
// State wants a wake to happen before the one which is already scheduled.
// The previous state is removed in order to only ever keep one in flight.
// That hopefully avoids pileups,
// e.g. because the system is busy
// and the user keeps doing something that queues more events.
Some(next)
} else {
// Not changing the case when the wanted wake is *after* scheduled,
// because wakes are not expensive as long as they don't pile up,
// and I can't see a pileup potential when it doesn't retrigger itself.
// Skipping an expected event is much more dangerous.
Some(scheduled)
}
},
(None, Some(next)) => Some(next),
// No need to change the unneeded wake - see above.
// (Some(_), None) => ...
(other, _) => other.clone(),
};
(loop_state, commands)
}
#[cfg(test)]
mod test {
use super::*;
use crate::animation;
use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::PanelCommand;
use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility };
use crate::state::test::application_with_fake_output;
fn imdetails_new() -> InputMethodDetails {
InputMethodDetails {
purpose: ContentPurpose::Normal,
hint: ContentHint::NONE,
}
}
#[test]
fn schedule_hide() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
let l = State::new(state, now);
let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
assert_matches!(commands.panel_visibility, Some(PanelCommand::Show{..}));
assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT));
now += animation::HIDING_TIMEOUT;
let (l, commands) = handle_event(l, Event::TimeoutReached(now), now);
assert_eq!(commands.panel_visibility, Some(PanelCommand::Hide));
assert_eq!(l.scheduled_wakeup, None);
}
}

View File

@ -23,6 +23,22 @@ static const struct zwp_input_method_v2_listener input_method_listener = {
.unavailable = imservice_handle_unavailable,
};
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct vis_manager *vis_manager,
struct wl_seat *seat,
EekboardContextService *state) {
struct zwp_input_method_v2 *im = NULL;
if (immanager) {
im = zwp_input_method_manager_v2_get_input_method(immanager, seat);
}
struct zwp_virtual_keyboard_v1 *vk = NULL;
if (vkmanager) {
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmanager, seat);
}
return submission_new(im, vk, state, vis_manager);
}
/// Un-inlined
struct zwp_input_method_v2 *imservice_manager_get_input_method(struct zwp_input_method_manager_v2 *manager,
struct wl_seat *seat) {

View File

@ -8,11 +8,7 @@ use std::ffi::CString;
use std::fmt;
use std::num::Wrapping;
use std::string::String;
use std::time::Instant;
use crate::event_loop::driver;
use crate::state;
use crate::state::Event;
use ::logging;
use ::util::c::into_cstring;
@ -26,32 +22,25 @@ pub mod c {
use super::*;
use std::os::raw::{c_char, c_void};
use std::ptr;
pub use ::ui_manager::c::UIManager;
pub use ::submission::c::StateManager;
// The following defined in C
/// struct zwp_input_method_v2*
#[repr(transparent)]
#[derive(PartialEq, Clone, Copy)]
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" {
fn imservice_destroy_im(im: InputMethod);
fn imservice_destroy_im(im: *mut c::InputMethod);
#[allow(improper_ctypes)] // IMService will never be dereferenced in C
pub fn imservice_connect_listeners(im: InputMethod, imservice: *const IMService);
pub fn eek_input_method_commit_string(im: InputMethod, text: *const c_char);
pub fn eek_input_method_delete_surrounding_text(im: InputMethod, before: u32, after: u32);
pub fn eek_input_method_commit(im: InputMethod, serial: u32);
pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService);
pub fn eek_input_method_commit_string(im: *mut 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_commit(im: *mut InputMethod, serial: u32);
fn eekboard_context_service_set_hint_purpose(state: *const StateManager, hint: u32, purpose: u32);
}
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
@ -60,7 +49,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_input_method_activate(imservice: *mut IMService,
im: InputMethod)
im: *const InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
imservice.preedit_string = String::new();
@ -73,7 +62,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_input_method_deactivate(imservice: *mut IMService,
im: InputMethod)
im: *const InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
imservice.pending = IMProtocolState {
@ -85,7 +74,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_surrounding_text(imservice: *mut IMService,
im: InputMethod,
im: *const InputMethod,
text: *const c_char, cursor: u32, _anchor: u32)
{
let imservice = check_imservice(imservice, im).unwrap();
@ -101,7 +90,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_content_type(imservice: *mut IMService,
im: InputMethod,
im: *const InputMethod,
hint: u32, purpose: u32)
{
let imservice = check_imservice(imservice, im).unwrap();
@ -129,7 +118,7 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_text_change_cause(imservice: *mut IMService,
im: InputMethod,
im: *const InputMethod,
cause: u32)
{
let imservice = check_imservice(imservice, im).unwrap();
@ -149,9 +138,10 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn imservice_handle_done(imservice: *mut IMService,
im: InputMethod)
im: *const InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
let active_changed = imservice.current.active ^ imservice.pending.active;
imservice.current = imservice.pending.clone();
imservice.pending = IMProtocolState {
@ -160,14 +150,26 @@ pub mod c {
};
imservice.serial += Wrapping(1u32);
imservice.send_event();
if active_changed {
(imservice.active_callback)(imservice.current.active);
if imservice.current.active {
unsafe {
eekboard_context_service_set_hint_purpose(
imservice.state_manager,
imservice.current.content_hint.bits(),
imservice.current.content_purpose.clone() as u32,
);
}
}
}
}
// TODO: this is really untested
#[no_mangle]
pub extern "C"
fn imservice_handle_unavailable(imservice: *mut IMService,
im: InputMethod)
im: *mut InputMethod)
{
let imservice = check_imservice(imservice, im).unwrap();
unsafe { imservice_destroy_im(im); }
@ -176,7 +178,7 @@ pub mod c {
// the keyboard is already decommissioned
imservice.current.active = false;
imservice.send_event();
(imservice.active_callback)(imservice.current.active);
}
// FIXME: destroy and deallocate
@ -192,7 +194,7 @@ pub mod c {
/// Care must be take
/// not to exceed the lifetime of the pointer with the reference,
/// especially not to store it.
fn check_imservice(imservice: *mut IMService, im: InputMethod)
fn check_imservice(imservice: *mut IMService, im: *const InputMethod)
-> Result<&'static mut IMService, &'static str>
{
if imservice.is_null() {
@ -231,7 +233,7 @@ bitflags!{
/// use rs::imservice::ContentPurpose;
/// assert_eq!(ContentPurpose::Alpha as u32, 1);
/// ```
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone)]
pub enum ContentPurpose {
Normal = 0,
Alpha = 1,
@ -326,8 +328,10 @@ impl Default for IMProtocolState {
pub struct IMService {
/// Owned reference (still created and destroyed in C)
pub im: c::InputMethod,
sender: driver::Threaded,
pub im: *mut c::InputMethod,
/// Unowned reference. Be careful, it's shared with C at large
state_manager: *const c::StateManager,
active_callback: Box<dyn Fn(bool)>,
pending: IMProtocolState,
current: IMProtocolState, // turn current into an idiomatic representation?
@ -342,14 +346,16 @@ pub enum SubmitError {
impl IMService {
pub fn new(
im: c::InputMethod,
sender: driver::Threaded,
im: *mut c::InputMethod,
state_manager: *const c::StateManager,
active_callback: Box<dyn Fn(bool)>,
) -> Box<IMService> {
// IMService will be referenced to by C,
// so it needs to stay in the same place in memory via Box
let imservice = Box::new(IMService {
im,
sender,
active_callback,
state_manager,
pending: IMProtocolState::default(),
current: IMProtocolState::default(),
preedit_string: String::new(),
@ -409,21 +415,4 @@ impl IMService {
pub fn is_active(&self) -> bool {
self.current.active
}
fn send_event(&self) {
let state = &self.current;
let timestamp = Instant::now();
let message = if state.active {
state::InputMethod::Active(
state::InputMethodDetails {
hint: state.content_hint,
purpose: state.content_purpose,
}
)
} else {
state::InputMethod::InactiveSince(timestamp)
};
self.sender.send(Event::InputMethod(message))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
}

View File

@ -33,7 +33,6 @@ struct transformation squeek_layout_calculate_transformation(
struct squeek_layout *squeek_load_layout(const char *name, uint32_t type, uint32_t variant_type, const char *overlay_name);
enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *);
uint32_t squeek_layout_get_purpose(const struct squeek_layout *);
void squeek_layout_free(struct squeek_layout*);
void squeek_layout_release(struct squeek_layout *layout,

View File

@ -1,17 +1,17 @@
/*!
* Layout-related data.
*
*
* The `View` contains `Row`s and each `Row` contains `Button`s.
* They carry data relevant to their positioning only,
* except the Button, which also carries some data
* about its appearance and function.
*
*
* The layout is determined bottom-up, by measuring `Button` sizes,
* deriving `Row` sizes from them, and then centering them within the `View`.
*
*
* That makes the `View` position immutable,
* and therefore different than the other positions.
*
*
* Note that it might be a better idea
* to make `View` position depend on its contents,
* and let the renderer scale and center it within the widget.
@ -32,8 +32,6 @@ use ::manager;
use ::submission::{ Submission, SubmitData, Timestamp };
use ::util::find_max_double;
use ::imservice::ContentPurpose;
// Traits
use std::borrow::Borrow;
use ::logging::Warn;
@ -44,7 +42,6 @@ pub mod c {
use gtk_sys;
use std::os::raw::c_void;
use crate::submission::c::Submission as CSubmission;
use std::ops::{ Add, Sub };
@ -67,7 +64,7 @@ pub mod c {
pub x: f64,
pub y: f64,
}
impl Add for Point {
type Output = Self;
fn add(self, other: Self) -> Self {
@ -84,7 +81,7 @@ pub mod c {
}
}
}
impl Sub<&Point> for Point {
type Output = Point;
fn sub(self, other: &Point) -> Point {
@ -155,14 +152,14 @@ pub mod c {
}
}
}
// This is constructed only in C, no need for warnings
#[allow(dead_code)]
#[repr(transparent)]
pub struct LevelKeyboard(*const c_void);
// The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers
/// Positions the layout contents within the available space.
/// The origin of the transformation is the point inside the margins.
#[no_mangle]
@ -186,13 +183,6 @@ pub mod c {
layout.kind.clone() as u32
}
#[no_mangle]
pub extern "C"
fn squeek_layout_get_purpose(layout: *const Layout) -> u32 {
let layout = unsafe { &*layout };
layout.purpose.clone() as u32
}
#[no_mangle]
pub extern "C"
fn squeek_layout_free(layout: *mut Layout) {
@ -208,7 +198,7 @@ pub mod c {
pub extern "C"
fn squeek_layout_release(
layout: *mut Layout,
submission: CSubmission,
submission: *mut Submission,
widget_to_layout: Transformation,
time: u32,
manager: manager::c::Manager,
@ -216,8 +206,7 @@ pub mod c {
) {
let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let submission = submission.clone_ref();
let mut submission = submission.borrow_mut();
let submission = unsafe { &mut *submission };
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
@ -229,7 +218,7 @@ pub mod c {
let key: &Rc<RefCell<KeyState>> = key.borrow();
seat::handle_release_key(
layout,
&mut submission,
submission,
Some(&ui_backend),
time,
Some(manager),
@ -244,19 +233,18 @@ pub mod c {
pub extern "C"
fn squeek_layout_release_all_only(
layout: *mut Layout,
submission: CSubmission,
submission: *mut Submission,
time: u32,
) {
let layout = unsafe { &mut *layout };
let submission = submission.clone_ref();
let mut submission = submission.borrow_mut();
let submission = unsafe { &mut *submission };
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
seat::handle_release_key(
layout,
&mut submission,
submission,
None, // don't update UI
Timestamp(time),
None, // don't switch layouts
@ -269,26 +257,25 @@ pub mod c {
pub extern "C"
fn squeek_layout_depress(
layout: *mut Layout,
submission: CSubmission,
submission: *mut Submission,
x_widget: f64, y_widget: f64,
widget_to_layout: Transformation,
time: u32,
ui_keyboard: EekGtkKeyboard,
) {
let layout = unsafe { &mut *layout };
let submission = submission.clone_ref();
let mut submission = submission.borrow_mut();
let submission = unsafe { &mut *submission };
let point = widget_to_layout.forward(
Point { x: x_widget, y: y_widget }
);
let state = layout.find_button_by_position(point)
.map(|place| place.button.state.clone());
if let Some(state) = state {
seat::handle_press_key(
layout,
&mut submission,
submission,
Timestamp(time),
&state,
);
@ -307,7 +294,7 @@ pub mod c {
pub extern "C"
fn squeek_layout_drag(
layout: *mut Layout,
submission: CSubmission,
submission: *mut Submission,
x_widget: f64, y_widget: f64,
widget_to_layout: Transformation,
time: u32,
@ -316,8 +303,7 @@ pub mod c {
) {
let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let submission = submission.clone_ref();
let mut submission = submission.borrow_mut();
let submission = unsafe { &mut *submission };
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
@ -325,7 +311,7 @@ pub mod c {
let point = ui_backend.widget_to_layout.forward(
Point { x: x_widget, y: y_widget }
);
let pressed = layout.pressed_keys.clone();
let button_info = {
let place = layout.find_button_by_position(point);
@ -345,7 +331,7 @@ pub mod c {
} else {
seat::handle_release_key(
layout,
&mut submission,
submission,
Some(&ui_backend),
time,
Some(manager),
@ -356,7 +342,7 @@ pub mod c {
if !found {
seat::handle_press_key(
layout,
&mut submission,
submission,
time,
&state,
);
@ -370,7 +356,7 @@ pub mod c {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
seat::handle_release_key(
layout,
&mut submission,
submission,
Some(&ui_backend),
time,
Some(manager),
@ -384,11 +370,11 @@ pub mod c {
#[cfg(test)]
mod test {
use super::*;
fn near(a: f64, b: f64) -> bool {
(a - b).abs() < ((a + b) * 0.001f64).abs()
}
#[test]
fn transform_back() {
let transform = Transformation {
@ -427,7 +413,7 @@ pub enum Label {
/// The graphical representation of a button
#[derive(Clone, Debug)]
pub struct Button {
/// ID string, e.g. for CSS
/// ID string, e.g. for CSS
pub name: CString,
/// Label to display to the user
pub label: Label,
@ -587,11 +573,11 @@ impl View {
offset: &row.0 + c::Point { x: button.0, y: 0.0 },
})
}
pub fn get_size(&self) -> Size {
self.size.clone()
}
/// Returns positioned rows, with appropriate x offsets (centered)
pub fn get_rows(&self) -> &Vec<(c::Point, Row)> {
&self.rows
@ -641,7 +627,6 @@ pub enum LatchedState {
pub struct Layout {
pub margins: Margins,
pub kind: ArrangementKind,
pub purpose: ContentPurpose,
pub current_view: String,
// If current view is latched,
@ -691,7 +676,7 @@ impl fmt::Display for NoSuchView {
// The usage of &mut on Rc<RefCell<KeyState>> doesn't mean anything special.
// Cloning could also be used.
impl Layout {
pub fn new(data: LayoutData, kind: ArrangementKind, purpose: ContentPurpose) -> Layout {
pub fn new(data: LayoutData, kind: ArrangementKind) -> Layout {
Layout {
kind,
current_view: "base".to_owned(),
@ -700,7 +685,6 @@ impl Layout {
keymaps: data.keymaps,
pressed_keys: HashSet::new(),
margins: data.margins,
purpose,
}
}
@ -747,7 +731,7 @@ impl Layout {
),
}
}
pub fn calculate_transformation(
&self,
available: Size,
@ -786,7 +770,7 @@ impl Layout {
}
}
}
fn apply_view_transition(
&mut self,
action: &Action,
@ -828,7 +812,7 @@ impl Layout {
///
/// Although the state is not defined at the keys
/// (it's in the relationship between view and action),
/// keys go through the following stages when clicked repeatedly:
/// keys go through the following stages when clicked repeatedly:
/// unlocked+unlatched -> locked+latched -> locked+unlatched
/// -> unlocked+unlatched
fn process_action_for_view<'a>(
@ -922,7 +906,7 @@ mod procedures {
})
}).collect()
}
#[cfg(test)]
mod test {
use super::*;
@ -1129,7 +1113,7 @@ mod test {
state: state,
})
}
#[test]
fn latch_lock_unlock() {
let action = Action::LockView {
@ -1168,7 +1152,7 @@ mod test {
latches: true,
looks_locked_from: vec![],
};
let submit = Action::Erase;
let view = View::new(vec![(
@ -1210,8 +1194,7 @@ mod test {
"base".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
"locked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
},
purpose: ContentPurpose::Normal,
};
};
// Basic cycle
layout.apply_view_transition(&switch);
@ -1237,14 +1220,14 @@ mod test {
latches: true,
looks_locked_from: vec![],
};
let unswitch = Action::LockView {
lock: "locked".into(),
unlock: "unlocked".into(),
latches: false,
looks_locked_from: vec![],
};
let submit = Action::Erase;
let view = View::new(vec![(
@ -1287,8 +1270,7 @@ mod test {
"locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
"unlocked".into() => (c::Point { x: 0.0, y: 0.0 }, view),
},
purpose: ContentPurpose::Normal,
};
};
layout.apply_view_transition(&switch);
assert_eq!(&layout.current_view, "locked");
@ -1304,14 +1286,14 @@ mod test {
latches: true,
looks_locked_from: vec![],
};
let switch_again = Action::LockView {
lock: "ĄĘ".into(),
unlock: "locked".into(),
latches: true,
looks_locked_from: vec![],
};
let submit = Action::Erase;
let view = View::new(vec![(
@ -1354,8 +1336,7 @@ mod test {
"locked".into() => (c::Point { x: 0.0, y: 0.0 }, view.clone()),
"ĄĘ".into() => (c::Point { x: 0.0, y: 0.0 }, view),
},
purpose: ContentPurpose::Normal,
};
};
// Latch twice, then Ąto-unlatch across 2 levels
layout.apply_view_transition(&switch);
@ -1455,7 +1436,6 @@ mod test {
views: hashmap! {
String::new() => (c::Point { x: 0.0, y: 0.0 }, view),
},
purpose: ContentPurpose::Normal,
};
assert_eq!(
layout.calculate_inner_size(),

View File

@ -11,34 +11,30 @@ extern crate gtk_sys;
#[allow(unused_imports)]
#[macro_use] // only for tests
extern crate maplit;
extern crate regex;
extern crate serde;
extern crate xkbcommon;
#[cfg(test)]
#[macro_use]
mod assert_matches;
#[macro_use]
mod logging;
mod action;
mod animation;
pub mod data;
mod drawing;
mod event_loop;
pub mod float_ord;
pub mod imservice;
mod keyboard;
mod layout;
mod locale;
mod main;
mod locale_config;
mod manager;
mod outputs;
mod popover;
mod resources;
mod state;
mod style;
mod submission;
pub mod tests;
pub mod util;
mod ui_manager;
mod vkeyboard;
mod xdg;

View File

@ -88,6 +88,15 @@ impl Drop for XkbInfo {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Translation<'a>(pub &'a str);
impl<'a> Translation<'a> {
pub fn to_owned(&'a self) -> OwnedTranslation {
OwnedTranslation(self.0.to_owned())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OwnedTranslation(pub String);

535
src/locale_config.rs Normal file
View File

@ -0,0 +1,535 @@
/*! Locale detection and management.
* Based on https://github.com/rust-locale/locale_config
*
* Ready for deletion/replacement once Debian starts packaging this,
* although this version doesn't need lazy_static.
*
* Copyright (c) 20162019 Jan Hudec <bulb@ucw.cz>
Copyright (c) 2016 A.J. Gardner <aaron.j.gardner@gmail.com>
Copyright (c) 2019, Bastien Orivel <eijebong@bananium.fr>
Copyright (c) 2019, Igor Gnatenko <i.gnatenko.brain@gmail.com>
Copyright (c) 2019, Sophie Tauchert <999eagle@999eagle.moe>
*/
use regex::Regex;
use std::borrow::Cow;
use std::env;
/// Errors that may be returned by `locale_config`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
pub enum Error {
/// Provided definition was not well formed.
///
/// This is returned when provided configuration string does not match even the rather loose
/// definition for language range from [RFC4647] or the composition format used by `Locale`.
///
/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt
NotWellFormed,
/// Placeholder for adding more errors in future. **Do not match!**.
__NonExhaustive,
}
impl ::std::fmt::Display for Error {
fn fmt(&self, out: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
out.write_str(match self {
&Error::NotWellFormed => "Language tag is not well-formed.",
// this is exception: here we do want exhaustive match so we don't publish version with
// missing descriptions by mistake.
&Error::__NonExhaustive => panic!("Placeholder error must not be instantiated!"),
})
}
}
/// Convenience Result alias.
type Result<T> = ::std::result::Result<T, Error>;
/// Iterator over `LanguageRange`s for specific category in a `Locale`
///
/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags
/// are returned in order of preference, which means the category-specific ones first and then
/// the generic ones.
///
/// The iterator is guaranteed to return at least one value.
pub struct TagsFor<'a, 'c> {
src: &'a str,
tags: std::str::Split<'a, &'static str>,
category: Option<&'c str>,
}
impl<'a, 'c> Iterator for TagsFor<'a, 'c> {
type Item = LanguageRange<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(cat) = self.category {
while let Some(s) = self.tags.next() {
if s.starts_with(cat) && s[cat.len()..].starts_with("=") {
return Some(
LanguageRange { language: Cow::Borrowed(&s[cat.len()+1..]) });
}
}
self.category = None;
self.tags = self.src.split(",");
}
while let Some(s) = self.tags.next() {
if s.find('=').is_none() {
return Some(
LanguageRange{ language: Cow::Borrowed(s) });
}
}
return None;
}
}
/// Language and culture identifier.
///
/// This object holds a [RFC4647] extended language range.
///
/// The internal data may be owned or shared from object with lifetime `'a`. The lifetime can be
/// extended using the `into_static()` method, which internally clones the data as needed.
///
/// # Syntax
///
/// The range is composed of `-`-separated alphanumeric subtags, possibly replaced by `*`s. It
/// might be empty.
///
/// In agreement with [RFC4647], this object only requires that the tag matches:
///
/// ```ebnf
/// language_tag = (alpha{1,8} | "*")
/// ("-" (alphanum{1,8} | "*"))*
/// ```
///
/// The exact interpretation is up to the downstream localization provider, but it expected that
/// it will be matched against a normalized [RFC5646] language tag, which has the structure:
///
/// ```ebnf
/// language_tag = language
/// ("-" script)?
/// ("-" region)?
/// ("-" variant)*
/// ("-" extension)*
/// ("-" private)?
///
/// language = alpha{2,3} ("-" alpha{3}){0,3}
///
/// script = aplha{4}
///
/// region = alpha{2}
/// | digit{3}
///
/// variant = alphanum{5,8}
/// | digit alphanum{3}
///
/// extension = [0-9a-wyz] ("-" alphanum{2,8})+
///
/// private = "x" ("-" alphanum{1,8})+
/// ```
///
/// * `language` is an [ISO639] 2-letter or, where not defined, 3-letter code. A code for
/// macro-language might be followed by code of specific dialect.
/// * `script` is an [ISO15924] 4-letter code.
/// * `region` is either an [ISO3166] 2-letter code or, for areas other than countries, [UN M.49]
/// 3-digit numeric code.
/// * `variant` is a string indicating variant of the language.
/// * `extension` and `private` define additional options. The private part has same structure as
/// the Unicode [`-u-` extension][u_ext]. Available options are documented for the facets that
/// use them.
///
/// The values obtained by inspecting the system are normalized according to those rules.
///
/// The content will be case-normalized as recommended in [RFC5646] §2.1.1, namely:
///
/// * `language` is written in lowercase,
/// * `script` is written with first capital,
/// * `country` is written in uppercase and
/// * all other subtags are written in lowercase.
///
/// When detecting system configuration, additional options that may be generated under the
/// [`-u-` extension][u_ext] currently are:
///
/// * `cf` — Currency format (`account` for parenthesized negative values, `standard` for minus
/// sign).
/// * `fw` — First day of week (`mon` to `sun`).
/// * `hc` — Hour cycle (`h12` for 112, `h23` for 023).
/// * `ms` — Measurement system (`metric` or `ussystem`).
/// * `nu` — Numbering system—only decimal systems are currently used.
/// * `va` — Variant when locale is specified in Unix format and the tag after `@` does not
/// correspond to any variant defined in [Language subtag registry].
///
/// And under the `-x-` extension, following options are defined:
///
/// * `df` — Date format:
///
/// * `iso`: Short date should be in ISO format of `yyyy-MM-dd`.
///
/// For example `-df-iso`.
///
/// * `dm` — Decimal separator for monetary:
///
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-002d` means to
/// use comma.
///
/// * `ds` — Decimal separator for numbers:
///
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-002d` means to
/// use comma.
///
/// * `gm` — Group (thousand) separator for monetary:
///
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-dm-00a0` means to
/// use non-breaking space.
///
/// * `gs` — Group (thousand) separator for numbers:
///
/// Followed by one or more Unicode codepoints in hexadecimal. For example `-ds-00a0` means to
/// use non-breaking space.
///
/// * `ls` — List separator:
///
/// Followed by one or more Unicode codepoints in hexadecimal. For example, `-ds-003b` means to
/// use a semicolon.
///
/// [RFC5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
/// [RFC4647]: https://www.rfc-editor.org/rfc/rfc4647.txt
/// [ISO639]: https://en.wikipedia.org/wiki/ISO_639
/// [ISO15924]: https://en.wikipedia.org/wiki/ISO_15924
/// [ISO3166]: https://en.wikipedia.org/wiki/ISO_3166
/// [UN M.49]: https://en.wikipedia.org/wiki/UN_M.49
/// [u_ext]: http://www.unicode.org/reports/tr35/#u_Extension
/// [Language subtag registry]: https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
pub struct LanguageRange<'a> {
language: Cow<'a, str>
}
impl<'a> LanguageRange<'a> {
/// Return LanguageRange for the invariant locale.
///
/// Invariant language is identified simply by empty string.
pub fn invariant() -> LanguageRange<'static> {
LanguageRange { language: Cow::Borrowed("") }
}
/// Create language tag from Unix/Linux/GNU locale tag.
///
/// Unix locale tags have the form
///
/// > *language* [ `_` *region* ] [ `.` *encoding* ] [ `@` *variant* ]
///
/// The *language* and *region* have the same format as RFC5646. *Encoding* is not relevant
/// here, since Rust always uses Utf-8. That leaves *variant*, which is unfortunately rather
/// free-form. So this function will translate known variants to corresponding RFC5646 subtags
/// and represent anything else with Unicode POSIX variant (`-u-va-`) extension.
///
/// Note: This function is public here for benefit of applications that may come across this
/// kind of tags from other sources than system configuration.
pub fn from_unix(s: &str) -> Result<LanguageRange<'static>> {
let unix_tag_regex = Regex::new(r"(?ix) ^
(?P<language> [[:alpha:]]{2,3} )
(?: _ (?P<region> [[:alpha:]]{2} | [[:digit:]]{3} ))?
(?: \. (?P<encoding> [0-9a-zA-Z-]{1,20} ))?
(?: @ (?P<variant> [[:alnum:]]{1,20} ))?
$ ").unwrap();
let unix_invariant_regex = Regex::new(r"(?ix) ^
(?: c | posix )
(?: \. (?: [0-9a-zA-Z-]{1,20} ))?
$ ").unwrap();
if let Some(caps) = unix_tag_regex.captures(s) {
let src_variant = caps.name("variant").map(|m| m.as_str()).unwrap_or("").to_ascii_lowercase();
let mut res = caps.name("language").map(|m| m.as_str()).unwrap().to_ascii_lowercase();
let region = caps.name("region").map(|m| m.as_str()).unwrap_or("");
let mut script = "";
let mut variant = "";
let mut uvariant = "";
match src_variant.as_ref() {
// Variants seen in the wild in GNU LibC (via http://lh.2xlibre.net/) or in Debian
// GNU/Linux Stretch system. Treatment of things not found in RFC5646 subtag registry
// (http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
// or CLDR according to notes at https://wiki.openoffice.org/wiki/LocaleMapping.
// Dialects:
// aa_ER@saaho - NOTE: Can't be found under that name in RFC5646 subtag registry,
// but there is language Saho with code ssy, which is likely that thing.
"saaho" if res == "aa" => res = String::from("ssy"),
// Scripts:
// @arabic
"arabic" => script = "Arab",
// @cyrillic
"cyrl" => script = "Cyrl",
"cyrillic" => script = "Cyrl",
// @devanagari
"devanagari" => script = "Deva",
// @hebrew
"hebrew" => script = "Hebr",
// tt@iqtelif
// Neither RFC5646 subtag registry nor CLDR knows anything about this, but as best
// as I can tell it is Tatar name for Latin (default is Cyrillic).
"iqtelif" => script = "Latn",
// @Latn
"latn" => script = "Latn",
// @latin
"latin" => script = "Latn",
// en@shaw
"shaw" => script = "Shaw",
// Variants:
// sr@ijekavianlatin
"ijekavianlatin" => {
script = "Latn";
variant = "ijekavsk";
},
// sr@ije
"ije" => variant = "ijekavsk",
// sr@ijekavian
"ijekavian" => variant = "ijekavsk",
// ca@valencia
"valencia" => variant = "valencia",
// Currencies:
// @euro - NOTE: We follow suite of Java and Openoffice and ignore it, because it
// is default for all locales where it sometimes appears now, and because we use
// explicit currency in monetary formatting anyway.
"euro" => {},
// Collation:
// gez@abegede - NOTE: This is collation, but CLDR does not have any code for it,
// so we for the moment leave it fall through as -u-va- instead of -u-co-.
// Anything else:
// en@boldquot, en@quot, en@piglatin - just randomish stuff
// @cjknarrow - beware, it's gonna end up as -u-va-cjknarro due to lenght limit
s if s.len() <= 8 => uvariant = &*s,
s => uvariant = &s[0..8], // the subtags are limited to 8 chars, but some are longer
};
if script != "" {
res.push('-');
res.push_str(script);
}
if region != "" {
res.push('-');
res.push_str(&*region.to_ascii_uppercase());
}
if variant != "" {
res.push('-');
res.push_str(variant);
}
if uvariant != "" {
res.push_str("-u-va-");
res.push_str(uvariant);
}
return Ok(LanguageRange {
language: Cow::Owned(res)
});
} else if unix_invariant_regex.is_match(s) {
return Ok(LanguageRange::invariant())
} else {
return Err(Error::NotWellFormed);
}
}
}
impl<'a> AsRef<str> for LanguageRange<'a> {
fn as_ref(&self) -> &str {
self.language.as_ref()
}
}
/// Locale configuration.
///
/// Users may accept several languages in some order of preference and may want to use rules from
/// different culture for some particular aspect of the program behaviour, and operating systems
/// allow them to specify this (to various extent).
///
/// The `Locale` objects represent the user configuration. They contain:
///
/// - The primary `LanguageRange`.
/// - Optional category-specific overrides.
/// - Optional fallbacks in case data (usually translations) for the primary language are not
/// available.
///
/// The set of categories is open-ended. The `locale` crate uses five well-known categories
/// `messages`, `numeric`, `time`, `collate` and `monetary`, but some systems define additional
/// ones (GNU Linux has additionally `paper`, `name`, `address`, `telephone` and `measurement`) and
/// these are provided in the user default `Locale` and other libraries can use them.
///
/// `Locale` is represented by a `,`-separated sequence of tags in `LanguageRange` syntax, where
/// all except the first one may be preceded by category name and `=` sign.
///
/// The first tag indicates the default locale, the tags prefixed by category names indicate
/// _overrides_ for those categories and the remaining tags indicate fallbacks.
///
/// Note that a syntactically valid value of HTTP `Accept-Language` header is a valid `Locale`. Not
/// the other way around though due to the presence of category selectors.
// TODO: Interning
#[derive(Clone,Debug,Eq,Hash,PartialEq)]
pub struct Locale {
// TODO: Intern the string for performance reasons
// XXX: Store pre-split to LanguageTags?
inner: String,
}
impl Locale {
/// Construct invariant locale.
///
/// Invariant locale is represented simply with empty string.
pub fn invariant() -> Locale {
Locale::from(LanguageRange::invariant())
}
/// Append fallback language tag.
///
/// Adds fallback to the end of the list.
pub fn add(&mut self, tag: &LanguageRange) {
for i in self.inner.split(',') {
if i == tag.as_ref() {
return; // don't add duplicates
}
}
self.inner.push_str(",");
self.inner.push_str(tag.as_ref());
}
/// Append category override.
///
/// Appending new override for a category that already has one will not replace the existing
/// override. This might change in future.
pub fn add_category(&mut self, category: &str, tag: &LanguageRange) {
if self.inner.split(',').next().unwrap() == tag.as_ref() {
return; // don't add useless override equal to the primary tag
}
for i in self.inner.split(',') {
if i.starts_with(category) &&
i[category.len()..].starts_with("=") &&
&i[category.len() + 1..] == tag.as_ref() {
return; // don't add duplicates
}
}
self.inner.push_str(",");
self.inner.push_str(category);
self.inner.push_str("=");
self.inner.push_str(tag.as_ref());
}
/// Iterate over `LanguageRange`s in this `Locale` applicable to given category.
///
/// Returns `LanguageRange`s in the `Locale` that are applicable to provided category. The tags
/// are returned in order of preference, which means the category-specific ones first and then
/// the generic ones.
///
/// The iterator is guaranteed to return at least one value.
pub fn tags_for<'a, 'c>(&'a self, category: &'c str) -> TagsFor<'a, 'c> {
let mut tags = self.inner.split(",");
while let Some(s) = tags.clone().next() {
if s.starts_with(category) && s[category.len()..].starts_with("=") {
return TagsFor {
src: self.inner.as_ref(),
tags: tags,
category: Some(category),
};
}
tags.next();
}
return TagsFor {
src: self.inner.as_ref(),
tags: self.inner.split(","),
category: None,
};
}
}
/// Locale is specified by a string tag. This is the way to access it.
// FIXME: Do we want to provide the full string representation? We would have it as single string
// then.
impl AsRef<str> for Locale {
fn as_ref(&self) -> &str {
self.inner.as_ref()
}
}
impl<'a> From<LanguageRange<'a>> for Locale {
fn from(t: LanguageRange<'a>) -> Locale {
Locale {
inner: t.language.into_owned(),
}
}
}
fn tag(s: &str) -> Result<LanguageRange> {
LanguageRange::from_unix(s)
}
// TODO: Read /etc/locale.alias
fn tag_inv(s: &str) -> LanguageRange {
tag(s).unwrap_or(LanguageRange::invariant())
}
pub fn system_locale() -> Option<Locale> {
// LC_ALL overrides everything
if let Ok(all) = env::var("LC_ALL") {
if let Ok(t) = tag(all.as_ref()) {
return Some(Locale::from(t));
}
}
// LANG is default
let mut loc =
if let Ok(lang) = env::var("LANG") {
Locale::from(tag_inv(lang.as_ref()))
} else {
Locale::invariant()
};
// category overrides
for &(cat, var) in [
("ctype", "LC_CTYPE"),
("numeric", "LC_NUMERIC"),
("time", "LC_TIME"),
("collate", "LC_COLLATE"),
("monetary", "LC_MONETARY"),
("messages", "LC_MESSAGES"),
("paper", "LC_PAPER"),
("name", "LC_NAME"),
("address", "LC_ADDRESS"),
("telephone", "LC_TELEPHONE"),
("measurement", "LC_MEASUREMENT"),
].iter() {
if let Ok(val) = env::var(var) {
if let Ok(tag) = tag(val.as_ref())
{
loc.add_category(cat, &tag);
}
}
}
// LANGUAGE defines fallbacks
if let Ok(langs) = env::var("LANGUAGE") {
for i in langs.split(':') {
if i != "" {
if let Ok(tag) = tag(i) {
loc.add(&tag);
}
}
}
}
if loc.as_ref() != "" {
return Some(loc);
} else {
return None;
}
}
#[cfg(test)]
mod test {
use super::LanguageRange;
#[test]
fn unix_tags() {
assert_eq!("cs-CZ", LanguageRange::from_unix("cs_CZ.UTF-8").unwrap().as_ref());
assert_eq!("sr-RS-ijekavsk", LanguageRange::from_unix("sr_RS@ijekavian").unwrap().as_ref());
assert_eq!("sr-Latn-ijekavsk", LanguageRange::from_unix("sr.UTF-8@ijekavianlatin").unwrap().as_ref());
assert_eq!("en-Arab", LanguageRange::from_unix("en@arabic").unwrap().as_ref());
assert_eq!("en-Arab", LanguageRange::from_unix("en.UTF-8@arabic").unwrap().as_ref());
assert_eq!("de-DE", LanguageRange::from_unix("DE_de.UTF-8@euro").unwrap().as_ref());
assert_eq!("ssy-ER", LanguageRange::from_unix("aa_ER@saaho").unwrap().as_ref());
assert!(LanguageRange::from_unix("foo_BAR").is_err());
assert!(LanguageRange::from_unix("en@arabic.UTF-8").is_err());
assert_eq!("", LanguageRange::from_unix("C").unwrap().as_ref());
assert_eq!("", LanguageRange::from_unix("C.UTF-8").unwrap().as_ref());
assert_eq!("", LanguageRange::from_unix("C.ISO-8859-1").unwrap().as_ref());
assert_eq!("", LanguageRange::from_unix("POSIX").unwrap().as_ref());
}
}

View File

@ -1,34 +0,0 @@
#pragma once
/// This all wraps https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/struct.MainContext.html#method.channel
#include <inttypes.h>
#include "input-method-unstable-v2-client-protocol.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "eek/eek-types.h"
#include "dbus.h"
struct receiver;
/// Wrapped<event_loop::driver::Threaded>
struct squeek_state_manager;
struct submission;
struct rsobjects {
struct receiver *receiver;
struct squeek_state_manager *state_manager;
struct submission *submission;
struct squeek_wayland *wayland;
};
void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler);
struct rsobjects squeek_init(void);
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_keyboard_present(struct squeek_state_manager *state, uint32_t keyboard_present);

View File

@ -1,196 +0,0 @@
/* Copyright (C) 2020,2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Glue for the main loop. */
use crate::outputs::OutputId;
use crate::state;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
mod c {
use super::*;
use std::os::raw::c_void;
use std::ptr;
use std::rc::Rc;
use std::time::Instant;
use crate::event_loop::driver;
use crate::imservice::IMService;
use crate::imservice::c::InputMethod;
use crate::outputs::Outputs;
use crate::outputs::c::WlOutput;
use crate::state;
use crate::submission::Submission;
use crate::util::c::Wrapped;
use crate::vkeyboard::c::ZwpVirtualKeyboardV1;
/// ServerContextService*
#[repr(transparent)]
pub struct UIManager(*const c_void);
/// DbusHandler*
#[repr(transparent)]
pub struct DBusHandler(*const c_void);
/// Holds the Rust structures that are interesting from C.
#[repr(C)]
pub struct RsObjects {
/// The handle to which Commands should be sent
/// for processing in the main loop.
receiver: Wrapped<Receiver<Commands>>,
state_manager: Wrapped<driver::Threaded>,
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" {
#[allow(improper_ctypes)]
fn init_wayland(wayland: *mut Wayland);
fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, height: u32);
fn server_context_service_real_hide_keyboard(service: *const UIManager);
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,
// given that dbus handler is using glib.
fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8);
}
/// Creates what's possible in Rust to eliminate as many FFI calls as possible,
/// because types aren't getting checked across their boundaries,
/// and that leads to suffering.
#[no_mangle]
pub extern "C"
fn squeek_init() -> RsObjects {
// Set up channels
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
let now = Instant::now();
let state_manager = driver::Threaded::new(sender, state::Application::new(now));
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
} else {
Some(IMService::new(wayland.input_method, state_manager.clone()))
};
let submission = Submission::new(vk, imservice);
RsObjects {
submission: Wrapped::new(submission),
state_manager: Wrapped::new(state_manager),
receiver: Wrapped::new(receiver),
wayland: Box::into_raw(wayland),
}
}
/// Places the UI loop callback in the glib main loop.
#[no_mangle]
pub extern "C"
fn register_ui_loop_handler(
receiver: Wrapped<Receiver<Commands>>,
ui_manager: *const UIManager,
dbus_handler: *const DBusHandler,
) {
let receiver = unsafe { receiver.unwrap() };
let receiver = Rc::try_unwrap(receiver).expect("References still present");
let receiver = receiver.into_inner();
let ctx = MainContext::default();
let _acqu = ctx.acquire();
receiver.attach(
Some(&ctx),
move |msg| {
main_loop_handle_message(msg, ui_manager, dbus_handler);
Continue(true)
},
);
#[cfg(not(feature = "glib_v0_14"))]
ctx.release();
}
/// A single iteration of the UI loop.
/// Applies state outcomes to external portions of the program.
/// This is the outest layer of the imperative shell,
/// and doesn't lend itself to testing other than integration.
fn main_loop_handle_message(
msg: Commands,
ui_manager: *const UIManager,
dbus_handler: *const DBusHandler,
) {
match msg.panel_visibility {
Some(PanelCommand::Show { output, height }) => unsafe {
server_context_service_update_keyboard(ui_manager, output.0, height);
},
Some(PanelCommand::Hide) => unsafe {
server_context_service_real_hide_keyboard(ui_manager);
},
None => {},
};
if let Some(visible) = msg.dbus_visible_set {
if dbus_handler != std::ptr::null() {
unsafe { dbus_handler_set_visible(dbus_handler, visible as u8) };
}
}
if let Some(hints) = msg.layout_hint_set {
unsafe {
server_context_service_set_hint_purpose(
ui_manager,
hints.hint.bits(),
hints.purpose.clone() as u32,
)
};
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum PanelCommand {
Show {
output: OutputId,
height: u32,
},
Hide,
}
/// The commands consumed by the main loop,
/// to be sent out to external components.
#[derive(Clone)]
pub struct Commands {
pub panel_visibility: Option<PanelCommand>,
pub layout_hint_set: Option<state::InputMethodDetails>,
pub dbus_visible_set: Option<bool>,
}

View File

@ -11,8 +11,7 @@ struct squeek_output_handle {
struct squeek_outputs *squeek_outputs_new(void);
void squeek_outputs_free(struct squeek_outputs*);
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);
void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output);
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);
#endif

View File

@ -1,11 +1,6 @@
/* Copyright (C) 2019-2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Managing Wayland outputs */
use std::vec::Vec;
use crate::event_loop;
use ::logging;
// traits
@ -16,22 +11,15 @@ pub mod c {
use super::*;
use std::os::raw::{ c_char, c_void };
use std::ptr;
use ::util::c::{COpaquePtr, Wrapped};
use ::util::c::COpaquePtr;
// Defined in C
#[repr(transparent)]
#[derive(Clone, PartialEq, Copy, Debug, Eq, Hash)]
#[derive(Clone, PartialEq, Copy)]
pub struct WlOutput(*const c_void);
impl WlOutput {
fn null() -> Self {
Self(ptr::null())
}
}
#[repr(C)]
struct WlOutputListener<T: COpaquePtr> {
geometry: extern fn(
@ -75,7 +63,7 @@ pub mod c {
}
/// Map to `wl_output.transform` values
#[derive(Clone, Copy, Debug)]
#[derive(Clone)]
pub enum Transform {
Normal = 0,
Rotated90 = 1,
@ -115,13 +103,28 @@ pub mod c {
) -> i32;
}
/// Wrapping Outputs is required for calling its methods from C
type COutputs = Wrapped<Outputs>;
type COutputs = ::util::c::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
// Callbacks from the output listener follow
extern fn outputs_handle_geometry(
outputs: COutputs,
wl_output: WlOutput,
@ -140,8 +143,7 @@ pub mod c {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= collection
.find_output_mut(wl_output)
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.transform = Some(transform) },
@ -169,8 +171,7 @@ pub mod c {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= collection
.find_output_mut(wl_output)
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => {
@ -191,27 +192,14 @@ pub mod c {
) {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output = collection
.find_output_mut(wl_output);
let event = match output {
Some(output) => {
output.current = output.pending.clone();
Some(Event {
output: OutputId(wl_output),
change: ChangeType::Altered(output.current),
})
},
None => {
log_print!(
logging::Level::Warning,
"Got done on unknown output",
);
None
}
let output = find_output_mut(&mut collection, wl_output);
match output {
Some(output) => { output.current = output.pending.clone(); }
None => log_print!(
logging::Level::Warning,
"Got done on unknown output",
),
};
if let Some(event) = event {
collection.send_event(event);
}
}
extern fn outputs_handle_scale(
@ -222,8 +210,7 @@ pub mod c {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= collection
.find_output_mut(wl_output)
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.scale = factor; }
@ -234,7 +221,11 @@ pub mod c {
};
}
// End callbacks
#[no_mangle]
pub extern "C"
fn squeek_outputs_new() -> COutputs {
COutputs::new(Outputs { outputs: Vec::new() })
}
#[no_mangle]
pub extern "C"
@ -244,17 +235,14 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput, id: u32) {
fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput) {
let collection = raw_collection.clone_ref();
let mut collection = collection.borrow_mut();
collection.outputs.push((
Output {
output: output.clone(),
pending: OutputState::uninitialized(),
current: OutputState::uninitialized(),
},
id,
));
collection.outputs.push(Output {
output: output.clone(),
pending: OutputState::uninitialized(),
current: OutputState::uninitialized(),
});
unsafe { squeek_output_add_listener(
output,
@ -267,23 +255,41 @@ pub mod c {
raw_collection,
)};
}
/// This will try to unregister the output, if the id matches a registered one.
#[no_mangle]
pub extern "C"
fn squeek_outputs_try_unregister(raw_collection: COutputs, id: u32) -> WlOutput {
fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle {
let collection = raw_collection.clone_ref();
let mut collection = collection.borrow_mut();
collection.remove_output_by_global(id)
.map_err(|e| log_print!(
logging::Level::Debug,
"Tried to remove global {:x} but it is not registered as an output: {:?}",
id, e,
))
.unwrap_or(WlOutput::null())
let collection = collection.borrow();
OutputHandle {
wl_output: collection.outputs[0].output.clone(),
outputs: raw_collection.clone(),
}
}
// 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
@ -294,16 +300,16 @@ pub struct Size {
}
/// wl_output mode
#[derive(Clone, Copy, Debug)]
pub struct Mode {
#[derive(Clone)]
struct Mode {
width: i32,
height: i32,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone)]
pub struct OutputState {
pub current_mode: Option<Mode>,
pub transform: Option<c::Transform>,
current_mode: Option<Mode>,
transform: Option<c::Transform>,
pub scale: i32,
}
@ -349,83 +355,12 @@ impl OutputState {
}
}
/// 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 {
pub struct Output {
output: c::WlOutput,
pending: OutputState,
current: OutputState,
}
#[derive(Debug)]
struct NotFound;
/// Wayland global ID type
type GlobalId = u32;
/// The outputs manager
pub struct Outputs {
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,
outputs: Vec<Output>,
}

View File

@ -5,17 +5,25 @@ use gtk;
use std::ffi::CString;
use std::cmp::Ordering;
use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale::{ OwnedTranslation, compare_current_locale };
use ::locale;
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
use ::locale_config::system_locale;
use ::logging;
use ::manager;
use ::resources;
// Traits
use gio::prelude::ActionMapExt;
use gio::prelude::SettingsExt;
use gio::ActionMapExt;
use gio::SettingsExt;
#[cfg(feature = "gio_v0_5")]
use gio::SimpleActionExt;
use glib::translate::FromGlibPtrNone;
use glib::variant::ToVariant;
use gtk::prelude::*;
#[cfg(not(feature = "gtk_v0_5"))]
use gtk::BuilderExtManual;
use gtk::PopoverExt;
use gtk::WidgetExt;
use std::io::Write;
use ::logging::Warn;
mod c {
@ -103,15 +111,50 @@ mod variants {
}
}
fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder {
let mut xml: Vec<u8> = Vec::new();
writeln!(
xml,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<interface>
<menu id=\"app-menu\">
<section>"
).unwrap();
for (input_name, translation) in inputs {
writeln!(
xml,
"
<item>
<attribute name=\"label\" translatable=\"yes\">{}</attribute>
<attribute name=\"action\">layout</attribute>
<attribute name=\"target\">{}</attribute>
</item>",
translation.0,
input_name,
).unwrap();
}
writeln!(
xml,
"
</section>
<section>
<item>
<attribute name=\"label\" translatable=\"yes\">Keyboard Settings</attribute>
<attribute name=\"action\">settings</attribute>
</item>
</section>
</menu>
</interface>"
).unwrap();
gtk::Builder::new_from_string(
&String::from_utf8(xml).expect("Bad menu definition")
)
}
fn get_settings(schema_name: &str) -> Option<gio::Settings> {
let mut error_handler = logging::Print{};
#[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(
gio::SettingsSchemaSource::get_default()
.or_warn(
&mut error_handler,
logging::Problem::Surprise,
"No gsettings schemas installed.",
@ -130,11 +173,7 @@ fn get_settings(schema_name: &str) -> Option<gio::Settings> {
fn set_layout(kind: String, name: String) {
let settings = get_settings("org.gnome.desktop.input-sources");
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 current = (kind.clone(), name.clone());
let inputs = variants::get_tuples(inputs).into_iter()
.filter(|t| t != &current);
@ -211,8 +250,13 @@ fn get_current_layout(
/// Translates all provided layout names according to current locale,
/// for the purpose of display (i.e. errors will be caught and reported)
fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
// `XkbInfo` being temporary means that its return values must be
// copied, forcing the use of `OwnedTranslation`.
// This procedure is rather ugly...
// Xkb lookup *must not* be applied to non-system layouts,
// so both translators can't be merged into one lookup table,
// therefore must be done in two steps.
// `XkbInfo` being temporary also means
// that its return values must be copied,
// forcing the use of `OwnedTranslation`.
enum Status {
/// xkb names should get all translated here
Translated(OwnedTranslation),
@ -221,7 +265,7 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
}
// Attempt to take all xkb names from gnome-desktop's xkb info.
let xkb_translator = ::locale::XkbInfo::new();
let xkb_translator = locale::XkbInfo::new();
let translated_names = layouts.iter()
.map(|id| match id {
@ -233,15 +277,49 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
&format!("No display name for xkb layout {}", name),
).unwrap_or_else(|| Status::Remaining(name.clone()))
},
LayoutId::Local (_) => unreachable!(),
LayoutId::Local(name) => Status::Remaining(name.clone()),
});
translated_names
.map(|status| match status {
Status::Remaining(name) => OwnedTranslation(name),
Status::Translated(t) => t,
})
.collect()
// Non-xkb layouts and weird xkb layouts
// still need to be looked up in the internal database.
let builtin_translations = system_locale()
.map(|locale|
locale.tags_for("messages")
.next().unwrap() // guaranteed to exist
.as_ref()
.to_owned()
)
.or_print(logging::Problem::Surprise, "No locale detected")
.and_then(|lang| {
resources::get_layout_names(lang.as_str())
.or_print(
logging::Problem::Surprise,
&format!("No translations for locale {}", lang),
)
});
match builtin_translations {
Some(translations) => {
translated_names
.map(|status| match status {
Status::Remaining(name) => {
translations.get(name.as_str())
.unwrap_or(&Translation(name.as_str()))
.to_owned()
},
Status::Translated(t) => t,
})
.collect()
},
None => {
translated_names
.map(|status| match status {
Status::Remaining(name) => OwnedTranslation(name),
Status::Translated(t) => t,
})
.collect()
},
}
}
pub fn show(
@ -258,11 +336,7 @@ pub fn show(
let settings = get_settings("org.gnome.desktop.input-sources");
let inputs = 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();
variants::get_tuples(inputs)
})
.unwrap_or_else(|| Vec::new());
@ -276,12 +350,12 @@ pub fn show(
.chain(overlay_layouts)
.collect();
let translated_names = translate_layout_names(&system_layouts);
// sorted collection of language layouts
let translated_names = translate_layout_names(&all_layouts);
// sorted collection of human and machine names
let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
.into_iter()
.zip(system_layouts.clone().into_iter())
.zip(all_layouts.clone().into_iter())
.collect();
human_names.sort_unstable_by(|(tr_a, layout_a), (tr_b, layout_b)| {
@ -293,30 +367,34 @@ 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");
builder.get_object("app-menu").unwrap()
}
};
// GVariant doesn't natively support `enum`s,
// so the `choices` vector will serve as a lookup table.
let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))>
= human_names.into_iter()
.enumerate()
.map(|(i, human_entry)| {(
format!("{}_{}", i, human_entry.1.get_name()),
human_entry,
)}).collect();
for (tr, l) in human_names.iter().rev() {
let detailed_action = format!("layout::{}", l.get_name());
let item = gio::MenuItem::new(Some(&tr.0), Some(detailed_action.as_str()));
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 builder = make_menu_builder(
choices_with_translations.iter()
.map(|(id, (translation, _))| (id.as_str(), (*translation).clone()))
.collect()
);
let choices: Vec<(String, LayoutId)>
= choices_with_translations.into_iter()
.map(|(id, (_tr, layout))| (id, layout))
.collect();
// Much more debuggable to populate the model & menu
// from a string representation
// than add items imperatively
let model: gio::MenuModel = builder.get_object("app-menu").unwrap();
let menu = gtk::Popover::new_from_model(Some(&window), &model);
menu.set_pointing_to(&gtk::Rectangle {
x: position.x.ceil() as i32,
y: position.y.ceil() as i32,
@ -325,36 +403,32 @@ pub fn show(
});
menu.set_constrain_to(gtk::PopoverConstraint::None);
let action_group = gio::SimpleActionGroup::new();
if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
let current_layout_name = all_layouts.iter()
let current_name_variant = choices.iter()
.find(
|l| l.get_name() == current_layout.get_name()
|(_id, layout)| layout == &current_layout
).unwrap()
.get_name();
log_print!(logging::Level::Debug, "Current Layout {}", current_layout_name);
.0.to_variant();
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(current_layout_name.to_variant().type_()),
&current_layout_name.to_variant()
Some(current_name_variant.type_()),
&current_name_variant,
);
let menu_inner = menu.clone();
layout_action.connect_change_state(move |_action, state| {
match state {
Some(v) => {
log_print!(logging::Level::Debug, "Selected layout {}", v);
v.get::<String>()
.or_print(
logging::Problem::Bug,
&format!("Variant is not string: {:?}", v)
)
.map(|state| {
let layout = all_layouts.iter()
let (_id, layout) = choices.iter()
.find(
|choices| state == choices.get_name()
|choices| state == choices.0
).unwrap();
set_visible_layout(
manager,
@ -369,21 +443,20 @@ pub fn show(
};
menu_inner.popdown();
});
let settings_action = gio::SimpleAction::new("settings", None);
settings_action.connect_activate(move |_, _| {
let s = CString::new("region").unwrap();
unsafe { c::popover_open_settings_panel(s.as_ptr()) };
});
let action_group = gio::SimpleActionGroup::new();
action_group.add_action(&layout_action);
action_group.add_action(&settings_action);
menu.insert_action_group("popup", Some(&action_group));
};
let settings_action = gio::SimpleAction::new("settings", None);
settings_action.connect_activate(move |_, _| {
let s = CString::new("region").unwrap();
unsafe { c::popover_open_settings_panel(s.as_ptr()) };
});
action_group.add_action(&settings_action);
menu.insert_action_group("popup", Some(&action_group));
menu.bind_model(Some(&model), Some("popup"));
glib::idle_add_local(move || {
menu.popup();
Continue(false)
});
menu.popup();
}

View File

@ -2,6 +2,11 @@
* This could be done using GResource, but that would need additional work.
*/
use std::collections::HashMap;
use ::locale::Translation;
use std::iter::FromIterator;
// TODO: keep a list of what is a language layout,
// and what a convenience layout. "_wide" is not a layout,
// neither is "number"
@ -13,9 +18,6 @@ static KEYBOARDS: &[(&'static str, &'static str)] = &[
("us_wide", include_str!("../data/keyboards/us_wide.yaml")),
// Language layouts: keep alphabetical.
("am", include_str!("../data/keyboards/am.yaml")),
("am+phonetic", include_str!("../data/keyboards/am+phonetic.yaml")),
("ara", include_str!("../data/keyboards/ara.yaml")),
("ara_wide", include_str!("../data/keyboards/ara_wide.yaml")),
@ -28,9 +30,6 @@ static KEYBOARDS: &[(&'static str, &'static str)] = &[
("br", include_str!("../data/keyboards/br.yaml")),
("ch+fr", include_str!("../data/keyboards/ch+fr.yaml")),
("ch+de", include_str!("../data/keyboards/ch+de.yaml")),
("ch", include_str!("../data/keyboards/ch.yaml")),
("ch_wide", include_str!("../data/keyboards/ch_wide.yaml")),
("de", include_str!("../data/keyboards/de.yaml")),
("de_wide", include_str!("../data/keyboards/de_wide.yaml")),
@ -71,9 +70,6 @@ static KEYBOARDS: &[(&'static str, &'static str)] = &[
("pl", include_str!("../data/keyboards/pl.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")),
("se", include_str!("../data/keyboards/se.yaml")),
@ -89,19 +85,11 @@ static KEYBOARDS: &[(&'static str, &'static str)] = &[
("us+dvorak", include_str!("../data/keyboards/us+dvorak.yaml")),
("us+dvorak_wide", include_str!("../data/keyboards/us+dvorak_wide.yaml")),
// Email
("email/us", include_str!("../data/keyboards/email/us.yaml")),
// URL
("url/us", include_str!("../data/keyboards/url/us.yaml")),
// Others
("number/us", include_str!("../data/keyboards/number/us.yaml")),
("pin/us", include_str!("../data/keyboards/pin/us.yaml")),
// Terminal
("terminal/fr", include_str!("../data/keyboards/terminal/fr.yaml")),
("terminal/fr_wide", include_str!("../data/keyboards/terminal/fr_wide.yaml")),
("terminal/us", include_str!("../data/keyboards/terminal/us.yaml")),
("terminal/us_wide", include_str!("../data/keyboards/terminal/us_wide.yaml")),
@ -123,6 +111,46 @@ pub fn get_overlays() -> Vec<&'static str> {
OVERLAY_NAMES.to_vec()
}
/// Translations of the layout identifier strings
static LAYOUT_NAMES: &[(&'static str, &'static str)] = &[
("de-DE", include_str!("../data/langs/de-DE.txt")),
("en-US", include_str!("../data/langs/en-US.txt")),
("es-ES", include_str!("../data/langs/es-ES.txt")),
("fur-IT", include_str!("../data/langs/fur-IT.txt")),
("he-IL", include_str!("../data/langs/he-IL.txt")),
("ja-JP", include_str!("../data/langs/ja-JP.txt")),
("pl-PL", include_str!("../data/langs/pl-PL.txt")),
("ru-RU", include_str!("../data/langs/ru-RU.txt")),
];
pub fn get_layout_names(lang: &str)
-> Option<HashMap<&'static str, Translation<'static>>>
{
let translations = LAYOUT_NAMES.iter()
.find(|(name, _data)| *name == lang)
.map(|(_name, data)| *data);
translations.map(make_mapping)
}
fn parse_line(line: &str) -> Option<(&str, Translation)> {
let comment = line.trim().starts_with("#");
if comment {
None
} else {
let mut iter = line.splitn(2, " ");
let name = iter.next().unwrap();
// will skip empty and unfinished lines
iter.next().map(|tr| (name, Translation(tr.trim())))
}
}
fn make_mapping(data: &str) -> HashMap<&str, Translation> {
HashMap::from_iter(
data.split("\n")
.filter_map(parse_line)
)
}
#[cfg(test)]
mod test {
use super::*;
@ -133,4 +161,32 @@ mod test {
assert!(get_keyboard(&format!("{}/us", name)).is_some());
}
}
#[test]
fn mapping_line() {
assert_eq!(
Some(("name", Translation("translation"))),
parse_line("name translation")
);
}
#[test]
fn mapping_bad() {
assert_eq!(None, parse_line("bad"));
}
#[test]
fn mapping_empty() {
assert_eq!(None, parse_line(""));
}
#[test]
fn mapping_comment() {
assert_eq!(None, parse_line("# comment"));
}
#[test]
fn mapping_comment_offset() {
assert_eq!(None, parse_line(" # comment"));
}
}

View File

@ -27,10 +27,10 @@
#include "submission.h"
#include "wayland.h"
#include "server-context-service.h"
#include "wayland-client-protocol.h"
enum {
PROP_0,
PROP_VISIBLE,
PROP_ENABLED,
PROP_LAST
};
@ -42,12 +42,13 @@ struct _ServerContextService {
/// Needed for instantiating the widget
struct submission *submission; // unowned
struct squeek_layout_state *layout;
struct squeek_state_manager *state_manager; // shared reference
struct ui_manager *manager; // unowned
struct vis_manager *vis_manager; // owned
gboolean visible;
PhoshLayerSurface *window;
GtkWidget *widget; // nullable
struct wl_output *current_output;
guint hiding;
guint last_requested_height;
};
@ -67,16 +68,112 @@ on_destroy (ServerContextService *self, GtkWidget *widget)
}
static void
make_window (ServerContextService *self, struct wl_output *output, uint32_t height)
on_notify_map (ServerContextService *self, GtkWidget *widget)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
g_object_set (self, "visible", TRUE, NULL);
}
static void
on_notify_unmap (ServerContextService *self, GtkWidget *widget)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
g_object_set (self, "visible", FALSE, NULL);
}
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
on_surface_configure(ServerContextService *self, PhoshLayerSurface *surface)
{
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) {
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 (
PHOSH_TYPE_LAYER_SURFACE,
"layer-shell", squeek_wayland->layer_shell,
"wl-output", output,
"wl-output", output.output,
"height", height,
"anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
| ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
@ -90,7 +187,9 @@ make_window (ServerContextService *self, struct wl_output *output, uint32_t heig
g_object_connect (self->window,
"swapped-signal::destroy", G_CALLBACK(on_destroy), self,
//"swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
"swapped-signal::map", G_CALLBACK(on_notify_map), self,
"swapped-signal::unmap", G_CALLBACK(on_notify_unmap), self,
"swapped-signal::configured", G_CALLBACK(on_surface_configure), self,
NULL);
// The properties below are just to make hacking easier.
@ -100,7 +199,8 @@ make_window (ServerContextService *self, struct wl_output *output, uint32_t heig
// or for hacks with regular windows.
gtk_widget_set_can_focus (GTK_WIDGET(self->window), FALSE);
g_object_set (G_OBJECT(self->window), "accept_focus", FALSE, NULL);
gtk_window_set_title (GTK_WINDOW(self->window), "Squeekboard");
gtk_window_set_title (GTK_WINDOW(self->window),
_("Squeekboard"));
gtk_window_set_icon_name (GTK_WINDOW(self->window), "squeekboard");
gtk_window_set_keep_above (GTK_WINDOW(self->window), TRUE);
}
@ -126,57 +226,109 @@ make_widget (ServerContextService *self)
gtk_widget_show_all(self->widget);
}
// Called from rust
/// Updates the type of hiddenness
void
server_context_service_real_hide_keyboard (ServerContextService *self)
static void
server_context_service_real_show_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
void
server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t 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 != height) {
//TODO: make sure that redrawing happens in the correct place (it doesn't now).
phosh_layer_surface_set_size(self->window, 0, height);
phosh_layer_surface_set_exclusive_zone(self->window, height);
phosh_layer_surface_wl_surface_commit(self->window);
self->current_output = output;
return;
}
}
self->current_output = output;
if (!self->window) {
make_window (self, output, height);
make_window (self);
}
if (!self->widget) {
make_widget (self);
}
self->visible = TRUE;
gtk_widget_show (GTK_WIDGET(self->window));
}
static gboolean
show_keyboard_source_func(ServerContextService *context)
{
server_context_service_real_show_keyboard(context);
return G_SOURCE_REMOVE;
}
static void
server_context_service_real_hide_keyboard (ServerContextService *self)
{
gtk_widget_hide (GTK_WIDGET(self->window));
self->visible = FALSE;
}
static gboolean
hide_keyboard_source_func(ServerContextService *context)
{
server_context_service_real_hide_keyboard(context);
return G_SOURCE_REMOVE;
}
static gboolean
on_hide (ServerContextService *self)
{
server_context_service_real_hide_keyboard(self);
self->hiding = 0;
return G_SOURCE_REMOVE;
}
static void
server_context_service_show_keyboard (ServerContextService *self)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self));
if (self->hiding) {
g_source_remove (self->hiding);
self->hiding = 0;
}
if (!self->visible) {
g_idle_add((GSourceFunc)show_keyboard_source_func, self);
}
}
void
server_context_service_force_show_keyboard (ServerContextService *self)
{
if (!submission_hint_available(self->submission)) {
eekboard_context_service_set_hint_purpose(
self->state,
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL
);
}
server_context_service_show_keyboard(self);
}
void
server_context_service_hide_keyboard (ServerContextService *self)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self));
if (self->visible) {
g_idle_add((GSourceFunc)hide_keyboard_source_func, self);
}
}
/// Meant for use by the input-method handler:
/// the visible keyboard is no longer needed.
/// The implementation will delay it slightly,
/// because the release may be due to switching from one text field to another.
/// In this case, the user doesn't really need the keyboard surface
/// to disappear completely.
void
server_context_service_release_visibility (ServerContextService *self)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self));
if (!self->hiding && self->visible) {
self->hiding = g_timeout_add (200, (GSourceFunc) on_hide, self);
}
}
static void
server_context_service_set_physical_keyboard_present (ServerContextService *self, gboolean physical_keyboard_present)
{
g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self));
squeek_visman_set_keyboard_present(self->vis_manager, physical_keyboard_present);
}
static void
server_context_service_set_property (GObject *object,
@ -187,8 +339,11 @@ server_context_service_set_property (GObject *object,
ServerContextService *self = SERVER_CONTEXT_SERVICE(object);
switch (prop_id) {
case PROP_VISIBLE:
self->visible = g_value_get_boolean (value);
break;
case PROP_ENABLED:
squeek_state_send_keyboard_present(self->state_manager, !g_value_get_boolean (value));
server_context_service_set_physical_keyboard_present (self, !g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -202,7 +357,11 @@ server_context_service_get_property (GObject *object,
GValue *value,
GParamSpec *pspec)
{
ServerContextService *self = SERVER_CONTEXT_SERVICE(object);
switch (prop_id) {
case PROP_VISIBLE:
g_value_set_boolean (value, self->visible);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -230,6 +389,18 @@ server_context_service_class_init (ServerContextServiceClass *klass)
gobject_class->get_property = server_context_service_get_property;
gobject_class->dispose = server_context_service_dispose;
/**
* Flag to indicate if keyboard is visible or not.
*/
pspec = g_param_spec_boolean ("visible",
"Visible",
"Visible",
FALSE,
G_PARAM_READWRITE);
g_object_class_install_property (gobject_class,
PROP_VISIBLE,
pspec);
/**
* ServerContextServie:keyboard:
*
@ -271,19 +442,24 @@ init (ServerContextService *self) {
}
ServerContextService *
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager)
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct vis_manager *visman)
{
ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL);
ui->submission = submission;
ui->state = self;
ui->layout = layout;
ui->state_manager = state_manager;
ui->manager = uiman;
ui->vis_manager = visman;
init(ui);
return ui;
}
// Used from Rust
void server_context_service_set_hint_purpose(ServerContextService *self, uint32_t hint,
uint32_t purpose) {
eekboard_context_service_set_hint_purpose(self->state, hint, purpose);
void
server_context_service_update_visible (ServerContextService *self, gboolean visible) {
if (visible) {
server_context_service_show_keyboard(self);
} else {
server_context_service_hide_keyboard(self);
}
}

View File

@ -20,6 +20,7 @@
#include "src/layout.h"
#include "src/submission.h"
#include "ui_manager.h"
G_BEGIN_DECLS
@ -28,8 +29,10 @@ G_BEGIN_DECLS
/** Manages the lifecycle of the window displaying layouts. */
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 squeek_state_manager *state_manager);
ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct vis_manager *visman);
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
#endif /* SERVER_CONTEXT_SERVICE_H */

View File

@ -1,10 +1,10 @@
/*
/*
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
* Copyright (C) 2010-2011 Red Hat, Inc.
* Copyright (C) 2018-2019 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
* Author: Guido Günther <agx@sigxcpu.org>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@ -29,36 +29,30 @@
#include "eekboard/eekboard-context-service.h"
#include "dbus.h"
#include "layout.h"
#include "main.h"
#include "outputs.h"
#include "submission.h"
#include "server-context-service.h"
#include "ui_manager.h"
#include "wayland.h"
#include <gdk/gdkwayland.h>
typedef enum _SqueekboardDebugFlags {
SQUEEKBOARD_DEBUG_FLAG_NONE = 0,
SQUEEKBOARD_DEBUG_FLAG_FORCE_SHOW = 1 << 0,
SQUEEKBOARD_DEBUG_FLAG_GTK_INSPECTOR = 1 << 1,
} SqueekboardDebugFlags;
/// Some state, some IO components, all mixed together.
/// Better move what's possible to state::Application,
/// or secondary data structures of the same general shape.
/// Global application state
struct squeekboard {
struct squeek_wayland wayland; // Just hooks.
DBusHandler *dbus_handler; // Controls visibility of the OSK.
EekboardContextService *settings_context; // Gsettings hooks.
ServerContextService *ui_context; // mess, includes the entire UI
/// Currently wanted layout. TODO: merge into state::Application
struct squeek_layout_state layout_choice;
struct submission *submission; // Wayland text input handling.
struct squeek_layout_state layout_choice; // Currently wanted layout.
struct ui_manager *ui_manager; // UI shape tracker/chooser. TODO: merge with layuot choice
};
GMainLoop *loop;
static gboolean opt_system = FALSE;
static gchar *opt_address = NULL;
static void
quit (void)
@ -83,16 +77,12 @@ on_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
SqueekboardDebugFlags *flags = user_data;
// TODO: could conceivable continue working
// if intrnal changes stop sending dbus changes
(void)connection;
(void)name;
(void)user_data;
g_warning("DBus unavailable, unclear how to continue. Is Squeekboard already running?");
if ((*flags & SQUEEKBOARD_DEBUG_FLAG_FORCE_SHOW) == 0) {
exit (1);
}
g_error("DBus unavailable, unclear how to continue.");
}
// Wayland
@ -109,38 +99,34 @@ registry_handle_global (void *data,
// Even when lower version would be served, it would not be supported,
// causing a hard exit
(void)version;
struct squeek_wayland *wayland = data;
struct squeekboard *instance = data;
if (!strcmp (interface, zwlr_layer_shell_v1_interface.name)) {
wayland->layer_shell = wl_registry_bind (registry, name,
instance->wayland.layer_shell = wl_registry_bind (registry, name,
&zwlr_layer_shell_v1_interface, 1);
} else if (!strcmp (interface, zwp_virtual_keyboard_manager_v1_interface.name)) {
wayland->virtual_keyboard_manager = wl_registry_bind(registry, name,
instance->wayland.virtual_keyboard_manager = wl_registry_bind(registry, name,
&zwp_virtual_keyboard_manager_v1_interface, 1);
} else if (!strcmp (interface, zwp_input_method_manager_v2_interface.name)) {
wayland->input_method_manager = wl_registry_bind(registry, name,
instance->wayland.input_method_manager = wl_registry_bind(registry, name,
&zwp_input_method_manager_v2_interface, 1);
} else if (!strcmp (interface, "wl_output")) {
struct wl_output *output = wl_registry_bind (registry, name,
&wl_output_interface, 2);
squeek_outputs_register(wayland->outputs, output, name);
squeek_outputs_register(instance->wayland.outputs, output);
} else if (!strcmp(interface, "wl_seat")) {
wayland->seat = wl_registry_bind(registry, name,
instance->wayland.seat = wl_registry_bind(registry, name,
&wl_seat_interface, 1);
}
}
static void
registry_handle_global_remove (void *data,
struct wl_registry *registry,
uint32_t name)
{
(void)registry;
struct squeek_wayland *wayland = data;
struct wl_output *output = squeek_outputs_try_unregister(wayland->outputs, name);
if (output) {
wl_output_destroy(output);
}
// TODO
}
static const struct wl_registry_listener registry_listener = {
@ -148,54 +134,6 @@ static const struct wl_registry_listener registry_listener = {
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"
GDBusProxy *_proxy = NULL;
@ -308,94 +246,48 @@ session_register(void) {
g_signal_connect (_client_proxy, "g-signal", G_CALLBACK (client_proxy_signal), NULL);
}
static void
phosh_theme_init (void)
{
GtkSettings *gtk_settings;
const char *desktop;
gboolean phosh_session;
g_auto (GStrv) components = NULL;
desktop = g_getenv ("XDG_CURRENT_DESKTOP");
if (!desktop) {
return;
}
components = g_strsplit (desktop, ":", -1);
phosh_session = g_strv_contains ((const char * const *)components, "Phosh");
if (!phosh_session) {
return;
}
gtk_settings = gtk_settings_get_default ();
g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL);
}
static GDebugKey debug_keys[] =
{
{ .key = "force-show",
.value = SQUEEKBOARD_DEBUG_FLAG_FORCE_SHOW,
},
{ .key = "gtk-inspector",
.value = SQUEEKBOARD_DEBUG_FLAG_GTK_INSPECTOR,
},
};
static SqueekboardDebugFlags
parse_debug_env (void)
{
const char *debugenv;
SqueekboardDebugFlags flags = SQUEEKBOARD_DEBUG_FLAG_NONE;
debugenv = g_getenv("SQUEEKBOARD_DEBUG");
if (!debugenv) {
return flags;
}
return g_parse_debug_string(debugenv, debug_keys, G_N_ELEMENTS (debug_keys));
}
int
main (int argc, char **argv)
{
SqueekboardDebugFlags debug_flags = SQUEEKBOARD_DEBUG_FLAG_NONE;
g_autoptr (GError) err = NULL;
g_autoptr(GOptionContext) opt_context = NULL;
const GOptionEntry options [] = {
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
opt_context = g_option_context_new ("- A on screen keyboard");
g_option_context_add_main_entries (opt_context, options, NULL);
g_option_context_add_group (opt_context, gtk_get_option_group (TRUE));
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
g_warning ("%s", err->message);
return 1;
}
textdomain (GETTEXT_PACKAGE);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
if (!gtk_init_check (&argc, &argv)) {
g_printerr ("Can't init GTK\n");
exit (1);
}
debug_flags = parse_debug_env ();
eek_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");
}
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);
// Also initializes wayland
struct rsobjects rsobjects = squeek_init();
if (!instance.wayland.seat) {
g_error("No seat Wayland global available.");
}
if (!instance.wayland.virtual_keyboard_manager) {
g_error("No virtual keyboard manager Wayland global available.");
}
if (!instance.wayland.layer_shell) {
g_error("No layer shell global available.");
}
if (!instance.wayland.input_method_manager) {
g_warning("Wayland input method interface not available");
}
instance.ui_manager = squeek_uiman_new();
instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
@ -404,17 +296,51 @@ main (int argc, char **argv)
// TODO: make dbus errors non-always-fatal
// dbus is not strictly necessary for the useful operation
// if text-input is used, as it can bring the keyboard in and out
GBusType bus_type;
if (opt_system) {
bus_type = G_BUS_TYPE_SYSTEM;
} else if (opt_address) {
bus_type = G_BUS_TYPE_NONE;
} else {
bus_type = G_BUS_TYPE_SESSION;
}
GDBusConnection *connection = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err);
if (connection == NULL) {
g_printerr ("Can't connect to the bus: %s. "
"Visibility switching unavailable.", err->message);
GError *error = NULL;
switch (bus_type) {
case G_BUS_TYPE_SYSTEM:
case G_BUS_TYPE_SESSION:
error = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (connection == NULL) {
g_printerr ("Can't connect to the bus: %s. "
"Visibility switching unavailable.", error->message);
g_error_free (error);
}
break;
case G_BUS_TYPE_NONE:
error = NULL;
connection = g_dbus_connection_new_for_address_sync (opt_address,
0,
NULL,
NULL,
&error);
if (connection == NULL) {
g_printerr ("Can't connect to the bus at %s: %s\n",
opt_address,
error->message);
g_error_free (error);
exit (1);
}
break;
default:
g_assert_not_reached ();
break;
}
guint owner_id = 0;
DBusHandler *service = NULL;
if (connection) {
service = dbus_handler_new(connection, DBUS_SERVICE_PATH, rsobjects.state_manager);
service = dbus_handler_new(connection, DBUS_SERVICE_PATH);
if (service == NULL) {
g_printerr ("Can't create dbus server\n");
@ -427,7 +353,7 @@ main (int argc, char **argv)
G_BUS_NAME_OWNER_FLAGS_NONE,
on_name_acquired,
on_name_lost,
&debug_flags,
NULL,
NULL);
if (owner_id == 0) {
g_printerr ("Can't own the name\n");
@ -435,30 +361,35 @@ main (int argc, char **argv)
}
}
eekboard_context_service_set_submission(instance.settings_context, rsobjects.submission);
struct vis_manager *vis_manager = squeek_visman_new();
instance.submission = get_submission(instance.wayland.input_method_manager,
instance.wayland.virtual_keyboard_manager,
vis_manager,
instance.wayland.seat,
instance.settings_context);
eekboard_context_service_set_submission(instance.settings_context, instance.submission);
ServerContextService *ui_context = server_context_service_new(
instance.settings_context,
rsobjects.submission,
instance.submission,
&instance.layout_choice,
rsobjects.state_manager);
instance.ui_manager,
vis_manager);
if (!ui_context) {
g_error("Could not initialize GUI");
exit(1);
}
instance.ui_context = ui_context;
register_ui_loop_handler(rsobjects.receiver, instance.ui_context, instance.dbus_handler);
squeek_visman_set_ui(vis_manager, instance.ui_context);
if (instance.dbus_handler) {
dbus_handler_set_ui_context(instance.dbus_handler, instance.ui_context);
}
eekboard_context_service_set_ui(instance.settings_context, instance.ui_context);
session_register();
if (debug_flags & SQUEEKBOARD_DEBUG_FLAG_GTK_INSPECTOR) {
gtk_window_set_interactive_debugging (TRUE);
}
if (debug_flags & SQUEEKBOARD_DEBUG_FLAG_FORCE_SHOW) {
squeek_state_send_force_visible (rsobjects.state_manager);
}
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
@ -473,5 +404,6 @@ main (int argc, char **argv)
}
g_main_loop_unref (loop);
squeek_wayland_deinit (&instance.wayland);
return 0;
}

View File

@ -1,517 +0,0 @@
/* Copyright (C) 2021 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Application-wide state is stored here.
* It's driven by the loop defined in the loop module. */
use crate::animation;
use crate::imservice::{ ContentHint, ContentPurpose };
use crate::main::{ Commands, PanelCommand };
use crate::outputs;
use crate::outputs::{OutputId, OutputState};
use std::cmp;
use std::collections::HashMap;
use std::time::Instant;
#[derive(Clone, Copy)]
pub enum Presence {
Present,
Missing,
}
#[derive(Clone)]
pub struct InputMethodDetails {
pub hint: ContentHint,
pub purpose: ContentPurpose,
}
#[derive(Clone)]
pub enum InputMethod {
Active(InputMethodDetails),
InactiveSince(Instant),
}
/// Incoming events.
/// This contains events that cause a change to the internal state.
#[derive(Clone)]
pub enum Event {
InputMethod(InputMethod),
Visibility(visibility::Event),
PhysicalKeyboard(Presence),
Output(outputs::Event),
/// Event triggered because a moment in time passed.
/// Use to animate state transitions.
/// The value is the ideal arrival time.
TimeoutReached(Instant),
}
impl From<InputMethod> for Event {
fn from(im: InputMethod) -> Self {
Self::InputMethod(im)
}
}
impl From<outputs::Event> for Event {
fn from(ev: outputs::Event) -> Self {
Self::Output(ev)
}
}
pub mod visibility {
#[derive(Clone)]
pub enum Event {
/// User requested the panel to show
ForceVisible,
/// The user requested the panel to go down
ForceHidden,
}
#[derive(Clone, PartialEq, Debug, Copy)]
pub enum State {
/// Last interaction was user forcing the panel to go visible
ForcedVisible,
/// Last interaction was user forcing the panel to hide
ForcedHidden,
/// Last interaction was the input method changing active state
NotForced,
}
}
/// The outwardly visible state.
#[derive(Clone)]
pub struct Outcome {
pub visibility: animation::Outcome,
pub im: InputMethod,
}
impl Outcome {
/// Returns the commands needed to apply changes as required by the new state.
/// This implementation doesn't actually take the old state into account,
/// instead issuing all the commands as needed to reach the new state.
/// The receivers of the commands bear the burden
/// of checking if the commands end up being no-ops.
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
let layout_hint_set = match new_state {
Outcome {
visibility: animation::Outcome::Visible{..},
im: InputMethod::Active(hints),
} => Some(hints.clone()),
Outcome {
visibility: animation::Outcome::Visible{..},
im: InputMethod::InactiveSince(_),
} => Some(InputMethodDetails {
hint: ContentHint::NONE,
purpose: ContentPurpose::Normal,
}),
Outcome {
visibility: animation::Outcome::Hidden,
..
} => None,
};
// FIXME: handle switching outputs
let (dbus_visible_set, panel_visibility) = match new_state.visibility {
animation::Outcome::Visible{output, height}
=> (Some(true), Some(PanelCommand::Show{output, height})),
animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)),
};
Commands {
panel_visibility,
layout_hint_set,
dbus_visible_set,
}
}
}
/// The actual logic of the program.
/// At this moment, limited to calculating visibility and IM hints.
///
/// It keeps the panel visible for a short time period after each hide request.
/// This prevents flickering on quick successive enable/disable events.
/// It does not treat user-driven hiding in a special way.
///
/// This is the "functional core".
/// All state changes return the next state and the optimal time for the next check.
///
/// This state tracker can be driven by any event loop.
#[derive(Clone)]
pub struct Application {
pub im: InputMethod,
pub visibility_override: visibility::State,
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 {
/// A conservative default, ignoring the actual state of things.
/// It will initially show the keyboard for a blink.
// The ignorance might actually be desired,
// as it allows for startup without waiting for a system check.
// The downside is that adding actual state should not cause transitions.
// Another acceptable alternative is to allow explicitly uninitialized parts.
pub fn new(now: Instant) -> Self {
Self {
im: InputMethod::InactiveSince(now),
visibility_override: visibility::State::NotForced,
physical_keyboard: Presence::Missing,
preferred_output: None,
outputs: Default::default(),
}
}
pub fn apply_event(self, event: Event, _now: Instant) -> Self {
match event {
Event::TimeoutReached(_) => self,
Event::Visibility(visibility) => Self {
visibility_override: match visibility {
visibility::Event::ForceHidden => visibility::State::ForcedHidden,
visibility::Event::ForceVisible => visibility::State::ForcedVisible,
},
..self
},
Event::PhysicalKeyboard(presence) => Self {
physical_keyboard: presence,
..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) {
(InputMethod::Active(_old), InputMethod::Active(new_im))
=> Self {
im: InputMethod::Active(new_im),
..self
},
// For changes in active state, remove user's visibility override.
// Both cases spelled out explicitly, rather than by the wildcard,
// to not lose the notion that it's the opposition that matters
(InputMethod::InactiveSince(_old), InputMethod::Active(new_im))
=> Self {
im: InputMethod::Active(new_im),
visibility_override: visibility::State::NotForced,
..self
},
(InputMethod::Active(_old), InputMethod::InactiveSince(since))
=> Self {
im: InputMethod::InactiveSince(since),
visibility_override: visibility::State::NotForced,
..self
},
// This is a weird case, there's no need to update an inactive state.
// But it's not wrong, just superfluous.
(InputMethod::InactiveSince(old), InputMethod::InactiveSince(_new))
=> Self {
// New is going to be newer than old, so it can be ignored.
// It was already inactive at that moment.
im: InputMethod::InactiveSince(old),
..self
},
}
}
}
fn get_preferred_height(output: &OutputState) -> Option<u32> {
output.get_pixel_size()
.map(|px_size| {
let height = {
if px_size.width > px_size.height {
px_size.width / 5
} else {
if (px_size.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
}
}
};
cmp::min(height, px_size.height / 2)
})
}
pub fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence
Outcome {
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(0);
// 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::ForcedVisible) => visible,
(Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
(Presence::Missing, visibility::State::NotForced) => match self.im {
InputMethod::Active(_) => visible,
InputMethod::InactiveSince(since) => {
if now < since + animation::HIDING_TIMEOUT { visible }
else { animation::Outcome::Hidden }
},
},
}
}
},
im: self.im.clone(),
}
}
/// Returns the next time to update the outcome.
pub fn get_next_wake(&self, now: Instant) -> Option<Instant> {
match self {
Self {
visibility_override: visibility::State::NotForced,
im: InputMethod::InactiveSince(since),
..
} => {
let anim_end = *since + animation::HIDING_TIMEOUT;
if now < anim_end { Some(anim_end) }
else { None }
}
_ => None,
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::outputs::c::WlOutput;
use std::time::Duration;
fn imdetails_new() -> InputMethodDetails {
InputMethodDetails {
purpose: ContentPurpose::Normal,
hint: ContentHint::NONE,
}
}
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]
fn avoid_hide() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
// Check 100ms at 1ms intervals. It should remain visible.
for _i in 0..100 {
now += Duration::from_millis(1);
assert_matches!(
state.get_outcome(now).visibility,
animation::Outcome::Visible{..},
"Hidden when it should remain visible: {:?}",
now.saturating_duration_since(start),
)
}
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible{..});
}
/// Make sure that hiding works when input method goes away
#[test]
fn hide() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
now += Duration::from_millis(1);
assert!(
now < start + Duration::from_millis(250),
"Hiding too slow: {:?}",
now.saturating_duration_since(start),
);
}
}
/// Check against the false showing bug.
/// Expectation: it will get hidden and not appear again
#[test]
fn false_show() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
// This reflects the sequence from Wayland:
// disable, disable, enable, disable
// all in a single batch.
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility {
now += Duration::from_millis(1);
assert!(
now < start + Duration::from_millis(250),
"Still not hidden: {:?}",
now.saturating_duration_since(start),
);
}
// One second without appearing again
for _i in 0..1000 {
now += Duration::from_millis(1);
assert_eq!(
state.get_outcome(now).visibility,
animation::Outcome::Hidden,
"Appeared unnecessarily: {:?}",
now.saturating_duration_since(start),
);
}
}
#[test]
fn force_visible() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::InactiveSince(now),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
now += Duration::from_secs(1);
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
assert_matches!(
state.get_outcome(now).visibility,
animation::Outcome::Visible{..},
"Failed to show: {:?}",
now.saturating_duration_since(start),
);
now += Duration::from_secs(1);
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
now += Duration::from_secs(1);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
now += Duration::from_secs(1);
assert_eq!(
state.get_outcome(now).visibility,
animation::Outcome::Hidden,
"Failed to release forced visibility: {:?}",
now.saturating_duration_since(start),
);
}
#[test]
fn keyboard_present() {
let start = Instant::now(); // doesn't matter when. It would be better to have a reproducible value though
let mut now = start;
let state = Application {
im: InputMethod::Active(imdetails_new()),
physical_keyboard: Presence::Missing,
visibility_override: visibility::State::NotForced,
..application_with_fake_output(start)
};
now += Duration::from_secs(1);
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Present), now);
assert_eq!(
state.get_outcome(now).visibility,
animation::Outcome::Hidden,
"Failed to hide: {:?}",
now.saturating_duration_since(start),
);
now += Duration::from_secs(1);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
now += Duration::from_secs(1);
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
assert_eq!(
state.get_outcome(now).visibility,
animation::Outcome::Hidden,
"Failed to remain hidden: {:?}",
now.saturating_duration_since(start),
);
now += Duration::from_secs(1);
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
assert_matches!(
state.get_outcome(now).visibility,
animation::Outcome::Visible{..},
"Failed to appear: {:?}",
now.saturating_duration_since(start),
);
}
}

View File

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

View File

@ -4,11 +4,20 @@
#include "input-method-unstable-v2-client-protocol.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "eek/eek-types.h"
#include "main.h"
#include "src/ui_manager.h"
struct submission;
struct squeek_layout;
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct vis_manager *vis_manager,
struct wl_seat *seat,
EekboardContextService *state);
// Defined in Rust
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state, struct vis_manager *vis_manager);
uint8_t submission_hint_available(struct submission *self);
void submission_set_ui(struct submission *self, ServerContextService *ui_context);
void submission_use_layout(struct submission *self, struct squeek_layout *layout, uint32_t time);
#endif

View File

@ -19,13 +19,12 @@
use std::collections::HashSet;
use std::ffi::CString;
use crate::vkeyboard::c::ZwpVirtualKeyboardV1;
use ::action::Modifier;
use ::imservice;
use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
use ::layout;
use ::ui_manager::VisibilityManager;
use ::util::vec_remove;
use ::vkeyboard;
use ::vkeyboard::VirtualKeyboard;
@ -36,30 +35,72 @@ use std::iter::FromIterator;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use crate::util::c::Wrapped;
pub type Submission = Wrapped<super::Submission>;
use std::os::raw::c_void;
use ::imservice::c::InputMethod;
use ::util::c::Wrapped;
use ::vkeyboard::c::ZwpVirtualKeyboardV1;
// The following defined in C
/// EekboardContextService*
#[repr(transparent)]
pub struct StateManager(*const c_void);
#[no_mangle]
pub extern "C"
fn submission_new(
im: *mut InputMethod,
vk: ZwpVirtualKeyboardV1,
state_manager: *const StateManager,
visibility_manager: Wrapped<VisibilityManager>,
) -> *mut Submission {
let imservice = if im.is_null() {
None
} else {
let visibility_manager = visibility_manager.clone_ref();
Some(IMService::new(
im,
state_manager,
Box::new(move |active| visibility_manager.borrow_mut().set_im_active(active)),
))
};
// TODO: add vkeyboard too
Box::<Submission>::into_raw(Box::new(
Submission {
imservice,
modifiers_active: Vec::new(),
virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(),
keymap_fds: Vec::new(),
keymap_idx: None,
}
))
}
#[no_mangle]
pub extern "C"
fn submission_use_layout(
submission: Submission,
submission: *mut Submission,
layout: *const layout::Layout,
time: u32,
) {
let submission = submission.clone_ref();
let mut submission = submission.borrow_mut();
if submission.is_null() {
panic!("Null submission pointer");
}
let submission: &mut Submission = unsafe { &mut *submission };
let layout = unsafe { &*layout };
submission.use_layout(layout, Timestamp(time));
}
#[no_mangle]
pub extern "C"
fn submission_hint_available(submission: Submission) -> u8 {
let submission = submission.clone_ref();
let submission = submission.borrow();
fn submission_hint_available(submission: *mut Submission) -> u8 {
if submission.is_null() {
panic!("Null submission pointer");
}
let submission: &mut Submission = unsafe { &mut *submission };
let active = submission.imservice.as_ref()
.map(|imservice| imservice.is_active());
(Some(true) == active) as u8
@ -92,17 +133,6 @@ pub enum SubmitData<'a> {
}
impl Submission {
pub fn new(vk: ZwpVirtualKeyboardV1, imservice: Option<Box<IMService>>) -> Self {
Submission {
imservice,
modifiers_active: Vec::new(),
virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(),
keymap_fds: Vec::new(),
keymap_idx: None,
}
}
/// Sends a submit text event if possible;
/// otherwise sends key press and makes a note of it
pub fn handle_press(

20
src/ui_manager.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef UI_MANAGER__
#define UI_MANAGER__
#include <inttypes.h>
#include "eek/eek-types.h"
#include "outputs.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(void);
void squeek_visman_set_ui(struct vis_manager *visman, ServerContextService *ui_context);
void squeek_visman_set_keyboard_present(struct vis_manager *visman, uint32_t keyboard_present);
#endif

248
src/ui_manager.rs Normal file
View File

@ -0,0 +1,248 @@
/* Copyright (C) 2020 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 std::os::raw::c_void;
use ::util::c::Wrapped;
/// ServerContextService*
#[repr(transparent)]
pub struct UIManager(*const c_void);
extern "C" {
pub fn server_context_service_update_visible(imservice: *const UIManager, active: u32);
pub fn server_context_service_release_visibility(imservice: *const UIManager);
}
#[no_mangle]
pub extern "C"
fn squeek_visman_new() -> Wrapped<VisibilityManager> {
Wrapped::new(VisibilityManager {
ui_manager: None,
visibility_state: VisibilityFactors {
im_active: false,
physical_keyboard_present: false,
}
})
}
/// Use to initialize the UI reference
#[no_mangle]
pub extern "C"
fn squeek_visman_set_ui(visman: Wrapped<VisibilityManager>, ui_manager: *const UIManager) {
let visman = visman.clone_ref();
let mut visman = visman.borrow_mut();
visman.set_ui_manager(Some(ui_manager))
}
#[no_mangle]
pub extern "C"
fn squeek_visman_set_keyboard_present(visman: Wrapped<VisibilityManager>, present: u32) {
let visman = visman.clone_ref();
let mut visman = visman.borrow_mut();
visman.set_keyboard_present(present != 0)
}
#[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,
}
}
}
#[derive(PartialEq, Debug)]
enum Visibility {
Hidden,
Visible,
}
#[derive(Debug)]
enum VisibilityTransition {
/// Hide immediately
Hide,
/// Hide if no show request comes soon
Release,
/// Show instantly
Show,
/// Don't do anything
NoTransition,
}
/// Contains visibility policy
#[derive(Clone, Debug)]
struct VisibilityFactors {
im_active: bool,
physical_keyboard_present: bool,
}
impl VisibilityFactors {
/// Static policy.
/// Use when transitioning from an undefined state (e.g. no UI before).
fn desired(&self) -> Visibility {
match self {
VisibilityFactors {
im_active: true,
physical_keyboard_present: false,
} => Visibility::Visible,
_ => Visibility::Hidden,
}
}
/// Stateful policy
fn transition_to(&self, next: &Self) -> VisibilityTransition {
use self::Visibility::*;
let im_deactivation = self.im_active && !next.im_active;
match (self.desired(), next.desired(), im_deactivation) {
(Visible, Hidden, true) => VisibilityTransition::Release,
(Visible, Hidden, _) => VisibilityTransition::Hide,
(Hidden, Visible, _) => VisibilityTransition::Show,
_ => VisibilityTransition::NoTransition,
}
}
}
// Temporary struct for migration. Should be integrated with Manager eventually.
pub struct VisibilityManager {
/// Owned reference. Be careful, it's shared with C at large
ui_manager: Option<*const c::UIManager>,
visibility_state: VisibilityFactors,
}
impl VisibilityManager {
fn set_ui_manager(&mut self, ui_manager: Option<*const c::UIManager>) {
let new = VisibilityManager {
ui_manager,
..unsafe { self.clone() }
};
self.apply_changes(new);
}
fn apply_changes(&mut self, new: Self) {
if let Some(ui) = &new.ui_manager {
if self.ui_manager.is_none() {
// Previous state was never applied, so effectively undefined.
// Just apply the new one.
let new_state = new.visibility_state.desired();
unsafe {
c::server_context_service_update_visible(
*ui,
(new_state == Visibility::Visible) as u32,
);
}
} else {
match self.visibility_state.transition_to(&new.visibility_state) {
VisibilityTransition::Hide => unsafe {
c::server_context_service_update_visible(*ui, 0);
},
VisibilityTransition::Show => unsafe {
c::server_context_service_update_visible(*ui, 1);
},
VisibilityTransition::Release => unsafe {
c::server_context_service_release_visibility(*ui);
},
VisibilityTransition::NoTransition => {}
}
}
}
*self = new;
}
pub fn set_im_active(&mut self, im_active: bool) {
let new = VisibilityManager {
visibility_state: VisibilityFactors {
im_active,
..self.visibility_state.clone()
},
..unsafe { self.clone() }
};
self.apply_changes(new);
}
pub fn set_keyboard_present(&mut self, keyboard_present: bool) {
let new = VisibilityManager {
visibility_state: VisibilityFactors {
physical_keyboard_present: keyboard_present,
..self.visibility_state.clone()
},
..unsafe { self.clone() }
};
self.apply_changes(new);
}
/// The struct is not really safe to clone due to the ui_manager reference.
/// This is only a helper for getting desired visibility.
unsafe fn clone(&self) -> Self {
VisibilityManager {
ui_manager: self.ui_manager.clone(),
visibility_state: self.visibility_state.clone(),
}
}
}

Some files were not shown because too many files have changed in this diff Show More