Compare commits

..

15 Commits

Author SHA1 Message Date
2e44b448af Merge tag 'v1.14.0' into pureos/byzantium 2021-05-22 14:20:33 +00:00
8afcd87fc8 Merge branch 'byzantium' into 'pureos/byzantium'
CI adjustments and readme

See merge request Librem5/debs/squeekboard!5
2021-04-28 12:30:52 +00:00
be458fb10e CI: Use a separate pipeline 2021-04-23 08:50:37 +00:00
b8e74b3721 Merge branch 'pureos/byzantium' into 'pureos/byzantium'
Release 1.13.0 "Externality" for Byzantium

See merge request Librem5/debs/squeekboard!4
2021-04-15 15:06:44 +00:00
d753f2dc2c Merge branch '1.13.0' into pureos/byzantium 2021-04-12 11:30:18 +00:00
f91c58ae4d Merge branch 'vcs-add' into 'pureos/byzantium'
Update debian/control to add Vcs fields.

See merge request Librem5/debs/squeekboard!3
2021-03-19 07:15:26 +00:00
0c258711cf Update debian/control to add Vcs fields. 2021-03-18 21:29:07 +00:00
622cd03918 Merge branch 'byzantium_fix' into 'pureos/byzantium'
byzantium: Fix version numbers

See merge request Librem5/debs/squeekboard!2
2021-02-13 08:22:46 +00:00
7589a2d1d1 byzantium: Fix version numbers 2021-02-12 15:30:27 +00:00
7cb431b58d Merge branch 'pureos/byzantium' into 'pureos/byzantium'
Release 1.12.0 for Byzantium

See merge request Librem5/debs/squeekboard!1
2021-02-03 11:09:32 +00:00
1dd4b38c88 Byzantium release 1.12.0 2021-01-28 14:40:26 +00:00
1d3e8c9a4b Pull packaging from Bullseye 2021-01-28 14:36:58 +00:00
1c6448a9f7 Merge remote-tracking branch 'upstream/pureos/byzantium' into pureos/byzantium 2021-01-16 14:59:28 +00:00
3d2f9f3d9e Document changes and release 1.9.3.0pureos0.1 2020-09-22 12:43:03 +02:00
a20ab70984 debian: Add gbp.conf 2020-09-22 12:42:30 +02:00
103 changed files with 1784 additions and 4251 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,92 +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: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,6 +1,7 @@
# 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.*"

View File

@ -1,6 +1,7 @@
# 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.*"

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.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -67,21 +59,27 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.72"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
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.113"
version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
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.135"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cf9235533494ea2ddcdb794665461814781c53f19d87b76e571a1c35acbad2b"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.135"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dcde03d87d4c973c04be249e7d8f0b35db1c848c487bd43032808e59dd8328d"
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

@ -23,14 +23,14 @@ rustc_less_1_36 = []
# Dependencies which don't change based on build flags
[dependencies.cairo-sys-rs]
version = "0.9"
version = ""
[dependencies.glib-sys]
version = "*"
version = ""
features = ["v2_44"]
[dependencies.gtk-sys]
version = "0.9"
version = ""
features = ["v3_22"]
[dependencies]

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,6 +0,0 @@
/* Theme independent style */
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,70 +0,0 @@
# Maintained by: Khaled Eldoheiri <khalid@kdehairy.com>
---
outlines:
default: { width: 32.66, height: 52 }
altline: { width: 48.99, height: 52 }
wide: { width: 62, height: 52 }
spaceline: { width: 195.96, height: 52 }
special: { width: 35.66, height: 52 }
views:
base:
- "ذ ض ص ث ق ف غ ع خ ح ج"
- "ش س ي ب ل ا ت ن م ك ط"
- "Shift_L ء ؤ ر ة و ز ظ د BackSpace"
- "show_numbers preferences space . Return"
extra:
- "ذ ض ص ث ق لإ إ ع خ ح ج"
- "ش س ى ب لأ أ ت ن م ك ط"
- "Shift_L ئ لآ لا ه آ ز ظ د 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: "extra"
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: "ض"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
space:
outline: "spaceline"
label: " "
text: " "
Return:
outline: "altline"
icon: "key-enter"
keysym: "Return"

View File

@ -1,70 +0,0 @@
# Maintained by: Khaled Eldoheiri <khalid@kdehairy.com>
---
outlines:
default: { width: 49, height: 42 }
altline: { width: 73.5, height: 42 }
wide: { width: 108, height: 42 }
spaceline: { width: 324, height: 42 }
special: { width: 49, height: 42 }
views:
base:
- "ذ ض ص ث ق ف غ ع خ ح ج"
- "ش س ي ب ل ا ت ن م ك ط"
- "Shift_L ء ؤ ر ة و ز ظ د BackSpace"
- "show_numbers preferences space . Return"
extra:
- "ذ ض ص ث ق لإ إ ع خ ح ج"
- "ش س ى ب لأ أ ت ن م ك ط"
- "Shift_L ئ لآ لا ه آ ز ظ د 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: "extra"
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: "ض"
show_symbols:
action:
set_view: "symbols"
outline: "altline"
label: "*/="
space:
outline: "spaceline"
label: " "
text: " "
Return:
outline: "altline"
icon: "key-enter"
keysym: "Return"

View File

@ -1,78 +0,0 @@
---
outlines:
default: { width: 32.72, height: 52 }
altline: { width: 47, height: 52 }
wide: { width: 49.09, height: 52 }
spaceline: { width: 185, height: 52 }
special: { width: 44, height: 52 }
views:
base:
- "я в е р т ъ у и о п ю"
- "а с д ф г х й к л ш щ"
- "Shift_L з ь ц ж б н м ч BackSpace"
- "show_numbers preferences space . Return"
upper:
- В Е Р Т Ъ У И О П Ю"
- "А С Д Ф Г Х Й К Л Ш Щ"
- "Shift_L З Ь Ц Ж Б Н М Ч BackSpace"
- "show_numbers preferences space , Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # € % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ $ ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] 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: "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: "*/="
space:
outline: "spaceline"
text: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
"\"":
keysym: "quotedbl"

View File

@ -8,19 +8,19 @@ outlines:
views:
base:
- "у е и ш щ к с д з ц б"
- "ь я а о ж г т н в м ч"
- "Shift_L ю й ъ ф х п р л BackSpace"
- "я в е р т ъ у и о п ю"
- "а с д ф г х й к л ш щ"
- "Shift_L з ь ц ж б н м ч BackSpace"
- "show_numbers preferences space . Return"
upper:
- "У Е И Ш Щ К С Д З Ц Б"
- "Ь Я А О Ж Г Т Н В М Ч"
- "Shift_L Ю Й Ъ Ф Х П Р Л BackSpace"
- "Я В Е Р Т Ъ У И О П Ю"
- "А С Д Ф Г Х Й К Л Ш Щ"
- "Shift_L З Ь Ц Ж Б Н М Ч BackSpace"
- "show_numbers preferences space , Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # € % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? ѝ BackSpace"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"

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,91 +0,0 @@
# Maintained by: Jordi Bossy <jordi@bossy.space>. 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 <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,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: ":"

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

@ -0,0 +1,13 @@
bg Български
de Немски
es Испански
emoji Емоджи
fi Френски
gr Гръцки
it Италянски
no Норевежки
pl Полски
ru Руски
se Шведски
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

@ -58,5 +58,3 @@ sq_button.small {
background: #1c71d8;
border-color: #3584e4;
}
@import url("resource:///sm/puri/squeekboard/common.css");

View File

@ -61,5 +61,3 @@ sq_button.small {
background: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#1c71d8;*/
border-color: @borders; /*#3584e4;*/
}
@import url("resource:///sm/puri/squeekboard/common.css");

786
debian/changelog vendored
View File

@ -1,770 +1,94 @@
squeekboard (1.16.0-1) experimental; urgency=medium
squeekboard (1.14.0-1pureos1) byzantium; 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
* debian: New upstream release
[ 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
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sat, 22 May 2021 14:19:27 +0000
[ 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
squeekboard (1.13.0-1pureos1) byzantium; urgency=medium
[ 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
* debian: New upstream release
[ Jordi ]
* Introduce Swiss French keyboard layout
* improve accents layout behavior and code cleaning
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 12 Apr 2021 10:50:32 +0000a
[ 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
squeekboard (1.12.0-1pureos1) byzantium; urgency=medium
[ Dorota Czaplejewicz ]
* data: Split into loading and parsing
* layout: Remove unused code
* build: Fix unnecessary shell quotes
* popover: Allow spanning outside panel area
* cargo: Update dependencies before release
* debian: New Byzantium release
[ undef ]
* Fix typos jp keyboard comments
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Thu, 28 Jan 2021 14:36:20 +0000
[ anteater ]
* use the correct GtkStyleProviderPriority to indicate that the styles are provided by the application
* remove some unnecessary unsafe code
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sat, 15 May 2021 12:45:20 +0000
squeekboard (1.13.0pureos0~amber0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* layout: Latch keys when clicked twice
* layout: Add stateless view switching
* layout: Plug in stateless view switching
* layout: Remove the little abomination of view change promise
* view: Ąto-unlatching when multiple latching buttons pressed
* renderer: Bring button drawing closer to Rust
* ffi: Eliminate squeek_button and squeek_row
* imservice: Increment serials on receiving done, not sending commit
* input-method: Fix commit/done mixup in protocol text
* CI: fix xheck_tag to be compatible with Amber
* italian: Fix colon
* popover: Fix prematurely deallocated CString
* Rust: Remove unnecessary no_mangle statements to silence warnings
* renderer: Reduce reliance on knowing the transform
* renderer: Split mutable geometry and place it directly in GtkKeyboard
* Revert "moved data/langs/he_IL.txt -> data/langs/he-IL.txt to better conform with existing translations."
* layout: Make it possible to opt out of latching per-key
* renderer: Mark latched buttons differently than locked
* appearance: Colour latched/locked according to design
* docs: Describe view switching
* language-terminal: Place keyboards in a sub-path
* layout selection: Fix emoji and number
* rust: Fix compiler warnings
* layout: Take into account text purpose again
* layouts: Make selection testable
* layouts: Stop assuming that layout name always changes on switch
* Cargo: Version bump
[ J.D. Laub ]
* Add US Dvorak layout (and Colemak wide)
* Add US Dvorak layout (and Colemak wide)
[ Jordi Masip ]
* Catalan keyboard layout
[ Myth ]
* Added hebrew keyboard layout
[ David96 ]
* Add Mod4 (Windows) key
[ Panawat Wong-klaew ]
* Add wide Thai keyboard layout
[ Guido Günther ]
* server-main: Add quit()
* server-main: Properly register to gnome-session (Closes: #274)
[ Kozova1 ]
* Added Hebrew translations for most layouts.
* moved data/langs/he_IL.txt -> data/langs/he-IL.txt to better conform with existing translations.
* Fixed Hebrew layout.
* moved data/langs/he_IL.txt -> data/langs/he-IL.txt to better conform with existing translations.
[ M33 ]
* Revert "Update tests/meson.build"
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 12 Apr 2021 10:40:32 +0000
squeekboard (1.12.0pureos0~amber0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* docs: Correct Cargo update instructions
* visibility: Centralize keyboard panel visibility policy and handling
* build: Fix release
* tests: Prefer the env var for finding test layouts
* tests: Explicitly pass source directory to tests
* debian: Build reproducibly
* tests: Allow legacy mode to have much longer tests.
* build: Enable unused warnings in C
* build: Enable wformat to remove warnings about missing wformat
* build: Fail on any C warnings when strict
* data: Made data flow in fallback clearer
* data: Flattened layout fallback function
* layouts: Use base as fallback for alternative layouts
* layouts: Simplify the main flow of source list
* tests: Add some description to the list of tested layouts
* layout_names: Unmess the list of builtin layouts
* dbus: Reset hints if text input missing
* visibility: Stop calling GTK functions from the visibility manager
[ Wannaphong Phatthiyaphaibun ]
* Add thai keyboard
* Update resources.rs
* Update meson.build
* escape " on thai keyboard
[ clonex10100 ]
* Added US Colemak Keyboard Layout
squeekboard (1.12.0-1) unstable; urgency=medium
[ Henry-Nicolas Tourneur ]
* d/rules: fix an FTBFS on mips64el with GOT > 64kb
* d/rules: export RUSTFLAGS only on architecture that needs it
* d/rules: export RUSTFLAGS only on architecture that needs it
[ Jiří Stránský ]
* Add Czech keyboard layouts
[ Stefan Grotz ]
* Esperanto keyboard
[ Vladimir ]
* Bulgarian language keyboard layout
[ Vladimir Stoilov ]
* bulgarian add translation and to needed lists
* Fix bulgarian layout size
[ Andreas Rönnquist ]
* no: Use wide button switching between numbers, symbols and base
[ jranaraki ]
* Farsi/Persian keyboard layout
* Farsi/Persian keyboard layout
* Added requirements to resources.rs and meson.build
* Updated the layout to provide more convenient and faster typing experience
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sun, 10 Jan 2021 09:43:42 +0000
squeekboard (1.11.1) amber-phone; urgency=medium
[ Mark Müller ]
* keyboard: Fix semicolon in German layout
* keyboard: Move semicolon in German layout to numbers view replacing redundant comma key
[ Dorota Czaplejewicz ]
* imservice: Set up UI according to current needs when it shows up
* UI: Keep visibility factors in a central place
* cargo: Update deps
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sat, 21 Nov 2020 11:08:06 +0000
squeekboard (1.11.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* UI: Delay hiding only when leaving a text field
* ui: Cancel hiding delay when activity requested again
* Update dependencies
[ Fabio Tomat ]
* Update fur-IT.txt fix typo for Spanish
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sat, 14 Nov 2020 06:46:28 +0000
squeekboard (1.10.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* virtual_keyboard: Fix desynced modifiers state
* rust: Fix deprecation warnings
* docs: Tutorial syntax cleanups
* docs: Reorganize tutorial
* build: Error on repeating declarations
* keymap: Generate from symbol map, not layout
* data: Restore testability of action->keysym conversion
* syntax: Let older rustc understand symbolmap's lifetime
* debian: Insert a "breaks" for librem5-base < 24
* keymap: Keep keymap fd management in one place
* vkeyboard: Use a generic slice instead of a vector
* tests: Check for missing return in builtin layouts except emoji
* keymap: Concentrate special handling of BackSpace, which is implicit in Erase action
* keymaps: Use multiple key maps, each within the limit of what Xorg can accept.
* build: Avoid MaybeUninit on older Debian
* tests: Fix bad field access
* cargo: Update dependencies
[ Guido Günther ]
* eekboard-context-service: Return early if schema is unavailable
* treewide: Use new style function definitions
* build: Enable '-Wold-style-definition' '-Wstrict-prototypes'
* build: Enable '-Wunused-function'
* eekboard-context-service: Drop EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE
* keyboard: Fix warning
* layout: Fix warning
* gitlab-ci: Enable --Werror
* eek-keyboard: Don't ignore return value
* build: Enable -Winit-self
* build: Enable -Wformat-security
* build: Enable -Wmaybe-uninitialized
* treewide: Drop redundant declarations
* build: Enable -Wredundant-declarations
* ServerContextService: Drop GObject boilerplate
* build: Enable '-Wformat-nonliteral'
* eekboad-context-service: Drop signal class handler
* eekboard-context-service: Drop docstrings for inexistent functions
* eekboard-context-service: Drop the GObject boilerplate
* eekboard-context-service: Drop private struct
* server-context-service: Consistenty name self argument 'self'
* server-context-service: swap signal arguments
* server-context-service: Don't show keyboard when disabled (Closes: #222)
[ Nazarii Kretovych ]
* Add Ukrainian keyboard layout.
[ Benjamin Schaaf ]
* Fix spelling mistakes in doc/hacking.md
* Expand the development documentation in the readme
* Expand key press detection to the edges of the view's bounding box
* Sort layouts by type before sorting by name
* Fix leak in level_keyboard_new
* Fix leak endlessly adding a resource path to the default theme
* Add settings option to popover
[ Al ]
* proposal for belgian layout (copy of fr)
* alphabetical order for src/resources.rs tests/meson.build
* debian: Build reproducibly
[ Arnaud Ferraris ]
* eek-gtk-keyboard: use virtual resolution to check arrangement kind
* server-context-service: optimize height calculation
* keyboards: add wide French layout
* keyboards: add wide Belgian layout
* keyboards: add wide terminal layout
* New upstream version 1.12.0
[ Fabio Tomat ]
* Revert "Add friulian keyboard"
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Tue, 26 Jan 2021 18:19:42 +0100
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 19 Oct 2020 14:07:01 +0000
squeekboard (1.11.1-1) unstable; urgency=medium
squeekboard (1.9.3) amber-phone; urgency=medium
* New upstream version 1.11.1
[ Björn Tantau ]
* Show more useful keys at the same time.
* Add Ctrl and Alt modifier keys.
* Add missing Ê key.
* Make f-keys slightly wider.
* Add Menu key.
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Tue, 24 Nov 2020 11:52:41 +0100
[ Guido Günther ]
* d/rules: Only remove Cargo.lock if it exists
* eek: Drop libcanberra usage
* debian: Build-depend on libfeedback
* eek-gtk-keyboard: Trigger event feedback on button press (Closes: #166)
squeekboard (1.11.0-1) unstable; urgency=medium
[ Dorota Czaplejewicz ]
* build: Add missing gio-unix dependency
* build: Make compatible with Debian Bullseye
* debian: Add amber to legacy distro list
* ci: Add amber job
* debian: Require lsb-release
* size: Hardcode size to work around screen rotation
* ci: Re-add x64 Buster build
* italian: Fix space and period
* New upstream release 1.11.0
[ Sebastian Krzyszkowiak ]
* Revert "Merge branch 'btantau-master-patch-76686' into 'master'"
* Terminal layout: another approach
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Mon, 16 Nov 2020 11:17:23 +0100
[ Luís Fernando Stürmer da Rosa ]
* Brazilian Portuguese Keyboard Layout.
squeekboard (1.10.0-2) unstable; urgency=medium
-- Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm> Wed, 05 Aug 2020 16:16:08 +0200
* Team upload.
* d/rules: set RUSTFLAGS to avoid an FTBFS on mips64el (Closes: #974036)
squeekboard (1.9.2) amber-phone; urgency=medium
-- Henry-Nicolas Tourneur <debian@nilux.be> Tue, 10 Nov 2020 18:40:50 +0000
[ Dorota Czaplejewicz ]
* keyboard: Remove unused code
* gsettings: Don't crash when unavailable
* dbus: Don't crash if can't make a connection
* gsettings: Don't crash on switching when unavailable
* layout: Split out choice to a struct on its own
* renderer: Simplify by dropping gobjectness
* levelkeyboard: Rearrange to make future conversion easier
* layout: Minor generalizations
* Remove unused code
* sizing: Create a standalone UI shape manager
* sizing: Ignore scaling factor for layout selection
* CI: Fix typo
* Update rust deps for release
squeekboard (1.10.0-1) unstable; urgency=medium
[ Andreas Rönnquist ]
* Swedish keyboard, wide button switching between numbers, symbols and base
* More fixes of button sizes
* Folder is doc, not docs
* New upstream release 1.10.0
* d/control: build-depend on libfeedbackd-dev and set team maintainership
* d/control: fix dependency name
* d/copyright: add missing entries
* d/copyright: add entries for new keyboard files and remove duplicate
`src/meson.build` appeared in 2 different paragraphs, remove the
duplicate entry.
* d/gbp.conf: fix debian version number
[ uzanto ]
* Add new file
* Replace duplicated show_symbols by show_eschars and removed "Delete" button that it's doing nothing
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 23 Oct 2020 13:18:43 +0200
[ Arnaud Ferraris ]
* keyboards: fr: fix keyboard layout
* keyboards: fr: make sure the layout fits the screen
* resources: include French keyboard layout
* keyboards: fr: improve consistency with other layouts
* keyboards: fr: improve diacritics layout
* tests: add french layout
squeekboard (1.9.3-1) experimental; urgency=medium
[ Vlad ]
* Fresh Russian layout
* Upload to experimental
* Update upstream source from tag 'v1.9.3'
Update to upstream version '1.9.3'
with Debian dir 7a3f8b82779759ba288b75755ba54500250b0ff4
* d/control: Use librust-xkbcommon-dev
This one avoids the empty feature package.
* Ship sm.puri.OSK0.desktop.
This is needed to fulfill phosh's session dependencies
* Conflict with phosh-osk-stub.
They're not useful at the same time and we'll drop phosh-osk-stub from
Debian once squeekboard is in.
* Drop pathes no longer required due to upstream changes
- 0001-Cargo.toml-update-to-Debian-dependencies-versions.patch
- 0002-popover.rs-fix-build-with-gtk-rs-0.7.0.patch
* d/gbp.conf: Don't use patch numbers.
Ordering is defined via the series file and patch numbers just
cause manual work.
[ Jordi Masip ]
* Removed unused dependency 'libcroco'
-- Guido Günther <agx@sigxcpu.org> Sat, 26 Sep 2020 13:01:18 +0200
[ Florian Klink ]
* sm.puri.Squeekboard.desktop: make path to Exec= absolute
squeekboard (1.9.2-1) UNRELEASED; urgency=medium
[ Ole Guldberg ]
* Danish keyboard layout
* Danish keyboard layout
* add test for danish layout
* Initial Debian release (Closes: #956960)
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 01 Jun 2020 09:39:12 +0000
squeekboard (1.9.1) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* layout: Improve press handling
* settings: Handle empty settings
* Variant: Use proper pointer conversion between C and Rust
* meta: Add doap file
* modifiers: Support Control and Alt
* CI: Test that any bump to changelog has a corresponding tag
* docs: Add the guiding principle
* hacking: Move into docs/
[ &t ]
* Fix minor comment typos
[ Dorota Czaplejewicz ]
* cargo: Bump package versions before release
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sun, 08 Mar 2020 10:04:29 +0000
squeekboard (1.9.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* imservice: Add commit_string method
* submission: Handle submitting strings
* input_method: Use for erasing
* logging: Use in merged functions
* translations: Remove redundant ones
* translations: Translate builtin layouts
* greek: Rename to gr which is used by gnome settings
[ Sebastian Krzyszkowiak ]
* layouts: Add Polish layouts
[ Dorota Czaplejewicz ]
* locks: Draw based on current view
* locking: Lock keys statelessly
* layouts: Better accented uppercase in PL
* emoji: Add more choices
* row: Eliminate angle
* layout: Center views relative to each other and the layout bounds
* drawing: Generalized foreach_visible_button
* variant: Fix double-free
* variant: Fix leak
* keyboard_layout: Fix leak
* layout: Improve scoping of locked variable
* terminal: Make */ easier to reach
[ Sebastian Krzyszkowiak ]
* layouts: terminal: Use altline outline for dot key
[ Dorota Czaplejewicz ]
* text input: Disable erasing
* cargo: Update deps
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Wed, 19 Feb 2020 14:32:39 +0000
squeekboard (1.8.1) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* action: Rename Level to View
* keyboard: Introduce a KeyCode type wrapping u32
* layout: Centralize handling key releases
* layout: Make handling presses uniform
* UI: Drop indirection for show/hide functions
* managers: Move visible flag to UI manager
* dbus_service: Remove unused function
* dbus: Remove unneeded gobjectness
* dbus: Rename handler from eekboard_service
* context: Moved keymap setting together with its generation
* key-emitter: Remove unused
* eekboard_context_service: Drop unused enable property
* services: Split out layout management from EekboardContextService
* submission: Move away from virtual-keyboard
* submission: Create a new wrapper over imservice
* imservice: Limited scope of unsafe
* EekGtkKeyboard: Use a direct reference to EekboardContext
* submission: Take over virtual_keyboard handling
* keyboard: Cleanups of unused code
* levelkeyboard: Drop unused manager references
* keyboard: Gather up keymap handling, drop layout
* submission: Remove wildcard reexport
* imservice: Rename commit_state to done to match protocol
* ci: Clean up `..` before it's searched for artifacts
* dbus: Log error on dbus exit
* logging: Try to improve common operations
* imservice: Return something more resembling an Error on failure
* logging: Unified to remove random eprint calls
* press_key: Use proper logging
* number: Fix keysym for Return
* build: Strip clap of optional features
* layouts: Fix segfault on switching to wide
* font: Use font from style context
* font: Only pass relevant data to label renderer
[ Sebastian Krzyszkowiak ]
* layout: terminal: Swap positions of preferences and actions button
* layout: terminal: Show actions button on all views
* layout: terminal: Replace actions button with period on symbols view
[ Dorota Czaplejewicz ]
* setup: Connect ui to the state manager
* debian: Add missing commas
[ David Boddie ]
* Tidy build file and docs
* Use pip to install recommonmark
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Fri, 31 Jan 2020 09:59:12 +0000
squeekboard (1.8.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* translations: Use gnome-desktop's xkb info database for layout names
* translations: Make the code cleaner
* overlay: Add terminal
* eek-layout: Remove unused
* pre-release: Update deps
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 14 Jan 2020 13:55:00 +0000
squeekboard (1.7.0) amber-phone; urgency=medium
* New terminal layout appearing on terminal input hint
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Wed, 08 Jan 2020 11:53:07 +0000
squeekboard (1.7.0) amber-phone; urgency=medium
* New terminal layout appearing on terminal input hint
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Wed, 08 Jan 2020 11:53:07 +0000
squeekboard (1.6.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* tools: Move entry.py
* build: Move building of squeekboard-test-layout to tools
* packaging: Install entty.py as squeekboard-entry
* Remove unused build dependencies
* Remove unused header generator
* logging: Move all facilities to one file
* logging: Described the design
* logging: Add described log levels
* popover: Install emoji layout
* popover: Show overlays as selected
* Fix old Rust woes
* emoji: Add a passable layout
* Fix g_ and stdlib allocation/free mismatches
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Thu, 02 Jan 2020 12:02:50 +0000
squeekboard (1.5.0) amber-phone; urgency=medium
[ Dorota Czaplejewicz ]
* keycodes: Sort to eliminate runtime indeterminism
* switcher: Switch layout on menu item click
* Drop squeek_key
* renderer: Remove some unneeded vars
* renderer: Simplified outline rendering
* renderer: Drop row from button rendering
* renderer: Drop unused params
* renderer: Simplify surface rendering
* rendering: Simplify Cairo context usage, remove unneeded calls.
* rendering: Remove unneeded redraw after button release
* renderer: Remove unused locked key render function
* renderer: Simply cut off when painting outside bounds
* renderer: Render whole keyboard the same way as pressed buttons
[ Mark Müller ]
* layout: add German wide layout
[ Dorota Czaplejewicz ]
* renderer: Remove unused functions
* cleanup: Remove references to squeek_view
* cleanup: Unbox View and Row
* cleanup: Remove unused single frame draw
* positioning: Calculate sizes instead of storing, move position out of widgets
* positioning: Clean up unused code
* Fix old Rust woes
[ Mark Müller ]
* layout: add Japanese Kana wide layout
[ Dorota Czaplejewicz ]
* Entry test: Add Terminal input purpose
* readme: Add note about Cargo dependencies
* Create a library/UI module separation
* hacking: Add DCO and licensing requirement
* Fix internal .md link
[ Mark Müller ]
* squeekboard-test-layout: add argument parsing and some more output
[ Dorota Czaplejewicz ]
* Use clap in the lockfile
* parsing: Remove bounds which weren't used anyway
* layout: Respect margins
* CI: Build arm64 .deb
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 23 Dec 2019 11:58:57 +0000
squeekboard (1.4.0) amber-phone; urgency=medium
* "text" property in layouts
* Adjusts to user's color scheme
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 02 Dec 2019 19:37:01 +0000
squeekboard (1.3.2) amber-phone; urgency=medium
* Make sure all key presses get accepted by the compositor
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 26 Nov 2019 15:36:27 +0000
squeekboard (1.3.1) amber-phone; urgency=medium
* Update and fix layouts and languages
* Make tests less likely to fail
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Wed, 20 Nov 2019 22:10:48 +0000
squeekboard (1.3.0) amber-phone; urgency=medium
* Language selection popup
* Swedish and Finnish layouts
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Sat, 16 Nov 2019 15:38:14 +0000
squeekboard (1.2.2) amber-phone; urgency=medium
* Landscape mode
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Wed, 30 Oct 2019 12:38:39 +0000
squeekboard (1.2.1) amber-phone; urgency=medium
* Use different distribution
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 08 Oct 2019 10:56:10 +0000
squeekboard (1.2.0) unstable; urgency=medium
* Use Cargo-based dependencies
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 24 Sep 2019 10:42:15 +0000
squeekboard (1.1.0) unstable; urgency=medium
* Use new keyboard layout format
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Mon, 02 Sep 2019 10:12:02 +0000
squeekboard (1.0.10) unstable; urgency=medium
* Use a shared DBus definition
-- Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm> Tue, 02 Jul 2019 20:12:02 +0000
squeekboard (1.0.9) unstable; urgency=medium
* Initial release.
-- David Boddie <david.boddie@puri.sm> Tue, 25 Jun 2019 19:33:00 +0200
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Tue, 09 Jun 2020 23:29:19 +0200

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
10

6
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,17 +20,19 @@ 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
Homepage: https://source.puri.sm/Librem5/squeekboard
Vcs-Browser: https://source.puri.sm/pureos/squeekboard
Vcs-Git: https://source.puri.sm/pureos/squeekboard.git
Package: squeekboard
Architecture: linux-any

7
debian/gbp.conf vendored Normal file
View File

@ -0,0 +1,7 @@
[DEFAULT]
debian-branch = pureos/byzantium
debian-tag = pureos/%(version)s
debian-tag-msg = %(pkg)s %(version)s
[tag]
sign-tags = true

29
debian/librem5-ci.yml vendored Normal file
View File

@ -0,0 +1,29 @@
include:
- 'https://source.puri.sm/Librem5/librem5-ci/raw/master/librem5-pipeline-definitions.yml'
- 'https://source.puri.sm/Librem5/librem5-ci/raw/master/librem5-pipeline-byzantium-jobs.yml'
stages:
- package
- test-package
.tags: &tags
tags:
- librem5
check_release:
<<: *tags
stage: test-package
only:
refs:
- pureos/byzantium
script:
- apt-get -y install git python3
- (head -n 1 ./debian/changelog && git tag) | ./debian/check_release.py
# check-tarball relies on the contents of other branches,
# which are irrelevant for MRs. It's similar to checking tags in this way.
check-tarball:
extends: .l5-check-tarball
only:
refs:
- pureos/byzantium

View File

@ -1 +1 @@
3.0 (native)
3.0 (quilt)

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,73 +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
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

@ -312,6 +312,7 @@ on_phosh_layer_surface_unmapped (PhoshLayerSurface *self, gpointer unused)
PhoshLayerSurfacePrivate *priv;
g_return_if_fail (PHOSH_IS_LAYER_SURFACE (self));
priv = phosh_layer_surface_get_instance_private (self);
priv = phosh_layer_surface_get_instance_private (self);
if (priv->layer_surface) {

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.16.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 = [
'',
'------------------',
@ -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

@ -1,11 +0,0 @@
ca
de
fa
fi
fur
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,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,46 +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.
#
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 09:38-0300\n"
"Last-Translator: Rafael Fontenelle <rafaelff@gnome.org>\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.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 "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,17 +0,0 @@
/* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Animation details */
use std::time::Duration;
/// 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,
Hidden,
}

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,7 +7,7 @@ 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::WidgetExt;
@ -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,141 +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 Sender = mpsc::Sender<Event>;
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,186 +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 };
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,
};
let l = State::new(state, now);
let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now);
assert_eq!(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;
@ -27,6 +23,9 @@ pub mod c {
use std::os::raw::{c_char, c_void};
pub use ::ui_manager::c::UIManager;
pub use ::submission::c::StateManager;
// The following defined in C
/// struct zwp_input_method_v2*
@ -41,6 +40,7 @@ pub mod c {
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
@ -141,6 +141,7 @@ pub mod c {
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 {
@ -149,7 +150,19 @@ 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
@ -165,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
@ -220,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,
@ -316,7 +329,9 @@ impl Default for IMProtocolState {
pub struct IMService {
/// Owned reference (still created and destroyed in C)
pub im: *mut c::InputMethod,
sender: driver::Threaded,
/// 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?
@ -332,13 +347,15 @@ pub enum SubmitError {
impl IMService {
pub fn new(
im: *mut c::InputMethod,
sender: driver::Threaded,
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(),
@ -398,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,6 +11,7 @@ extern crate gtk_sys;
#[allow(unused_imports)]
#[macro_use] // only for tests
extern crate maplit;
extern crate regex;
extern crate serde;
extern crate xkbcommon;
@ -18,21 +19,18 @@ extern crate xkbcommon;
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;

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,33 +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;
};
void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler);
struct rsobjects squeek_rsobjects_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk);
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,149 +0,0 @@
/* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Glue for the main loop. */
use crate::state;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
mod c {
use super::*;
use std::os::raw::c_void;
use std::rc::Rc;
use std::time::Instant;
use crate::event_loop::driver;
use crate::imservice::IMService;
use crate::imservice::c::InputMethod;
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 {
receiver: Wrapped<Receiver<Commands>>,
state_manager: Wrapped<driver::Threaded>,
submission: Wrapped<Submission>,
}
extern "C" {
fn server_context_service_real_show_keyboard(service: *const UIManager);
fn server_context_service_real_hide_keyboard(service: *const UIManager);
fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32);
// 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_rsobjects_new(
im: *mut InputMethod,
vk: ZwpVirtualKeyboardV1,
) -> RsObjects {
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
let now = Instant::now();
let state_manager = driver::Threaded::new(sender, state::Application::new(now));
let imservice = if im.is_null() {
None
} else {
Some(IMService::new(im, state_manager.clone()))
};
let submission = Submission::new(vk, imservice);
RsObjects {
submission: Wrapped::new(submission),
state_manager: Wrapped::new(state_manager),
receiver: Wrapped::new(receiver),
}
}
/// 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();
ctx.acquire();
receiver.attach(
Some(&ctx),
move |msg| {
main_loop_handle_message(msg, ui_manager, dbus_handler);
Continue(true)
},
);
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) => unsafe {
server_context_service_real_show_keyboard(ui_manager);
},
Some(PanelCommand::Hide) => unsafe {
server_context_service_real_hide_keyboard(ui_manager);
},
None => {},
};
if let Some(visible) = msg.dbus_visible_set {
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,
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

@ -5,7 +5,9 @@ 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;
@ -18,9 +20,10 @@ use gio::SimpleActionExt;
use glib::translate::FromGlibPtrNone;
use glib::variant::ToVariant;
#[cfg(not(feature = "gtk_v0_5"))]
use gtk::prelude::*;
use gtk::BuilderExtManual;
use gtk::PopoverExt;
use gtk::WidgetExt;
use std::io::Write;
use ::logging::Warn;
mod c {
@ -108,6 +111,46 @@ 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{};
gio::SettingsSchemaSource::get_default()
@ -207,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),
@ -217,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 {
@ -229,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(
@ -268,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)| {
@ -285,14 +367,32 @@ pub fn show(
}
});
let builder = gtk::Builder::new_from_resource("/sm/puri/squeekboard/popover.ui");
let model: gio::Menu = 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);
}
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 {
@ -303,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,
@ -347,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,24 +18,12 @@ 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")),
("be", include_str!("../data/keyboards/be.yaml")),
("be_wide", include_str!("../data/keyboards/be_wide.yaml")),
("bg", include_str!("../data/keyboards/bg.yaml")),
("bg+phonetic", include_str!("../data/keyboards/bg+phonetic.yaml")),
("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")),
@ -86,19 +79,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")),
@ -120,6 +105,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::*;
@ -130,4 +155,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

@ -30,6 +30,7 @@
enum {
PROP_0,
PROP_VISIBLE,
PROP_ENABLED,
PROP_LAST
};
@ -42,10 +43,12 @@ struct _ServerContextService {
struct submission *submission; // unowned
struct squeek_layout_state *layout;
struct ui_manager *manager; // unowned
struct squeek_state_manager *state_manager; // shared reference
struct vis_manager *vis_manager; // owned
gboolean visible;
PhoshLayerSurface *window;
GtkWidget *widget; // nullable
guint hiding;
guint last_requested_height;
};
@ -64,6 +67,23 @@ on_destroy (ServerContextService *self, GtkWidget *widget)
//eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context));
}
static void
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)
{
@ -167,6 +187,8 @@ make_window (ServerContextService *self)
g_object_connect (self->window,
"swapped-signal::destroy", G_CALLBACK(on_destroy), 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);
@ -177,7 +199,8 @@ make_window (ServerContextService *self)
// 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);
}
@ -203,8 +226,7 @@ make_widget (ServerContextService *self)
gtk_widget_show_all(self->widget);
}
// Called from rust
void
static void
server_context_service_real_show_keyboard (ServerContextService *self)
{
if (!self->window) {
@ -213,16 +235,99 @@ server_context_service_real_show_keyboard (ServerContextService *self)
if (!self->widget) {
make_widget (self);
}
self->visible = TRUE;
gtk_widget_show (GTK_WIDGET(self->window));
}
// Called from rust
void
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)
{
if (self->window) {
gtk_widget_hide (GTK_WIDGET(self->window));
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
@ -234,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);
@ -249,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;
@ -277,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:
*
@ -318,20 +442,24 @@ init (ServerContextService *self) {
}
ServerContextService *
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager)
server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct 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->manager = uiman;
ui->state_manager = state_manager;
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

@ -29,7 +29,7 @@ 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 ui_manager *uiman, struct squeek_state_manager *state_manager);
ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct 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);

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,7 +29,6 @@
#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"
@ -39,29 +38,21 @@
#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;
/// UI shape tracker/chooser. TODO: merge into state::Application
struct ui_manager *ui_manager;
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)
@ -86,16 +77,13 @@ 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.");
exit (1);
}
// Wayland
@ -259,106 +247,16 @@ 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);
}
/// Create Rust objects in one go,
/// to avoid crossing the language barrier and losing type information
static struct rsobjects create_rsobjects(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat) {
struct zwp_input_method_v2 *im = NULL;
if (immanager) {
im = zwp_input_method_manager_v2_get_input_method(immanager, seat);
}
struct zwp_virtual_keyboard_v1 *vk = NULL;
if (vkmanager) {
vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmanager, seat);
}
return squeek_rsobjects_new(im, vk);
}
static GDebugKey debug_keys[] =
{
{ .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 ();
@ -385,19 +283,11 @@ main (int argc, char **argv)
g_error("No virtual keyboard manager Wayland global available.");
exit(1);
}
if (!instance.wayland.layer_shell) {
g_error("No layer shell global available.");
exit(1);
}
if (!instance.wayland.input_method_manager) {
g_warning("Wayland input method interface not available");
}
struct rsobjects rsobjects = create_rsobjects(instance.wayland.input_method_manager,
instance.wayland.virtual_keyboard_manager,
instance.wayland.seat);
instance.ui_manager = squeek_uiman_new();
instance.settings_context = eekboard_context_service_new(&instance.layout_choice);
@ -407,17 +297,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");
@ -430,7 +354,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");
@ -438,31 +362,36 @@ 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,
instance.ui_manager,
rsobjects.state_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);

View File

@ -1,417 +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 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
#[derive(Clone)]
pub enum Event {
InputMethod(InputMethod),
Visibility(visibility::Event),
PhysicalKeyboard(Presence),
/// 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)
}
}
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,
};
let (dbus_visible_set, panel_visibility) = match new_state.visibility {
animation::Outcome::Visible => (Some(true), Some(PanelCommand::Show)),
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,
}
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,
}
}
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::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
},
}
}
}
pub fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence
Outcome {
visibility: match (self.physical_keyboard, self.visibility_override) {
(_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
(_, visibility::State::ForcedVisible) => animation::Outcome::Visible,
(Presence::Present, visibility::State::NotForced) => animation::Outcome::Hidden,
(Presence::Missing, visibility::State::NotForced) => match self.im {
InputMethod::Active(_) => animation::Outcome::Visible,
InputMethod::InactiveSince(since) => {
if now < since + animation::HIDING_TIMEOUT { animation::Outcome::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)]
mod test {
use super::*;
use std::time::Duration;
fn imdetails_new() -> InputMethodDetails {
InputMethodDetails {
purpose: ContentPurpose::Normal,
hint: ContentHint::NONE,
}
}
/// 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,
};
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_eq!(
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_eq!(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,
};
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,
};
// 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,
};
now += Duration::from_secs(1);
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
assert_eq!(
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,
};
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_eq!(
state.get_outcome(now).visibility,
animation::Outcome::Visible,
"Failed to appear: {:?}",
now.saturating_duration_since(start),
);
}
}

View File

@ -4,12 +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(

View File

@ -5,7 +5,6 @@
#include "eek/eek-types.h"
#include "outputs.h"
#include "main.h"
struct ui_manager;
@ -15,5 +14,7 @@ uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
struct vis_manager;
struct vis_manager *squeek_visman_new(struct squeek_state_manager *state_manager);
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

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020, 2021 Purism SPC
/* Copyright (C) 2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
@ -10,10 +10,48 @@
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"
@ -80,3 +118,131 @@ impl Manager {
}
}
}
#[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(),
}
}
}

View File

@ -58,15 +58,9 @@ foreach layout : [
'us', 'us_wide',
# Block: Languages
'am', 'am+phonetic',
'ara', 'ara_wide',
'be', 'be_wide',
'bg',
'bg+phonetic',
'br',
'ch+fr',
'ch+de',
'ch', 'ch_wide',
'cz', 'cz_wide',
'cz+qwerty', 'cz+qwerty_wide',
'de', 'de_wide',
@ -93,14 +87,12 @@ foreach layout : [
# Terminal keyboards
'terminal/fr',
'terminal/fr_wide',
'terminal/us',
'terminal/us_wide',
# Block: Not languages.
'emoji/us',
'number/us',
'pin/us',
]
extra = []
if layout.startswith('emoji/')

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

@ -1,13 +1,10 @@
#!/usr/bin/env python3
import gi
import random
import sys
gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk
from gi.repository import GLib
try:
terminal = [("Terminal", Gtk.InputPurpose.TERMINAL)]
@ -17,8 +14,6 @@ except AttributeError:
def new_grid(items, set_type):
grid = Gtk.Grid(orientation='vertical', column_spacing=8, row_spacing=8)
grid.props.margin = 6
i = 0
for text, value in items:
l = Gtk.Label(label=text)
@ -46,51 +41,20 @@ class App(Gtk.Application):
] + terminal
hints = [
("OSK provided", Gtk.InputHints.INHIBIT_OSK),
("Uppercase chars", Gtk.InputHints.UPPERCASE_CHARS),
("OSK provided", Gtk.InputHints.INHIBIT_OSK)
]
purpose_timer = 0;
def on_purpose_toggled(self, btn, entry):
purpose = Gtk.InputPurpose.PIN if btn.get_active() else Gtk.InputPurpose.PASSWORD
entry.set_input_purpose(purpose)
def on_timeout(self, e):
r = random.randint(0, len(self.purposes) - 1)
(_, purpose) = self.purposes[r]
print(f"Setting {purpose}")
e.set_input_purpose(purpose)
return True
def on_is_focus_changed(self, e, *args):
if not self.purpose_timer and e.props.is_focus:
GLib.timeout_add_seconds (3, self.on_timeout, e)
def add_random (self, grid):
l = Gtk.Label(label="Random")
e = Gtk.Entry(hexpand=True)
e.connect("notify::is-focus", self.on_is_focus_changed)
e.set_input_purpose(Gtk.InputPurpose.FREE_FORM)
grid.attach(l, 0, len(self.purposes), 1, 1)
grid.attach(e, 1, len(self.purposes), 1, 1)
def do_activate(self):
w = Gtk.ApplicationWindow(application=self)
w.set_default_size (300, 500)
notebook = Gtk.Notebook()
def add_purpose(entry, purpose):
entry.set_input_purpose(purpose)
def add_hint(entry, hint):
entry.set_input_hints(hint)
purpose_grid = new_grid(self.purposes, add_purpose)
self.add_random(purpose_grid)
hint_grid = new_grid(self.hints, add_hint)
purpose_scroll = Gtk.ScrolledWindow()
purpose_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
purpose_scroll.add(purpose_grid)
notebook.append_page(purpose_scroll, Gtk.Label(label="Purposes"))
notebook.append_page(purpose_grid, Gtk.Label(label="Purposes"))
notebook.append_page(hint_grid, Gtk.Label(label="Hints"))
w.add(notebook)
w.show_all()

View File

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

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