diff --git a/Cargo.lock b/Cargo.lock
index bee2b6b5..bb1d07ef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,15 +1,232 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "bitflags"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "cairo-rs"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "dtoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "fragile"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "gdk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gdk-pixbuf-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gio"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fragile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "glib"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gtk"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "atk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-pixbuf-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gdk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "libc"
version = "0.2.62"
@@ -25,6 +242,11 @@ name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "memchr"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "memmap"
version = "0.7.0"
@@ -34,6 +256,36 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "pango"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "proc-macro2"
version = "1.0.4"
@@ -50,12 +302,35 @@ dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "regex"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "rs"
version = "0.1.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gtk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
"xkbcommon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -100,11 +375,24 @@ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "thread_local"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "utf8-ranges"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "winapi"
version = "0.3.8"
@@ -142,19 +430,45 @@ dependencies = [
]
[metadata]
+"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
+"checksum atk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7017e53393e713212aed7aea336b6553be4927f58c37070a56c2fe3d107e489"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
+"checksum cairo-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd940f0d609699e343ef71c4af5f66423afbf30d666f796dabd8fd15229cf5b6"
+"checksum cairo-sys-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d25596627380be4381247dba06c69ad05ca21b3b065bd9827e416882ac41dcd2"
+"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e"
+"checksum fragile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f8140122fa0d5dcb9fc8627cfce2b37cc1500f752636d46ea28bc26785c2f9"
+"checksum gdk 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc52c7244046df9d959df87289f1fc5cca23f9f850bab0c967963e2ecb83a96"
+"checksum gdk-pixbuf 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc3aa730cb4df3de5d9fed59f43afdf9e5fb2d3d10bfcbd04cec031435ce87f5"
+"checksum gdk-pixbuf-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08284f16ce4d909b10d785a763ba190e222d2c1557b29908bf0a661e27a8ac3b"
+"checksum gdk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "108548ebf5329b551f2b97ab356908d14627905abb74b936c3372de1535aee81"
+"checksum gio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "29a44b051990573448edc80b1995237f8b97b5734d2aec05105b9242aa10af11"
+"checksum gio-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6975ada29f7924dc1c90b30ed3b32d777805a275556c05e420da4fbdc22eb250"
+"checksum glib 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a333edf5b9f1411c246ef14e7881b087255f04c56dbef48c64a0cb039b4b340"
+"checksum glib-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3573351e846caed9f11207b275cd67bc07f0c2c94fb628e5d7c92ca056c7882d"
+"checksum gobject-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08475e4a08f27e6e2287005950114735ed61cec2cb8c1187682a5aec8c69b715"
+"checksum gtk 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "56a6b30f194f09a17bb7ffa95c3ecdb405abd3b75ff981f831b1f6d18fe115ff"
+"checksum gtk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d487d333a4b87072e6bf9f2e55befa0ebef01b9496c2e263c0f4a1ff3d6c04b1"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+"checksum pango 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c2cb169402a3eb1ba034a7cc7d95b8b1c106e9be5ba4be79a5a93dc1a2795f4"
+"checksum pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6eb49268e69dd0c1da5d3001a61aac08e2e9d2bfbe4ae4b19b9963c998f6453"
+"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea"
"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+"checksum regex 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d9d8297cc20bbb6184f8b45ff61c8ee6a9ac56c156cec8e38c3e5084773c44ad"
+"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd"
"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e"
"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
+"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
index 327eee53..b7fbd203 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,10 +5,33 @@ version = "0.1.0"
[dependencies]
bitflags = "1.0.*"
maplit = "1.0.*"
+regex = "1.1.*"
serde = { version = "1.0.*", features = ["derive"] }
serde_yaml = "0.8.*"
xkbcommon = { version = "0.4.*", features = ["wayland"] }
+[dependencies.gio]
+version = ""
+features = ["v2_44"]
+
+[dependencies.glib]
+version = ""
+features = ["v2_44"]
+
+[dependencies.glib-sys]
+version = ""
+features = ["v2_44"]
+
+
+[dependencies.gtk]
+version = "0.5.*"
+features = ["v3_22"]
+
+[dependencies.gtk-sys]
+version = ""
+features = ["v3_22"]
+
+
[lib]
name = "rs"
path = "src/lib.rs"
diff --git a/data/langs/en-US.txt b/data/langs/en-US.txt
new file mode 100644
index 00000000..6d1ef2b1
--- /dev/null
+++ b/data/langs/en-US.txt
@@ -0,0 +1,8 @@
+us English (US)
+de German
+el Greek
+es Spanish
+it Italian
+jp+kana Japanese (kana)
+nb Norwegian
+
diff --git a/data/langs/pl-PL.txt b/data/langs/pl-PL.txt
new file mode 100644
index 00000000..0a26adf3
--- /dev/null
+++ b/data/langs/pl-PL.txt
@@ -0,0 +1,8 @@
+us angielski (USA)
+de niemiecki
+el grecki
+es hiszpański
+it włoski
+jp+kana japoński (kana)
+nb norweski
+
diff --git a/data/popup.ui b/data/popup.ui
new file mode 100644
index 00000000..9e76694d
--- /dev/null
+++ b/data/popup.ui
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/data/squeekboard.gresources.xml b/data/squeekboard.gresources.xml
index 7287d3c1..8ccee29d 100644
--- a/data/squeekboard.gresources.xml
+++ b/data/squeekboard.gresources.xml
@@ -29,6 +29,7 @@
keyboards/symbols/ua.xml
keyboards/symbols/ug.xml
keyboards/symbols/zh-bopomofo.xml
+ popup.ui
icons/key-enter.svg
icons/key-shift.svg
icons/keyboard-mode-symbolic.svg
diff --git a/debian/control b/debian/control
index dfa1060c..b11e6a92 100644
--- a/debian/control
+++ b/debian/control
@@ -12,7 +12,13 @@ Build-Depends:
libgtk-3-dev,
libcroco3-dev,
librust-bitflags-1-dev (>= 1.0),
+ librust-gio+v2-44-dev,
+ librust-glib+v2-44-dev,
+ librust-glib-sys-dev,
+ 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),
diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c
index 0674f103..5d28a764 100644
--- a/eek/eek-gtk-keyboard.c
+++ b/eek/eek-gtk-keyboard.c
@@ -145,7 +145,7 @@ static void release(EekGtkKeyboard *self, guint32 time)
{
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
- squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, time, self);
+ squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, eek_renderer_get_transformation(priv->renderer), time, self);
}
static gboolean
diff --git a/src/action.rs b/src/action.rs
index 06f789d8..61660653 100644
--- a/src/action.rs
+++ b/src/action.rs
@@ -36,4 +36,5 @@ pub enum Action {
/// The key events this symbol submits when submitting text is not possible
keys: Vec,
},
+ ShowPreferences,
}
diff --git a/src/data.rs b/src/data.rs
index 0066b8ec..85276346 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -509,10 +509,7 @@ fn create_action(
&view_names
),
},
- Some(Action::ShowPrefs) => ::action::Action::Submit {
- text: None,
- keys: Vec::new(),
- },
+ Some(Action::ShowPrefs) => ::action::Action::ShowPreferences,
None => ::action::Action::Submit {
text: None,
keys: keysyms.into_iter().map(::action::KeySym).collect(),
diff --git a/src/layout.h b/src/layout.h
index ea6ee949..c88bea30 100644
--- a/src/layout.h
+++ b/src/layout.h
@@ -62,7 +62,10 @@ const char *squeek_layout_get_keymap(const struct squeek_layout*);
enum squeek_arrangement_kind squeek_layout_get_kind(const struct squeek_layout *);
void squeek_layout_free(struct squeek_layout*);
-void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, uint32_t timestamp, EekGtkKeyboard *ui_keyboard);
+void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
+ struct transformation widget_to_layout,
+ uint32_t timestamp,
+ EekGtkKeyboard *ui_keyboard);
void squeek_layout_release_all_only(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, uint32_t timestamp);
void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
double x_widget, double y_widget,
diff --git a/src/layout.rs b/src/layout.rs
index 442dc893..27e32cd5 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -37,6 +37,7 @@ pub mod c {
use std::ffi::CStr;
use std::os::raw::{ c_char, c_void };
use std::ptr;
+ use gtk_sys;
// The following defined in C
@@ -45,7 +46,7 @@ pub mod c {
#[repr(transparent)]
#[derive(Copy, Clone)]
- pub struct EekGtkKeyboard(*const c_void);
+ pub struct EekGtkKeyboard(pub *const gtk_sys::GtkWidget);
/// Defined in eek-types.h
#[repr(C)]
@@ -245,7 +246,7 @@ pub mod c {
origin_y: f64,
scale: f64,
}
-
+
impl Transformation {
fn forward(&self, p: Point) -> Point {
Point {
@@ -253,6 +254,25 @@ pub mod c {
y: (p.y - self.origin_y) / self.scale,
}
}
+ fn reverse(&self, p: Point) -> Point {
+ Point {
+ x: p.x * self.scale + self.origin_x,
+ y: p.y * self.scale + self.origin_y,
+ }
+ }
+ pub fn reverse_bounds(&self, b: Bounds) -> Bounds {
+ let start = self.reverse(Point { x: b.x, y: b.y });
+ let end = self.reverse(Point {
+ x: b.x + b.width,
+ y: b.y + b.height,
+ });
+ Bounds {
+ x: start.x,
+ y: start.y,
+ width: end.x - start.x,
+ height: end.y - start.y,
+ }
+ }
}
// This is constructed only in C, no need for warnings
@@ -319,28 +339,30 @@ pub mod c {
}
}
+ /// Release pointer in the specified position
#[no_mangle]
pub extern "C"
fn squeek_layout_release(
layout: *mut Layout,
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
+ widget_to_layout: Transformation,
time: u32,
ui_keyboard: EekGtkKeyboard,
) {
+ let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let key: &Rc> = key.borrow();
- layout.release_key(
+ ui::release_key(
+ layout,
&virtual_keyboard,
- &mut key.clone(),
- Timestamp(time)
- );
- let view = layout.get_current_view();
- ::layout::procedures::release_ui_buttons(
- &view, key, ui_keyboard,
+ &widget_to_layout,
+ time,
+ ui_keyboard,
+ key
);
}
}
@@ -418,6 +440,7 @@ pub mod c {
time: u32,
ui_keyboard: EekGtkKeyboard,
) {
+ let time = Timestamp(time);
let layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
@@ -442,36 +465,30 @@ pub mod c {
if Rc::ptr_eq(&state, &wrapped_key.0) {
found = true;
} else {
- layout.release_key(
+ ui::release_key(
+ layout,
&virtual_keyboard,
- &mut key.clone(),
- Timestamp(time),
- );
- let view = layout.get_current_view();
- ::layout::procedures::release_ui_buttons(
- &view, key, ui_keyboard,
+ &widget_to_layout,
+ time,
+ ui_keyboard,
+ key,
);
}
}
if !found {
- layout.press_key(
- &virtual_keyboard,
- &mut state,
- Timestamp(time),
- );
+ layout.press_key(&virtual_keyboard, &mut state, time);
unsafe { eek_gtk_on_button_pressed(c_place, ui_keyboard) };
}
} else {
for wrapped_key in pressed {
let key: &Rc> = wrapped_key.borrow();
- layout.release_key(
+ ui::release_key(
+ layout,
&virtual_keyboard,
- &mut key.clone(),
- Timestamp(time),
- );
- let view = layout.get_current_view();
- ::layout::procedures::release_ui_buttons(
- &view, key, ui_keyboard,
+ &widget_to_layout,
+ time,
+ ui_keyboard,
+ key,
);
}
}
@@ -503,6 +520,28 @@ 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 {
+ origin_x: 10f64,
+ origin_y: 11f64,
+ scale: 12f64,
+ };
+ let point = Point { x: 1f64, y: 1f64 };
+ let transformed = transform.reverse(transform.forward(point.clone()));
+ assert!(near(point.x, transformed.x));
+ assert!(near(point.y, transformed.y));
+ }
+ }
}
}
@@ -717,6 +756,12 @@ pub struct Layout {
pub keymap_str: CString,
// Changeable state
// a Vec would be enough, but who cares, this will be small & fast enough
+ // TODO: turn those into per-input point *_buttons to track dragging.
+ // The renderer doesn't need the list of pressed keys any more,
+ // because it needs to iterate
+ // through all buttons of the current view anyway.
+ // When the list tracks actual location,
+ // it becomes possible to place popovers and other UI accurately.
pub pressed_keys: HashSet<::util::Pointer>>,
pub locked_keys: HashSet<::util::Pointer>>,
}
@@ -949,6 +994,64 @@ mod procedures {
);
}
}
+
+ pub fn get_button_bounds(
+ view: &View,
+ row: &Row,
+ button: &Button
+ ) -> Option {
+ match &row.bounds {
+ Some(row) => Some(c::Bounds {
+ x: view.bounds.x + row.x + button.bounds.x,
+ y: view.bounds.y + row.y + button.bounds.y,
+ width: button.bounds.width,
+ height: button.bounds.height,
+ }),
+ _ => None,
+ }
+ }
+}
+
+/// Top level UI procedures
+mod ui {
+ use super::*;
+
+ // TODO: turn into release_button
+ pub fn release_key(
+ layout: &mut Layout,
+ virtual_keyboard: &VirtualKeyboard,
+ widget_to_layout: &c::procedures::Transformation,
+ time: Timestamp,
+ ui_keyboard: c::EekGtkKeyboard,
+ key: &Rc>,
+ ) {
+ layout.release_key(virtual_keyboard, &mut key.clone(), time);
+
+ let view = layout.get_current_view();
+ let action = RefCell::borrow(key).action.clone();
+ if let Action::ShowPreferences = action {
+ let paths = ::layout::procedures::find_key_paths(
+ view, key
+ );
+ // getting first item will cause mispositioning
+ // with more than one button with the same key
+ // on the keyboard
+ if let Some((row, button)) = paths.get(0) {
+ let bounds = ::layout::procedures::get_button_bounds(
+ view, row, button
+ ).unwrap_or_else(|| {
+ eprintln!("BUG: Clicked button has no position?");
+ c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
+ });
+ ::popover::show(
+ ui_keyboard,
+ widget_to_layout.reverse_bounds(bounds)
+ );
+ }
+ }
+
+ procedures::release_ui_buttons(view, key, ui_keyboard);
+ }
}
#[cfg(test)]
diff --git a/src/lib.rs b/src/lib.rs
index d4652349..21bfde8a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,8 +1,14 @@
#[macro_use]
extern crate bitflags;
+extern crate gio;
+extern crate glib;
+extern crate glib_sys;
+extern crate gtk;
+extern crate gtk_sys;
#[allow(unused_imports)]
#[macro_use] // only for tests
extern crate maplit;
+extern crate regex;
extern crate serde;
extern crate xkbcommon;
@@ -12,7 +18,10 @@ pub mod float_ord;
pub mod imservice;
mod keyboard;
mod layout;
+mod locale;
+mod locale_config;
mod outputs;
+mod popover;
mod resources;
mod submission;
mod util;
diff --git a/src/locale.rs b/src/locale.rs
new file mode 100644
index 00000000..8333a24d
--- /dev/null
+++ b/src/locale.rs
@@ -0,0 +1,39 @@
+/*! Locale-specific functions */
+
+use std::cmp;
+use std::ffi::CString;
+
+mod c {
+ use std::os::raw::c_char;
+
+ #[allow(non_camel_case_types)]
+ pub type c_int = i32;
+
+ #[no_mangle]
+ extern "C" {
+ // from libc
+ pub fn strcoll(cs: *const c_char, ct: *const c_char) -> c_int;
+ }
+}
+
+fn cstring_safe(s: &str) -> CString {
+ CString::new(s)
+ .unwrap_or(CString::new("").unwrap())
+}
+
+pub fn compare_current_locale(a: &str, b: &str) -> cmp::Ordering {
+ let a = cstring_safe(a);
+ let b = cstring_safe(b);
+ let a = a.as_ptr();
+ let b = b.as_ptr();
+ let result = unsafe { c::strcoll(a, b) };
+ if result == 0 {
+ cmp::Ordering::Equal
+ } else if result > 0 {
+ cmp::Ordering::Greater
+ } else if result < 0 {
+ cmp::Ordering::Less
+ } else {
+ unreachable!()
+ }
+}
diff --git a/src/locale_config.rs b/src/locale_config.rs
new file mode 100644
index 00000000..0595f2b7
--- /dev/null
+++ b/src/locale_config.rs
@@ -0,0 +1,541 @@
+/*! 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) 2016–2019 Jan Hudec
+ Copyright (c) 2016 A.J. Gardner
+ Copyright (c) 2019, Bastien Orivel
+ Copyright (c) 2019, Igor Gnatenko
+ 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 {
+ use ::std::error::Error;
+ out.write_str(self.description())
+ }
+}
+
+impl ::std::error::Error for Error {
+ fn description(&self) -> &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 = ::std::result::Result;
+
+/// 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 {
+ 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 1–12, `h23` for 0–23).
+/// * `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> {
+ let unix_tag_regex = Regex::new(r"(?ix) ^
+ (?P [[:alpha:]]{2,3} )
+ (?: _ (?P [[:alpha:]]{2} | [[:digit:]]{3} ))?
+ (?: \. (?P [0-9a-zA-Z-]{1,20} ))?
+ (?: @ (?P [[: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 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 for Locale {
+ fn as_ref(&self) -> &str {
+ self.inner.as_ref()
+ }
+}
+
+impl<'a> From> for Locale {
+ fn from(t: LanguageRange<'a>) -> Locale {
+ Locale {
+ inner: t.language.into_owned(),
+ }
+ }
+}
+
+fn tag(s: &str) -> Result {
+ 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 {
+ // 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());
+ }
+}
diff --git a/src/popover.h b/src/popover.h
new file mode 100644
index 00000000..1b164e5a
--- /dev/null
+++ b/src/popover.h
@@ -0,0 +1,9 @@
+#ifndef POPOVER_H__
+#define POPOVER_H__
+
+#include
+#include "eek/eek-keyboard.h"
+
+void squeek_popover_show(GtkWidget*, struct button_place);
+
+#endif
diff --git a/src/popover.rs b/src/popover.rs
new file mode 100644
index 00000000..c9b60fb7
--- /dev/null
+++ b/src/popover.rs
@@ -0,0 +1,190 @@
+/*! The layout chooser popover */
+
+use gio;
+use gtk;
+use ::layout::c::EekGtkKeyboard;
+use ::locale::compare_current_locale;
+use ::locale_config::system_locale;
+use ::resources;
+
+use gio::ActionExt;
+use gio::ActionMapExt;
+use gio::SettingsExt;
+use glib::translate::FromGlibPtrNone;
+use glib::variant::ToVariant;
+use gtk::PopoverExt;
+use gtk::WidgetExt;
+use std::io::Write;
+
+mod variants {
+ use glib;
+ use glib_sys;
+
+ use glib::translate::FromGlibPtrFull;
+ use glib::translate::ToGlibPtr;
+
+ /// Unpacks tuple & array variants
+ fn get_items(items: glib::Variant) -> Vec {
+ let variant_naked = items.to_glib_none().0;
+ let count = unsafe { glib_sys::g_variant_n_children(variant_naked) };
+ (0..count).map(|index|
+ unsafe {
+ glib::Variant::from_glib_full(
+ glib_sys::g_variant_get_child_value(variant_naked, index)
+ )
+ }
+ ).collect()
+ }
+
+ /// Unpacks "a(ss)" variants
+ pub fn get_tuples(items: glib::Variant) -> Vec<(String, String)> {
+ get_items(items)
+ .into_iter()
+ .map(get_items)
+ .map(|v| {
+ (
+ v[0].get::().unwrap(),
+ v[1].get::().unwrap(),
+ )
+ })
+ .collect()
+ }
+}
+
+fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
+ let mut xml: Vec = Vec::new();
+ writeln!(
+ xml,
+ "
+
+
+"
+ ).unwrap();
+ gtk::Builder::new_from_string(
+ &String::from_utf8(xml).expect("Bad menu definition")
+ )
+}
+
+fn set_layout(kind: String, name: String) {
+ let settings = gio::Settings::new("org.gnome.desktop.input-sources");
+ let inputs = settings.get_value("sources").unwrap();
+ let inputs = variants::get_tuples(inputs).into_iter();
+ for (index, (ikind, iname)) in inputs.enumerate() {
+ if (&ikind, &iname) == (&kind, &name) {
+ settings.set_uint("current", index as u32);
+ }
+ }
+ settings.apply();
+}
+
+pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
+ unsafe { gtk::set_initialized() };
+ let window = unsafe { gtk::Widget::from_glib_none(window.0) };
+
+ let settings = gio::Settings::new("org.gnome.desktop.input-sources");
+ let inputs = settings.get_value("sources").unwrap();
+ let current = settings.get_uint("current") as usize;
+ let inputs = variants::get_tuples(inputs);
+
+ let input_names: Vec<&str> = inputs.iter()
+ .map(|(_kind, name)| name.as_str())
+ .collect();
+
+ let translations = system_locale()
+ .map(|locale|
+ locale.tags_for("messages")
+ .next().unwrap() // guaranteed to exist
+ .as_ref()
+ .to_owned()
+ )
+ .and_then(|lang| resources::get_layout_names(lang.as_str()));
+
+ // sorted collection of human and machine names
+ let mut human_names: Vec<(&str, &str)> = match translations {
+ Some(translations) => {
+ input_names.iter()
+ .map(|name| (*name, *translations.get(name).unwrap_or(name)))
+ .collect()
+ },
+ // display bare codes
+ None => {
+ input_names.iter()
+ .map(|n| (*n, *n)) // turns &&str into &str
+ .collect()
+ }
+ };
+
+ human_names.sort_unstable_by(|(_, human_label_a), (_, human_label_b)| {
+ compare_current_locale(human_label_a, human_label_b)
+ });
+
+ let builder = make_menu_builder(human_names);
+ // 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(>k::Rectangle {
+ x: position.x.ceil() as i32,
+ y: position.y.ceil() as i32,
+ width: position.width.floor() as i32,
+ height: position.width.floor() as i32,
+ });
+
+ let initial_state = input_names[current].to_variant();
+
+ let layout_action = gio::SimpleAction::new_stateful(
+ "layout",
+ Some(initial_state.type_()),
+ &initial_state,
+ );
+
+ let action_group = gio::SimpleActionGroup::new();
+ action_group.add_action(&layout_action);
+
+ menu.insert_action_group("popup", Some(&action_group));
+ menu.bind_model(Some(&model), Some("popup"));
+
+ menu.connect_closed(move |_menu| {
+ let state = match layout_action.get_state() {
+ Some(v) => {
+ let s = v.get::().or_else(|| {
+ eprintln!("Variant is not string: {:?}", v);
+ None
+ });
+ // FIXME: the `get_state` docs call for unrefing,
+ // but the function is nowhere to be found
+ // glib::Variant::unref(v);
+ s
+ },
+ None => {
+ eprintln!("No variant selected");
+ None
+ },
+ };
+ set_layout("xkb".into(), state.unwrap_or("us".into()));
+ });
+
+ menu.popup();
+}
diff --git a/src/resources.rs b/src/resources.rs
index c5840873..4255753d 100644
--- a/src/resources.rs
+++ b/src/resources.rs
@@ -2,6 +2,13 @@
* This could be done using GResource, but that would need additional work.
*/
+use std::collections::HashMap;
+
+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"
const KEYBOARDS: &[(*const str, *const str)] = &[
("us", include_str!("../data/keyboards/us.yaml")),
("us_wide", include_str!("../data/keyboards/us_wide.yaml")),
@@ -28,3 +35,76 @@ pub fn get_keyboard(needle: &str) -> Option<&'static str> {
unsafe { &*value }
})
}
+
+/// Translations of the layout identifier strings
+const LAYOUT_NAMES: &[(*const str, *const str)] = &[
+ ("en-US", include_str!("../data/langs/en-US.txt")),
+ ("pl-PL", include_str!("../data/langs/pl-PL.txt")),
+];
+
+pub fn get_layout_names(lang: &str)
+ -> Option>
+{
+ let translations = LAYOUT_NAMES.iter()
+ .find(|(name, _data)| {
+ let name: *const str = *name;
+ (unsafe { &*name }) == lang
+ })
+ .map(|(_name, data)| {
+ let data: *const str = *data;
+ unsafe { &*data }
+ });
+ translations.map(make_mapping)
+}
+
+fn parse_line(line: &str) -> Option<(&str, &str)> {
+ 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, tr.trim()))
+ }
+}
+
+fn make_mapping(data: &str) -> HashMap<&str, &str> {
+ HashMap::from_iter(
+ data.split("\n")
+ .filter_map(parse_line)
+ )
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn mapping_line() {
+ assert_eq!(
+ Some(("name", "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"));
+ }
+}
diff --git a/src/submission.rs b/src/submission.rs
index 17b3bfa9..476605a8 100644
--- a/src/submission.rs
+++ b/src/submission.rs
@@ -23,6 +23,7 @@ pub mod c {
}
}
+#[derive(Clone, Copy)]
pub struct Timestamp(pub u32);
/// Layout-independent backend. TODO: Have one instance per program or seat