Compare commits

..

1 Commits

45 changed files with 613 additions and 1045 deletions

20
Cargo.lock generated
View File

@ -54,6 +54,15 @@ name = "cc"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "dbus"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libdbus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.4.4"
@ -232,6 +241,14 @@ name = "libc"
version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libdbus-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linked-hash-map"
version = "0.5.2"
@ -324,6 +341,7 @@ name = "rs"
version = "0.1.0"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"dbus 0.6.5 (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)",
@ -436,6 +454,7 @@ dependencies = [
"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 dbus 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819"
"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"
@ -451,6 +470,7 @@ dependencies = [
"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 libdbus-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
"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"

View File

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

View File

@ -30,7 +30,7 @@ Most common testing is done in CI. Occasionally, and for each release, do perfor
- the application draws correctly
- it shows when relevant
- it changes layouts
- it changes views
- it changes levels
Testing with an application:
@ -50,8 +50,10 @@ Testing layouts:
Layouts can be selected using the GNOME Settings application.
```
# define all available layouts. First one is currently selected.
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'de')]"
# define all available layouts
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'ua')]"
# choose the active layout
$ gsettings set org.gnome.desktop.input-sources current 1
```
Coding
@ -112,7 +114,7 @@ Use the `cargo.sh` script for maintaining the Cargo part of the build. The scrip
```
cd build_dir
sh /source_path/cargo.sh test
sh /source_path/cargo.sh '' test
```
### Cargo dependencies
@ -123,6 +125,6 @@ Dependencies must be specified in `Cargo.toml` with 2 numbers: "major.minor". Si
```
cd build_dir
sh /source_path/cargo.sh update
sh /source_path/cargo.sh '' update
ninja test
```

View File

@ -11,7 +11,14 @@ SOURCE_DIR="$(dirname "$SCRIPT_PATH")"
CARGO_TARGET_DIR="$(pwd)"
export CARGO_TARGET_DIR
if [ -n "${1}" ]; then
OUT_PATH="$(realpath "$1")"
fi
cd "$SOURCE_DIR"
shift
cargo "$@"
if [ -n "${OUT_PATH}" ]; then
cp "${CARGO_TARGET_DIR}"/debug/librs.a "${OUT_PATH}"
fi

View File

@ -1,34 +0,0 @@
#!/bin/sh
# This script manages Cargo builds
# while keeping the artifact directory within the build tree
# instead of the source tree
set -e
SCRIPT_PATH="$(realpath "$0")"
SOURCE_DIR="$(dirname "$SCRIPT_PATH")"
RELEASE=""
BINARY_DIR="debug"
if [ "${1}" = "--release" ]; then
shift
BINARY_DIR="release"
RELEASE="--release"
fi
if [ "${1}" = "--rename" ]; then
shift
FILENAME="${1}"
shift
fi
OUT_PATH="$(realpath "${1}")"
shift
OUT_BASENAME="$(basename "${OUT_PATH}")"
FILENAME="${FILENAME:-"${OUT_BASENAME}"}"
sh "$SOURCE_DIR"/cargo.sh build $RELEASE "$@"
if [ -n "${OUT_PATH}" ]; then
cp -a ./"${BINARY_DIR}"/"${FILENAME}" "${OUT_PATH}"
fi

View File

@ -1,4 +1,5 @@
# Maintained by: Mark Müller <markmueller86@gmail.com>
# German layout by Mark Müller
# Version 2019111700
---
bounds: { x: 0, y: 1, width: 360, height: 208 }
@ -82,7 +83,7 @@ buttons:
space:
outline: "spaceline"
label: " "
text: " "
keysym: "space"
Return:
outline: "altline"
icon: "key-enter"

View File

@ -1,88 +0,0 @@
# Maintained by: Mark Müller <markmueller86@gmail.com>
---
bounds: { x: 0, y: 1, width: 540, height: 168 }
outlines:
default:
bounds: { x: 0, y: 0, width: 48, height: 42 }
altline:
bounds: { x: 0, y: 0, width: 81, height: 42 }
wide:
bounds: { x: 0, y: 0, width: 108, height: 42 }
spaceline:
bounds: { x: 0, y: 0, width: 216, height: 42 }
special:
bounds: { x: 0, y: 0, 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 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"
eschars:
- "ä è é ö ü Ä È É Ö Ü"
- "à â ê î ô À Â È Î Ô"
- "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"
keysym: "BackSpace"
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: "äÄ"
space:
outline: "spaceline"
text: " "
Return:
outline: "altline"
icon: "key-enter"
keysym: "Return"

View File

@ -53,7 +53,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "altline"
@ -87,112 +86,111 @@ buttons:
label: "αι"
period:
outline: "altline"
text: "."
label: "."
space:
outline: spaceline
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
aring:
text: "å"
label: "å"
Aring:
text: "Å"
label: "Å"
oslash:
text: "ø"
label: "ø"
Oslash:
text: "Ø"
label: "Ø"
ae:
text: "æ"
label: "æ"
AE:
text: "Æ"
label: "Æ"
asterisk:
text: "*"
label: "*"
asciitilde:
text: "~"
label: "~"
quoteleft:
text: "`"
label: "`"
bar:
text: "|"
label: "|"
U00B7:
text: "·"
label: "·"
squareroot:
text: "√"
label: "√"
Greek_pi:
text: "π"
label: "π"
division:
text: "÷"
label: "÷"
multiply:
text: "×"
label: "×"
paragraph:
text: "¶"
label: "¶"
Greek_tau:
text: "τ"
label: "τ"
copyright:
text: "©"
label: "©"
numbersign:
text: "#"
label: "#"
U00AE:
text: "®"
label: "®"
at:
text: "@"
label: "@"
dollar:
text: "$"
label: "$"
U00A3:
text: "£"
label: "£"
percent:
text: "%"
label: "%"
EuroSign:
text: "€"
label: "€"
ampersand:
text: "&"
label: "&"
U00A5:
text: "¥"
label: "¥"
minus:
text: "-"
label: "-"
asciicircum:
text: "^"
label: "^"
underscore:
text: "_"
label: "_"
degree:
text: "°"
label: "°"
plus:
text: "+"
label: "+"
equal:
text: "="
label: "="
parenleft:
text: "("
label: "("
parenright:
text: ")"
label: ")"
braceleft:
text: "{"
label: "{"
braceright:
text: "}"
label: "}"
comma:
text: ","
label: ","
backslash:
text: "\\"
label: "\\"
slash:
text: "/"
label: "/"
quotedbl:
text: "\""
label: "\""
quoteright:
text: "'"
label: "'"
less:
text: "<"
label: "<"
greater:
text: ">"
label: ">"
colon:
text: ":"
label: ":"
semicolon:
text: ";"
label: ";"
exclam:
text: "!"
label: "!"
question:
text: "?"
label: "?"
bracketleft:
text: "["
label: "["
bracketright:
text: "]"
label: "]"

View File

@ -51,7 +51,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "default"
@ -81,14 +80,14 @@ buttons:
period:
outline: "default"
text: "."
label: "."
space:
outline: "spaceline"
text: " "
label: " "
Return:
outline: "altline"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
label: ":"
"\"":
keysym: "quotedbl"

View File

@ -46,7 +46,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "altline"
@ -70,109 +69,108 @@ buttons:
outline: altline
space:
outline: spaceline
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
aring:
text: "å"
label: "å"
Aring:
text: "Å"
label: "Å"
ouml:
text: "ö"
label: "ö"
Ouml:
text: "Ö"
label: "Ö"
auml:
text: "ä"
label: "ä"
Auml:
text: "Ä"
label: "Ä"
asterisk:
text: "*"
label: "*"
asciitilde:
text: "~"
label: "~"
quoteleft:
text: "`"
label: "`"
bar:
text: "|"
label: "|"
U00B7:
text: "·"
label: "·"
squareroot:
text: "√"
label: "√"
Greek_pi:
text: "π"
label: "π"
division:
text: "÷"
label: "÷"
multiply:
text: "×"
label: "×"
paragraph:
text: "¶"
label: "¶"
Greek_tau:
text: "τ"
label: "τ"
copyright:
text: "©"
label: "©"
numbersign:
text: "#"
label: "#"
U00AE:
text: "®"
label: "®"
at:
text: "@"
label: "@"
dollar:
text: "$"
label: "$"
U00A3:
text: "£"
label: "£"
percent:
text: "%"
label: "%"
EuroSign:
text: "€"
label: "€"
ampersand:
text: "&"
label: "&"
U00A5:
text: "¥"
label: "¥"
minus:
text: "-"
label: "-"
asciicircum:
text: "^"
label: "^"
underscore:
text: "_"
label: "_"
degree:
text: "°"
label: "°"
plus:
text: "+"
label: "+"
equal:
text: "="
label: "="
parenleft:
text: "("
label: "("
parenright:
text: ")"
label: ")"
braceleft:
text: "{"
label: "{"
braceright:
text: "}"
label: "}"
comma:
text: ","
label: ","
backslash:
text: "\\"
label: "\\"
slash:
text: "/"
label: "/"
quotedbl:
text: "\""
label: "\""
quoteright:
text: "'"
label: "'"
less:
text: "<"
label: "<"
greater:
text: ">"
label: ">"
colon:
text: ":"
label: ":"
semicolon:
text: ";"
label: ";"
exclam:
text: "!"
label: "!"
question:
text: "?"
label: "?"
bracketleft:
text: "["
label: "["
bracketright:
text: "]"
label: "]"

View File

@ -53,7 +53,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "default"
@ -92,7 +91,6 @@ buttons:
Return:
outline: "altline"
icon: "key-enter"
keysym: "Return"
colon:
label: ":"
"\"":

View File

@ -1,4 +1,5 @@
# Maintained by: Mark Müller <markmueller86@gmail.com>
# Japanese Kana layout by Mark Müller
# Version 2019111800
---
bounds: { x: 0, y: 1, width: 360, height: 208 }
@ -220,11 +221,11 @@ buttons:
action: "show_prefs"
outline: "special"
icon: "keyboard-mode-symbolic"
# space button using text tag for ideographic space
# space button with unicode keysym for ideographic space
space:
outline: "default-wide"
label: "␣"
text: " "
keysym: "U3000"
# switch to number view
numbers:
action:

View File

@ -26,13 +26,13 @@ views:
- "show_numbers preferences space . Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # $ % & - _ + ( )"
- "show_symbols , \" ' : ; ! ? BackSpace"
- "at numbersign dollar percent ampersand minus underscore plus parenleft parenright"
- "show_symbols comma quotedbl quoteright colon semicolon exclam question BackSpace"
- "show_letters preferences space . Return"
symbols:
- "~ ` | U00B7 squareroot Greek_pi Greek_tau division multiply paragraph"
- "copyright U00AE U00A3 EuroSign U00A5 asciicircum degree * { }"
- "show_numbers \\ / < > = [ ] BackSpace"
- "asciitilde quoteleft bar U00B7 squareroot Greek_pi Greek_tau division multiply paragraph"
- "copyright U00AE U00A3 EuroSign U00A5 asciicircum degree asterisk braceleft braceright"
- "show_numbers backslash slash less greater equal bracketleft bracketright BackSpace"
- "show_letters preferences space . Return"
buttons:
@ -46,7 +46,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "altline"
@ -70,37 +69,108 @@ buttons:
outline: altline
space:
outline: spaceline
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
aring:
label: "å"
Aring:
label: "Å"
oslash:
label: "ø"
Oslash:
label: "Ø"
ae:
label: "æ"
AE:
label: "Æ"
asterisk:
label: "*"
asciitilde:
label: "~"
quoteleft:
label: "`"
bar:
label: "|"
U00B7:
text: "·"
label: "·"
squareroot:
text: "√"
label: "√"
Greek_pi:
text: "π"
label: "π"
division:
text: "÷"
label: "÷"
multiply:
text: "×"
label: "×"
paragraph:
text: "¶"
label: "¶"
Greek_tau:
text: "τ"
label: "τ"
copyright:
text: "©"
label: "©"
numbersign:
label: "#"
U00AE:
text: "®"
label: "®"
at:
label: "@"
dollar:
label: "$"
U00A3:
text: "£"
label: "£"
percent:
label: "%"
EuroSign:
text: "€"
label: "€"
ampersand:
label: "&"
U00A5:
text: "¥"
label: "¥"
minus:
label: "-"
asciicircum:
text: "^"
label: "^"
underscore:
label: "_"
degree:
text: "°"
label: "°"
plus:
label: "+"
equal:
label: "="
parenleft:
label: "("
parenright:
label: ")"
braceleft:
label: "{"
braceright:
label: "}"
comma:
label: ","
backslash:
label: "\\"
slash:
label: "/"
quotedbl:
label: "\""
quoteright:
label: "'"
less:
label: "<"
greater:
label: ">"
colon:
label: ":"
semicolon:
label: ";"
exclam:
label: "!"
question:
label: "?"
bracketleft:
label: "["
bracketright:
label: "]"

View File

@ -22,24 +22,22 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
space:
outline: spaceline
text: " "
label: " "
Return:
outline: outline7
icon: "key-enter"
keysym: "BackSpace"
asterisk:
text: "*"
label: "*"
numbersign:
text: "#"
label: "#"
minus:
text: "-"
label: "-"
plus:
text: "+"
label: "+"
parenleft:
text: "("
label: "("
parenright:
text: ")"
label: ")"

View File

@ -46,7 +46,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "altline"
@ -70,97 +69,96 @@ buttons:
outline: altline
space:
outline: spaceline
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
asterisk:
text: "*"
label: "*"
asciitilde:
text: "~"
label: "~"
quoteleft:
text: "`"
label: "`"
bar:
text: "|"
label: "|"
U00B7:
text: "·"
label: "·"
squareroot:
text: "√"
label: "√"
Greek_pi:
text: "π"
label: "π"
division:
text: "÷"
label: "÷"
multiply:
text: "×"
label: "×"
paragraph:
text: "¶"
label: "¶"
Greek_tau:
text: "τ"
label: "τ"
copyright:
text: "©"
label: "©"
numbersign:
text: "#"
label: "#"
U00AE:
text: "®"
label: "®"
at:
text: "@"
label: "@"
dollar:
text: "$"
label: "$"
U00A3:
text: "£"
label: "£"
percent:
text: "%"
label: "%"
EuroSign:
text: "€"
label: "€"
ampersand:
text: "&"
label: "&"
U00A5:
text: "¥"
label: "¥"
minus:
text: "-"
label: "-"
asciicircum:
text: "^"
label: "^"
underscore:
text: "_"
label: "_"
degree:
text: "°"
label: "°"
plus:
text: "+"
label: "+"
equal:
text: "="
label: "="
parenleft:
text: "("
label: "("
parenright:
text: ")"
label: ")"
braceleft:
text: "{"
label: "{"
braceright:
text: "}"
label: "}"
comma:
text: ","
label: ","
backslash:
text: "\\"
label: "\\"
slash:
text: "/"
label: "/"
quotedbl:
text: "\""
label: "\""
quoteright:
text: "'"
label: "'"
less:
text: "<"
label: "<"
greater:
text: ">"
label: ">"
colon:
text: ":"
label: ":"
semicolon:
text: ";"
label: ";"
exclam:
text: "!"
label: "!"
question:
text: "?"
label: "?"
bracketleft:
text: "["
label: "["
bracketright:
text: "]"
label: "]"

View File

@ -46,7 +46,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "special"
@ -73,13 +72,14 @@ buttons:
label: "*/="
period:
outline: "special"
text: "."
label: "."
space:
outline: "spaceline"
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
label: ":"
"\"":
keysym: "quotedbl"

View File

@ -18,22 +18,22 @@ views:
- "q w e r t y u i o p"
- "a s d f g h j k l"
- "Shift_L z x c v b n m BackSpace"
- "show_numbers preferences space . Return"
- "show_numbers preferences space 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 . Return"
- "show_numbers preferences space period Return"
numbers:
- "1 2 3 4 5 6 7 8 9 0"
- "@ # $ % & - _ + ( )"
- "show_symbols , \" ' colon ; ! ? BackSpace"
- "show_letters preferences space . Return"
- "show_letters preferences space period Return"
symbols:
- "~ ` | · √ π τ ÷ × ¶"
- "© ® £ € ¥ ^ ° * { }"
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
- "show_letters preferences space . Return"
- "show_letters preferences space period Return"
buttons:
Shift_L:
@ -46,7 +46,6 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
preferences:
action: "show_prefs"
outline: "special"
@ -71,15 +70,16 @@ buttons:
set_view: "symbols"
outline: "altline"
label: "*/="
".":
period:
outline: "special"
text: "."
label: "."
space:
outline: "spaceline"
text: " "
label: " "
Return:
outline: "wide"
icon: "key-enter"
keysym: "Return"
colon:
text: ":"
label: ":"
"\"":
keysym: "quotedbl"

View File

@ -1,8 +0,0 @@
us 英語 (US)
de ドイツ語
el ギリシャ語
es スペイン語
it イタリア語
jp+kana 日本語 (かな)
nb ノルウェー語

View File

@ -2,7 +2,6 @@
<gresources>
<gresource prefix="/sm/puri/squeekboard">
<file compressed="true">style.css</file>
<file compressed="true">style-Adwaita:dark.css</file>
<file compressed="true" preprocess="xml-stripblanks">popup.ui</file>
<file>icons/key-enter.svg</file>
<file>icons/key-shift.svg</file>

View File

@ -1,46 +0,0 @@
sq_view {
background-color: rgba(0, 0, 0, 255);
color: #ffffff;
font-family: cantarell, sans-serif;
}
sq_view sq_button {
color: #deddda;
background: #464448;
border-style: solid;
border-width: 1px;
border-color: #5e5c64;
border-radius: 3px;
margin: 4px 2px 4px 2px;
}
sq_view.wide sq_button {
margin: 1px 1px 1px 1px;
}
sq_button:active {
background: #747077;
border-color: #96949d;
}
sq_button.altline,
sq_button.special,
sq_button.wide {
background: #2b292f;
border-color: #3e3a44;
}
sq_button.locked {
background: #ffffff;
color: #2b292f;
}
#Return {
background: #1c71d8;
border-color: #1a5fb4;
}
#Return:active {
background: #1c71d8;
border-color: #3584e4;
}

View File

@ -1,15 +1,15 @@
sq_view {
background-color: @theme_base_color; /*rgba(0, 0, 0, 255);*/
color: @theme_text_color; /*#ffffff;*/
background-color: rgba(0, 0, 0, 255);
color: #ffffff;
font-family: cantarell, sans-serif;
}
sq_view sq_button {
color: @theme_fg_color; /*#deddda;*/
background: mix(@theme_bg_color, @theme_base_color, -0.5); /* #464448; */
color: #deddda;
background: #464448;
border-style: solid;
border-width: 1px;
border-color: @borders; /* #5e5c64;*/
border-color: #5e5c64;
border-radius: 3px;
margin: 4px 2px 4px 2px;
}
@ -18,32 +18,29 @@ sq_view.wide sq_button {
margin: 1px 1px 1px 1px;
}
sq_button:active,
sq_button.altline:active,
sq_button.special:active,
sq_button.wide:active {
background: mix(@theme_bg_color, @theme_selected_bg_color, 0.4);/* #747077; */
border-color: mix(@borders, @theme_selected_fg_color, 0.5);/* #96949d; */
sq_button:active {
background: #747077;
border-color: #96949d;
}
sq_button.altline,
sq_button.special,
sq_button.wide {
background: mix(@theme_bg_color, @theme_base_color, 0.5); /*#2b292f;*/
border-color: @borders; /* #3e3a44; */
background: #2b292f;
border-color: #3e3a44;
}
sq_button.locked {
background: @theme_fg_color; /*#ffffff;*/
color: @theme_bg_color; /*#2b292f;*/
background: #ffffff;
color: #2b292f;
}
#Return {
background: @theme_selected_bg_color; /* #1c71d8; */
border-color: @borders; /*#1a5fb4;*/
background: #1c71d8;
border-color: #1a5fb4;
}
#Return:active {
background: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#1c71d8;*/
border-color: @borders; /*#3584e4;*/
background: #1c71d8;
border-color: #3584e4;
}

20
debian/changelog vendored
View File

@ -1,23 +1,3 @@
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

12
debian/control vendored
View File

@ -12,7 +12,7 @@ Build-Depends:
libgtk-3-dev,
libcroco3-dev,
librust-bitflags-1-dev (>= 1.0),
librust-clap-2+default-dev (>= 2.32),
librust-dbus-0.6-dev (>= 0.6),
librust-gio+v2-44-dev,
librust-glib+v2-44-dev,
librust-glib-sys-dev,
@ -41,13 +41,3 @@ Depends:
${misc:Depends}
Description: On-screen keyboard for Wayland
Virtual keyboard supporting Wayland, built primarily for the Librem 5 phone.
Package: squeekboard-devel
Architecture: linux-any
Depends:
${shlibs:Depends}
${misc:Depends}
Description: Resources for making Squeekboard layouts
Tools for creating Squeekboard layouts:
.
* squeekboard-test-layout

View File

@ -1 +0,0 @@
usr/bin/squeekboard-test-layout /usr/bin

View File

@ -1,2 +0,0 @@
tools/squeekboard-restyled usr/bin
usr/bin/squeekboard /usr/bin

View File

@ -1,2 +1,2 @@
# yaml-rust 0.4.3 shares some roots with libyaml, including the string which lintian checks, creating a false positive
squeekboard binary: embedded-library usr/bin/squeekboard: libyaml
squeekboard binary: embedded-library usr/bin/squeekboard-real: libyaml

View File

@ -158,8 +158,6 @@ eek_gtk_keyboard_real_button_press_event (GtkWidget *self,
return TRUE;
}
// TODO: this belongs more in gtk_keyboard, with a way to find out which key to re-render
static gboolean
eek_gtk_keyboard_real_button_release_event (GtkWidget *self,
@ -172,18 +170,6 @@ eek_gtk_keyboard_real_button_release_event (GtkWidget *self,
return TRUE;
}
static gboolean
eek_gtk_keyboard_leave_event (GtkWidget *self,
GdkEventCrossing *event)
{
if (event->type == GDK_LEAVE_NOTIFY) {
// TODO: can the event have different coords than the previous move event?
release(EEK_GTK_KEYBOARD(self), event->time);
}
return TRUE;
}
static gboolean
eek_gtk_keyboard_real_motion_notify_event (GtkWidget *self,
GdkEventMotion *event)
@ -293,9 +279,6 @@ eek_gtk_keyboard_class_init (EekGtkKeyboardClass *klass)
eek_gtk_keyboard_real_button_release_event;
widget_class->motion_notify_event =
eek_gtk_keyboard_real_motion_notify_event;
widget_class->leave_notify_event =
eek_gtk_keyboard_leave_event;
widget_class->touch_event = handle_touch_event;
gobject_class->set_property = eek_gtk_keyboard_set_property;

View File

@ -26,7 +26,6 @@
#include "eek-keyboard.h"
#include "eek-renderer.h"
#include "src/style.h"
enum {
PROP_0,
@ -624,7 +623,10 @@ eek_renderer_init (EekRenderer *self)
gtk_icon_theme_add_resource_path (theme, "/sm/puri/squeekboard/icons");
priv->css_provider = squeek_load_style();
/* Create a default CSS provider and load a style sheet */
priv->css_provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (priv->css_provider,
"/sm/puri/squeekboard/style.css");
}
static void

View File

@ -207,8 +207,22 @@ static void
settings_get_layout(GSettings *settings, char **type, char **layout)
{
GVariant *inputs = g_settings_get_value(settings, "sources");
// current layout is always first
g_variant_get_child(inputs, 0, "(ss)", type, layout);
guint32 index;
g_settings_get(settings, "current", "u", &index);
GVariantIter *iter;
g_variant_get(inputs, "a(ss)", &iter);
for (unsigned i = 0;
g_variant_iter_loop(iter, "(ss)", type, layout);
i++) {
if (i == index) {
//printf("Found layout %s %s\n", *type, *layout);
break;
}
}
g_variant_iter_free(iter);
g_variant_unref(inputs);
}
void

View File

@ -1,10 +1,52 @@
extern crate rs;
extern crate xkbcommon;
use rs::tests::check_builtin_layout;
use std::env;
fn main() -> () {
check_builtin_layout(
env::args().nth(1).expect("No argument given").as_str()
);
use rs::data::{ Layout, LoadError };
use xkbcommon::xkb;
fn check_layout(name: &str) {
let layout = Layout::from_resource(name)
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
.expect("layout broken");
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap_str = layout.keymap_str
.clone()
.into_string().expect("Failed to decode keymap string");
let keymap = xkb::Keymap::new_from_string(
&context,
keymap_str.clone(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).expect("Failed to create keymap");
let state = xkb::State::new(&keymap);
// "Press" each button with keysyms
for view in layout.views.values() {
for row in &view.rows {
for button in &row.buttons {
let keystate = button.state.borrow();
for keycode in &keystate.keycodes {
match state.key_get_one_sym(*keycode) {
xkb::KEY_NoSymbol => {
eprintln!("{}", keymap_str);
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
},
_ => {},
}
}
}
}
}
}
fn main() -> () {
check_layout(env::args().nth(1).expect("No argument given").as_str());
}

View File

@ -1,7 +1,7 @@
project(
'squeekboard',
'c', 'rust',
version: '1.4.0',
version: '1.3.0',
license: 'GPLv3',
meson_version: '>=0.51.0',
default_options: [
@ -33,11 +33,6 @@ endif
if get_option('buildtype') != 'plain'
add_project_arguments('-fstack-protector-strong', language: 'c')
endif
if get_option('buildtype') == 'release'
cargo_build_flags = ['--release'] # for artifacts
else
cargo_build_flags = []
endif
prefix = get_option('prefix')
datadir = join_paths(prefix, get_option('datadir'))
@ -59,9 +54,8 @@ summary = [
]
message('\n'.join(summary))
dep_cargo = find_program('cargo')
cargo = find_program('cargo')
cargo_script = find_program('cargo.sh')
cargo_build = find_program('cargo_build.sh')
subdir('data')
subdir('protocols')

View File

@ -1,16 +0,0 @@
#[macro_use]
extern crate clap;
extern crate rs;
use rs::tests::check_layout_file;
fn main() -> () {
let matches = clap_app!(test_layout =>
(name: "squeekboard-test-layout")
(about: "Test keyboard layout for errors. Returns OK or an error message containing further information.")
(@arg INPUT: +required "Yaml keyboard layout file to test")
).get_matches();
if check_layout_file(matches.value_of("INPUT").unwrap()) == () {
println!("Test result: OK");
}
}

View File

@ -26,8 +26,8 @@ use ::xdg;
// traits, derives
use std::io::BufReader;
use std::iter::FromIterator;
use serde::Deserialize;
use util::WarningHandler;
/// Gathers stuff defined in C or called by C
@ -151,30 +151,21 @@ fn list_layout_sources(
ret
}
struct PrintWarnings;
impl WarningHandler for PrintWarnings {
fn handle(&mut self, warning: &str) {
println!("{}", warning);
}
}
fn load_layout_data(source: DataSource)
-> Result<::layout::LayoutData, LoadError>
{
let handler = PrintWarnings{};
match source {
DataSource::File(path) => {
Layout::from_file(path.clone())
.map_err(LoadError::BadData)
.and_then(|layout|
layout.build(handler).0.map_err(LoadError::BadKeyMap)
layout.build().map_err(LoadError::BadKeyMap)
)
},
DataSource::Resource(name) => {
Layout::from_resource(&name)
.and_then(|layout|
layout.build(handler).0.map_err(LoadError::BadKeyMap)
layout.build().map_err(LoadError::BadKeyMap)
)
},
}
@ -234,28 +225,22 @@ struct Bounds {
/// Buttons are embedded in a single string
type ButtonIds = String;
/// All info about a single button
/// Buttons can have multiple instances though.
#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct ButtonMeta {
/// Special action to perform on activation. Conflicts with keysym, text.
/// Action other than keysym (conflicts with keysym)
action: Option<Action>,
/// The name of the XKB keysym to emit on activation.
/// Conflicts with action, text
/// The name of the outline. If not present, will be "default"
outline: Option<String>,
/// FIXME: start using it
keysym: Option<String>,
/// The text to submit on activation. Will be derived from ID if not present
/// Conflicts with action, keysym
text: Option<String>,
/// If not present, will be derived from text or the button ID
/// If not present, will be derived from the button ID
label: Option<String>,
/// Conflicts with label
icon: Option<String>,
/// The name of the outline. If not present, will be "default"
outline: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
enum Action {
#[serde(rename="locking")]
@ -311,7 +296,7 @@ impl Layout {
.map_err(LoadError::BadResource)
}
pub fn from_file(path: PathBuf) -> Result<Layout, Error> {
fn from_file(path: PathBuf) -> Result<Layout, Error> {
let infile = BufReader::new(
fs::OpenOptions::new()
.read(true)
@ -320,8 +305,8 @@ impl Layout {
serde_yaml::from_reader(infile).map_err(Error::Yaml)
}
pub fn build<H: WarningHandler>(self, mut warning_handler: H)
-> (Result<::layout::LayoutData, FormattingError>, H)
pub fn build(self)
-> Result<::layout::LayoutData, FormattingError>
{
let button_names = self.views.values()
.flat_map(|rows| {
@ -338,8 +323,7 @@ impl Layout {
create_action(
&self.buttons,
name,
self.views.keys().collect(),
&mut warning_handler,
self.views.keys().collect()
)
)}).collect();
@ -384,15 +368,13 @@ impl Layout {
)
});
let button_states = HashMap::<String, KeyState>::from_iter(
button_states
);
let button_states
= HashMap::<String, KeyState>::from_iter(
button_states
);
// TODO: generate from symbols
let keymap_str = match generate_keymap(&button_states) {
Err(e) => { return (Err(e), warning_handler) },
Ok(v) => v,
};
let keymap_str = generate_keymap(&button_states)?;
let button_states_cache = hash_map_map(
button_states,
@ -423,8 +405,7 @@ impl Layout {
name,
button_states_cache.get(name.into())
.expect("Button state not created")
.clone(),
&mut warning_handler,
.clone()
))
}).collect(),
})
@ -433,148 +414,116 @@ impl Layout {
)})
);
(
Ok(::layout::LayoutData {
views: views,
keymap_str: {
CString::new(keymap_str)
.expect("Invalid keymap string generated")
},
}),
warning_handler,
)
Ok(::layout::LayoutData {
views: views,
keymap_str: {
CString::new(keymap_str)
.expect("Invalid keymap string generated")
},
})
}
}
fn create_action<H: WarningHandler>(
fn create_action(
button_info: &HashMap<String, ButtonMeta>,
name: &str,
view_names: Vec<&String>,
warning_handler: &mut H,
) -> ::action::Action {
let default_meta = ButtonMeta::default();
let symbol_meta = button_info.get(name)
.unwrap_or(&default_meta);
fn keysym_valid(name: &str) -> bool {
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
}
enum SubmitData {
Action(Action),
Text(String),
Keysym(String),
};
let submission = match (
&symbol_meta.action,
&symbol_meta.keysym,
&symbol_meta.text
) {
(Some(action), None, None) => SubmitData::Action(action.clone()),
(None, Some(keysym), None) => SubmitData::Keysym(keysym.clone()),
(None, None, Some(text)) => SubmitData::Text(text.clone()),
(None, None, None) => SubmitData::Text(name.into()),
_ => {
warning_handler.handle(&format!(
"Button {} has more than one of (action, keysym, text)",
name
));
SubmitData::Text("".into())
},
};
fn filter_view_name<H: WarningHandler>(
fn filter_view_name(
button_name: &str,
view_name: String,
view_names: &Vec<&String>,
warning_handler: &mut H,
view_names: &Vec<&String>
) -> String {
if view_names.contains(&&view_name) {
view_name
} else {
warning_handler.handle(&format!("Button {} switches to missing view {}",
eprintln!(
"Button {} switches to missing view {}",
button_name,
view_name,
));
view_name
);
"base".into()
}
}
match submission {
SubmitData::Action(
Action::SetView(view_name)
) => ::action::Action::SetLevel(
filter_view_name(
name, view_name.clone(), &view_names,
warning_handler,
)
fn keysym_valid(name: &str) -> bool {
xkb::keysym_from_name(name, xkb::KEYSYM_NO_FLAGS) != xkb::KEY_NoSymbol
}
let keysyms = match &symbol_meta.action {
// Non-submit action
Some(_) => Vec::new(),
// Submit action
None => match &symbol_meta.keysym {
// Keysym given explicitly
Some(keysym) => vec!(match keysym_valid(keysym.as_str()) {
true => keysym.clone(),
false => {
eprintln!("Keysym name invalid: {}", keysym);
"space".into() // placeholder
},
}),
// Keysyms left open to derive
// TODO: when button name is meant diretly as xkb keysym name,
// mark it so, e.g. with a "#"
None => match keysym_valid(name) {
// Button name is actually a valid xkb name
true => vec!(String::from(name)),
// Button name is not a valid xkb name,
// so assume it's a literal string to be submitted
false => {
if name.chars().count() == 0 {
// A name read from yaml with no valid Unicode.
// Highly improbable, but let's be safe.
eprintln!("Key {} doesn't have any characters", name);
vec!("space".into()) // placeholder
} else {
name.chars().map(|codepoint| {
let codepoint_string = codepoint.to_string();
match keysym_valid(codepoint_string.as_str()) {
true => codepoint_string,
false => format!("U{:04X}", codepoint as u32),
}
}).collect()
}
},
},
},
};
match &symbol_meta.action {
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
filter_view_name(name, view_name.clone(), &view_names)
),
SubmitData::Action(Action::Locking {
Some(Action::Locking {
lock_view, unlock_view
}) => ::action::Action::LockLevel {
lock: filter_view_name(
name,
lock_view.clone(),
&view_names,
warning_handler,
),
lock: filter_view_name(name, lock_view.clone(), &view_names),
unlock: filter_view_name(
name,
unlock_view.clone(),
&view_names,
warning_handler,
&view_names
),
},
SubmitData::Action(
Action::ShowPrefs
) => ::action::Action::ShowPreferences,
SubmitData::Keysym(keysym) => ::action::Action::Submit {
Some(Action::ShowPrefs) => ::action::Action::ShowPreferences,
None => ::action::Action::Submit {
text: None,
keys: vec!(::action::KeySym(
match keysym_valid(keysym.as_str()) {
true => keysym.clone(),
false => {
warning_handler.handle(&format!(
"Keysym name invalid: {}",
keysym,
));
"space".into() // placeholder
},
}
)),
keys: keysyms.into_iter().map(::action::KeySym).collect(),
},
SubmitData::Text(text) => ::action::Action::Submit {
text: {
CString::new(text.clone())
.map_err(|e| {
warning_handler.handle(&format!(
"Text {} contains problems: {:?}",
text,
e
));
e
}).ok()
},
keys: text.chars().map(|codepoint| {
let codepoint_string = codepoint.to_string();
::action::KeySym(match keysym_valid(codepoint_string.as_str()) {
true => codepoint_string,
false => format!("U{:04X}", codepoint as u32),
})
}).collect(),
}
}
}
/// TODO: Since this will receive user-provided data,
/// all .expect() on them should be turned into soft fails
fn create_button<H: WarningHandler>(
fn create_button(
button_info: &HashMap<String, ButtonMeta>,
outlines: &HashMap<String, Outline>,
name: &str,
state: Rc<RefCell<KeyState>>,
warning_handler: &mut H,
) -> ::layout::Button {
let cname = CString::new(name.clone())
.expect("Bad name");
@ -590,18 +539,6 @@ fn create_button<H: WarningHandler>(
} else if let Some(icon) = &button_meta.icon {
::layout::Label::IconName(CString::new(icon.as_str())
.expect("Bad icon"))
} else if let Some(text) = &button_meta.text {
::layout::Label::Text(
CString::new(text.as_str())
.unwrap_or_else(|e| {
warning_handler.handle(&format!(
"Text {} is invalid: {}",
text,
e,
));
CString::new("").unwrap()
})
)
} else {
::layout::Label::Text(cname.clone())
};
@ -611,7 +548,7 @@ fn create_button<H: WarningHandler>(
if outlines.contains_key(outline) {
outline.clone()
} else {
warning_handler.handle(&format!("Outline named {} does not exist! Using default for button {}", outline, name));
eprintln!("Outline named {} does not exist! Using default for button {}", outline, name);
"default".into()
}
}
@ -621,9 +558,7 @@ fn create_button<H: WarningHandler>(
let outline = outlines.get(&outline_name)
.map(|outline| (*outline).clone())
.unwrap_or_else(|| {
warning_handler.handle(
&format!("No default outline defined! Using 1x1!")
);
eprintln!("No default outline defied Using 1x1!");
Outline {
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
}
@ -650,14 +585,6 @@ mod tests {
use std::error::Error as ErrorTrait;
struct PanicWarn;
impl WarningHandler for PanicWarn {
fn handle(&mut self, warning: &str) {
panic!("{}", warning);
}
}
#[test]
fn test_parse_path() {
assert_eq!(
@ -672,7 +599,6 @@ mod tests {
icon: None,
keysym: None,
action: None,
text: None,
label: Some("test".into()),
outline: None,
}
@ -730,7 +656,7 @@ mod tests {
fn test_layout_punctuation() {
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
.unwrap()
.build(PanicWarn).0
.build()
.unwrap();
assert_eq!(
out.views["base"]
@ -745,7 +671,7 @@ mod tests {
fn test_layout_unicode() {
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
.unwrap()
.build(PanicWarn).0
.build()
.unwrap();
assert_eq!(
out.views["base"]
@ -761,7 +687,7 @@ mod tests {
fn test_layout_unicode_multi() {
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
.unwrap()
.build(PanicWarn).0
.build()
.unwrap();
assert_eq!(
out.views["base"]
@ -776,7 +702,7 @@ mod tests {
#[test]
fn parsing_fallback() {
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
.map(|layout| layout.build(PanicWarn).0.unwrap())
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
.is_ok()
);
}
@ -816,20 +742,18 @@ mod tests {
".".into() => ButtonMeta {
icon: None,
keysym: None,
text: None,
action: None,
label: Some("test".into()),
outline: None,
}
},
".",
Vec::new(),
&mut PanicWarn,
Vec::new()
),
::action::Action::Submit {
text: Some(CString::new(".").unwrap()),
text: None,
keys: vec!(::action::KeySym("U002E".into())),
},
}
);
}
}

View File

@ -51,26 +51,14 @@ pub struct KeyState {
pub action: Action,
}
/// Sorts an iterator by converting it to a Vector and back
fn sorted<'a, I: Iterator<Item=&'a str>>(
iter: I
) -> impl Iterator<Item=&'a str> {
let mut v: Vec<&'a str> = iter.collect();
v.sort();
v.into_iter()
}
/// Generates a mapping where each key gets a keycode, starting from ~~8~~
/// HACK: starting from 9, because 8 results in keycode 0,
/// which the compositor likes to discard
/// Generates a mapping where each key gets a keycode, starting from 8
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
key_names: C
) -> HashMap<String, u32> {
HashMap::from_iter(
// sort to remove a source of indeterminism in keycode assignment
sorted(key_names.into_iter())
key_names.into_iter()
.map(|name| String::from(name))
.zip(9..)
.zip(8..)
)
}

View File

@ -1,5 +1,6 @@
#[macro_use]
extern crate bitflags;
extern crate dbus;
extern crate gio;
extern crate glib;
extern crate glib_sys;
@ -24,7 +25,5 @@ mod outputs;
mod popover;
mod resources;
mod submission;
mod style;
pub mod tests;
pub mod util;
mod util;
mod xdg;

View File

@ -1,8 +1,8 @@
/*! 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.
* Modified for dbus querying.
* 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>
@ -11,10 +11,13 @@
Copyright (c) 2019, Sophie Tauchert <999eagle@999eagle.moe>
*/
use dbus::Connection;
use regex::Regex;
use std::borrow::Cow;
use std::env;
use dbus::stdintf::org_freedesktop_dbus::Properties;
/// Errors that may be returned by `locale_config`.
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
pub enum Error {
@ -463,19 +466,23 @@ fn tag(s: &str) -> Result<LanguageRange> {
// TODO: Read /etc/locale.alias
fn tag_inv(s: &str) -> LanguageRange {
tag(s).unwrap_or(LanguageRange::invariant())
tag(s).unwrap_or_else(|_e| LanguageRange::invariant())
}
pub fn system_locale() -> Option<Locale> {
/// Returns the locale using Unix roles and the given lookup method.
// TODO: maybe don't drop all errors on the floor like that
fn unix_locale<'a, 'b, F, E>(get: F) -> Locale
where F: Fn(&'a str) -> ::std::result::Result<String, E>
{
// LC_ALL overrides everything
if let Ok(all) = env::var("LC_ALL") {
if let Ok(all) = get("LC_ALL") {
if let Ok(t) = tag(all.as_ref()) {
return Some(Locale::from(t));
return Locale::from(t);
}
}
// LANG is default
let mut loc =
if let Ok(lang) = env::var("LANG") {
if let Ok(lang) = get("LANG") {
Locale::from(tag_inv(lang.as_ref()))
} else {
Locale::invariant()
@ -494,7 +501,7 @@ pub fn system_locale() -> Option<Locale> {
("telephone", "LC_TELEPHONE"),
("measurement", "LC_MEASUREMENT"),
].iter() {
if let Ok(val) = env::var(var) {
if let Ok(val) = get(var) {
if let Ok(tag) = tag(val.as_ref())
{
loc.add_category(cat, &tag);
@ -502,7 +509,7 @@ pub fn system_locale() -> Option<Locale> {
}
}
// LANGUAGE defines fallbacks
if let Ok(langs) = env::var("LANGUAGE") {
if let Ok(langs) = get("LANGUAGE") {
for i in langs.split(':') {
if i != "" {
if let Ok(tag) = tag(i) {
@ -511,16 +518,44 @@ pub fn system_locale() -> Option<Locale> {
}
}
}
if loc.as_ref() != "" {
return Some(loc);
} else {
return None;
}
loc
}
fn env_locale() -> Locale {
unix_locale(env::var)
}
fn dbus_locale()
-> std::result::Result<Locale, Box<dyn std::error::Error>>
{
let connection = Connection::get_private(dbus::BusType::Session)?;
let property = connection.with_path(
"org.freedesktop.locale1", "/org/freedesktop/locale1", 100
);
let locales: Vec<String>
= property.get("org.freedesktop.locale1", "Locale")?;
let val_pairs: Vec<(String, String)> = locales.into_iter().map(|s| {
let mut splits = s.splitn(2, "=");
(splits.next().unwrap().into(), splits.next().unwrap_or("").into())
}).collect();
Ok(unix_locale(|var| {
match val_pairs.iter().find(|(name, _val)| name == &var) {
Some((_name, val)) => Ok(val.clone()),
None => Err(()),
}
}))
}
pub fn system_locale() -> Locale {
dbus_locale().unwrap_or_else(|_e| env_locale())
}
#[cfg(test)]
mod test {
use super::LanguageRange;
use super::*;
#[test]
fn unix_tags() {

View File

@ -39,6 +39,7 @@ cc = meson.get_compiler('c')
deps = [
# dependency('glib-2.0', version: '>=2.26.0'),
dependency('dbus-1', version: '>=1.3'),
dependency('gio-2.0', version: '>=2.26.0'),
dependency('gtk+-3.0', version: '>=3.0'),
dependency('libcroco-0.6'),
@ -58,7 +59,7 @@ rslibs = custom_target(
output: ['librs.a'],
install: false,
console: true,
command: [cargo_build] + cargo_build_flags + ['@OUTPUT@', '--lib']
command: [cargo_script, '@OUTPUT@', 'build']
)
build_rstests = custom_target(
@ -72,14 +73,14 @@ build_rstests = custom_target(
output: ['src'],
install: false,
console: true,
command: [cargo_script, 'test', '--no-run'],
command: [cargo_script, '', 'test', '--no-run'],
depends: rslibs, # no point building tests if the code itself fails
)
test(
'rstest',
cargo_script,
args: ['test'],
args: ['', 'test'],
# this is a whole Carg-based test suite, let it run for a while
timeout: 900,
depends: build_rstests,
@ -97,7 +98,20 @@ libsqueekboard = static_library('libsqueekboard',
'-DEEK_COMPILATION=1'],
)
squeekboard = executable('squeekboard',
# the straight binary needs to be demoted in favor of the wrapper script
# due to styling being inconsistent
bindir = join_paths(prefix, get_option('bindir'))
wrapper_conf = configuration_data()
wrapper_conf.set('bindir', bindir)
configure_file(
input: '../tools/squeekboard.in',
output: 'squeekboard',
install_dir: bindir,
configuration: wrapper_conf,
install: true,
)
squeekboard = executable('squeekboard-real',
'server-main.c',
wl_proto_sources,
squeekboard_resources,
@ -111,17 +125,3 @@ squeekboard = executable('squeekboard',
'-DEEKBOARD_COMPILATION=1',
'-DEEK_COMPILATION=1'],
)
bindir = join_paths(prefix, get_option('bindir'))
test_layout = custom_target('squeekboard-test-layout',
build_by_default: true,
# meson doesn't track all inputs, cargo does
build_always_stale: true,
output: ['squeekboard-test-layout'],
console: true,
command: [cargo_build] + cargo_build_flags
+ ['--rename', 'test_layout', '@OUTPUT@', '--bin', 'test_layout'],
install: true,
install_dir: bindir,
)

View File

@ -7,9 +7,9 @@ use ::locale::compare_current_locale;
use ::locale_config::system_locale;
use ::resources;
use gio::ActionExt;
use gio::ActionMapExt;
use gio::SettingsExt;
use gio::SimpleActionExt;
use glib::translate::FromGlibPtrNone;
use glib::variant::ToVariant;
use gtk::PopoverExt;
@ -18,11 +18,8 @@ use std::io::Write;
mod variants {
use glib;
use glib::Variant;
use glib_sys;
use std::os::raw::c_char;
use glib::ToVariant;
use glib::translate::FromGlibPtrFull;
use glib::translate::ToGlibPtr;
@ -52,44 +49,6 @@ mod variants {
})
.collect()
}
/// "a(ss)" variant
/// Rust doesn't allow implementing existing traits for existing types
pub struct ArrayPairString(pub Vec<(String, String)>);
impl ToVariant for ArrayPairString {
fn to_variant(&self) -> Variant {
let tspec = "a(ss)".to_glib_none();
let builder = unsafe {
let vtype = glib_sys::g_variant_type_checked_(tspec.0);
glib_sys::g_variant_builder_new(vtype)
};
let ispec = "(ss)".to_glib_none();
for (a, b) in &self.0 {
let a = a.to_glib_none();
let b = b.to_glib_none();
// string pointers are weak references
// and will get silently invalidated
// as soon as the source is out of scope
{
let a: *const c_char = a.0;
let b: *const c_char = b.0;
unsafe {
glib_sys::g_variant_builder_add(
builder,
ispec.0,
a, b
);
}
}
}
unsafe {
let ret = glib_sys::g_variant_builder_end(builder);
glib_sys::g_variant_builder_unref(builder);
glib::Variant::from_glib_full(ret)
}
}
}
}
fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
@ -129,15 +88,12 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
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 current = (kind.clone(), name.clone());
let inputs = variants::get_tuples(inputs).into_iter()
.filter(|t| t != &current);
let inputs = vec![(kind, name)].into_iter()
.chain(inputs).collect();
settings.set_value(
"sources",
&variants::ArrayPairString(inputs).to_variant()
);
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();
}
@ -147,20 +103,18 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
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()));
let translations = resources::get_layout_names(system_locale()
.tags_for("messages")
.next().unwrap() // guaranteed to exist by locale_config
.as_ref()
);
// sorted collection of human and machine names
let mut human_names: Vec<(&str, &str)> = match translations {
@ -195,35 +149,39 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
height: position.width.floor() as i32,
});
if let Some(current_name) = input_names.get(0) {
let current_name = current_name.to_variant();
let initial_state = input_names[current].to_variant();
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(current_name.type_()),
&current_name,
);
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(initial_state.type_()),
&initial_state,
);
layout_action.connect_change_state(|_action, state| {
match state {
Some(v) => {
v.get::<String>()
.or_else(|| {
eprintln!("Variant is not string: {:?}", v);
None
})
.map(|state| set_layout("xkb".into(), state));
},
None => eprintln!("No variant selected"),
};
});
let action_group = gio::SimpleActionGroup::new();
action_group.add_action(&layout_action);
menu.insert_action_group("popup", Some(&action_group));
};
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::<String>().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();
}

View File

@ -13,7 +13,6 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("us", include_str!("../data/keyboards/us.yaml")),
("us_wide", include_str!("../data/keyboards/us_wide.yaml")),
("de", include_str!("../data/keyboards/de.yaml")),
("de_wide", include_str!("../data/keyboards/de_wide.yaml")),
("el", include_str!("../data/keyboards/el.yaml")),
("es", include_str!("../data/keyboards/es.yaml")),
("fi", include_str!("../data/keyboards/fi.yaml")),
@ -43,7 +42,6 @@ const LAYOUT_NAMES: &[(*const str, *const 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")),
("ja-JP", include_str!("../data/langs/ja-JP.txt")),
("pl-PL", include_str!("../data/langs/pl-PL.txt")),
];

View File

@ -1,7 +0,0 @@
#ifndef __STYLE_H
#define __STYLE_H
#include "gtk/gtk.h"
GtkCssProvider *squeek_load_style();
#endif

View File

@ -1,124 +0,0 @@
/*
* Copyright (C) 2000 Red Hat, Inc.
* Copyright (C) 2019 Purism, SPC
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.Free
*/
/*! CSS data loading */
use std::env;
use glib::object::ObjectExt;
use util::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use gio;
use gtk;
use gtk_sys;
use gtk::CssProviderExt;
use glib::translate::ToGlibPtr;
/// Loads the layout style based on current theme
/// without having to worry about string allocation
#[no_mangle]
pub extern "C"
fn squeek_load_style() -> *const gtk_sys::GtkCssProvider {
unsafe { gtk::set_initialized() };
let theme = gtk::Settings::get_default()
.map(|settings| get_theme_name(&settings));
let css_name = path_from_theme(theme);
let resource_name = if gio::resources_get_info(
&css_name,
gio::ResourceLookupFlags::NONE
).is_ok() {
css_name
} else { // use default if this path doesn't exist
path_from_theme(None)
};
let provider = gtk::CssProvider::new();
provider.load_from_resource(&resource_name);
provider.to_glib_full()
}
}
// not Adwaita, but rather fall back to default
const DEFAULT_THEME_NAME: &str = "";
struct GtkTheme {
name: String,
variant: Option<String>,
}
/// Gets theme as determined by the toolkit
/// Ported from GTK's gtksettings.c
fn get_theme_name(settings: &gtk::Settings) -> GtkTheme {
let env_theme = env::var("GTK_THEME")
.map(|theme| {
let mut parts = theme.splitn(2, ":");
GtkTheme {
// guaranteed at least empty string
// as the first result from splitting a string
name: parts.next().unwrap().into(),
variant: parts.next().map(String::from)
}
})
.map_err(|e| {
match &e {
env::VarError::NotPresent => {},
e => eprintln!("GTK_THEME variable invalid: {}", e),
};
e
}).ok();
match env_theme {
Some(theme) => theme,
None => GtkTheme {
name: {
settings.get_property("gtk-theme-name")
.ok_warn("No theme name")
.and_then(|value| value.get::<String>())
.unwrap_or(DEFAULT_THEME_NAME.into())
},
variant: {
settings.get_property("gtk-application-prefer-dark-theme")
.ok_warn("No settings key")
.and_then(|value| value.get::<bool>())
.and_then(|dark_preferred| match dark_preferred {
true => Some("dark".into()),
false => None,
})
},
},
}
}
fn path_from_theme(theme: Option<GtkTheme>) -> String {
format!(
"/sm/puri/squeekboard/style{}.css",
match theme {
Some(GtkTheme { name, variant: Some(variant) }) => {
format!("-{}:{}", name, variant)
},
Some(GtkTheme { name, variant: None }) => format!("-{}", name),
None => "".into(),
}
)
}

View File

@ -1,78 +0,0 @@
/*! Testing functionality */
use ::data::Layout;
use xkbcommon::xkb;
use ::util::WarningHandler;
pub struct CountAndPrint(u32);
impl WarningHandler for CountAndPrint {
fn handle(&mut self, warning: &str) {
self.0 = self.0 + 1;
println!("{}", warning);
}
}
impl CountAndPrint {
fn new() -> CountAndPrint {
CountAndPrint(0)
}
}
pub fn check_builtin_layout(name: &str) {
check_layout(Layout::from_resource(name).expect("Invalid layout data"))
}
pub fn check_layout_file(path: &str) {
check_layout(Layout::from_file(path.into()).expect("Invalid layout file"))
}
fn check_layout(layout: Layout) {
let handler = CountAndPrint::new();
let (layout, handler) = layout.build(handler);
if handler.0 > 0 {
println!("{} mistakes in layout", handler.0)
}
let layout = layout.expect("layout broken");
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap_str = layout.keymap_str
.clone()
.into_string().expect("Failed to decode keymap string");
let keymap = xkb::Keymap::new_from_string(
&context,
keymap_str.clone(),
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
).expect("Failed to create keymap");
let state = xkb::State::new(&keymap);
// "Press" each button with keysyms
for view in layout.views.values() {
for row in &view.rows {
for button in &row.buttons {
let keystate = button.state.borrow();
for keycode in &keystate.keycodes {
match state.key_get_one_sym(*keycode) {
xkb::KEY_NoSymbol => {
eprintln!("{}", keymap_str);
panic!("Keysym {} on key {:?} can't be resolved", keycode, button.name);
},
_ => {},
}
}
}
}
}
if handler.0 > 0 {
panic!("Layout contains mistakes");
}
}

View File

@ -177,27 +177,6 @@ impl<T> Borrow<Rc<T>> for Pointer<T> {
}
}
/// Sugar for logging errors in results
pub trait Warn {
type Value;
fn ok_warn(self, msg: &str) -> Option<Self::Value>;
}
impl<T, E: std::error::Error> Warn for Result<T, E> {
type Value = T;
fn ok_warn(self, msg: &str) -> Option<T> {
self.map_err(|e| {
eprintln!("{}: {}", msg, e);
e
}).ok()
}
}
pub trait WarningHandler {
/// Handle a warning
fn handle(&mut self, warning: &str);
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -49,7 +49,7 @@ endforeach
# and the need to call it manually
foreach layout : [
'us', 'us_wide',
'de', 'de_wide',
'de',
'el',
'es',
'fi',
@ -62,7 +62,7 @@ foreach layout : [
test(
'test_layout_' + layout,
cargo_script,
args: ['run', '--example', 'test_layout', layout]
args: ['', 'run', '--example', 'test_layout', layout]
)
endforeach

View File

@ -12,4 +12,4 @@ for DIR in ${DIRS}; do
fi;
done;
exec $(which squeekboard)
exec @bindir@/squeekboard-real