Compare commits
36 Commits
v1.3.2
...
squeekboar
| Author | SHA1 | Date | |
|---|---|---|---|
| 3af10285b7 | |||
| 53997abc46 | |||
| 34765be22e | |||
| 8b4c643d3e | |||
| 358b25c431 | |||
| 2749fdb686 | |||
| 8e7909e877 | |||
| afaacd3f68 | |||
| 6a164d8119 | |||
| 3c45e3e53c | |||
| cdf263d984 | |||
| 2ddfcfaff0 | |||
| a901c85bcb | |||
| fdbbe8f126 | |||
| f284627beb | |||
| d45724c462 | |||
| 93d0dcdc99 | |||
| b252f7659b | |||
| af6ad1fce6 | |||
| 4ee8a91dfe | |||
| 6d5f793718 | |||
| 59f6173282 | |||
| 3aec821f92 | |||
| 3ac4caa3b9 | |||
| 80ac591535 | |||
| 034570bfa0 | |||
| 1abca0a44e | |||
| 23693521b7 | |||
| 0179507254 | |||
| 0c7e77a05f | |||
| 0adde1004f | |||
| 9b271a6919 | |||
| 1db561d33a | |||
| 9571adb107 | |||
| f834f174d8 | |||
| 3c0b142c4f |
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0.*"
|
||||
clap = "2.32.*"
|
||||
maplit = "1.0.*"
|
||||
regex = "1.1.*"
|
||||
serde = { version = "1.0.*", features = ["derive"] }
|
||||
|
||||
12
HACKING.md
12
HACKING.md
@ -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 levels
|
||||
- it changes views
|
||||
|
||||
Testing with an application:
|
||||
|
||||
@ -50,10 +50,8 @@ Testing layouts:
|
||||
Layouts can be selected using the GNOME Settings application.
|
||||
|
||||
```
|
||||
# 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
|
||||
# define all available layouts. First one is currently selected.
|
||||
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'de')]"
|
||||
```
|
||||
|
||||
Coding
|
||||
@ -114,7 +112,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
|
||||
@ -125,6 +123,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
|
||||
```
|
||||
|
||||
7
cargo.sh
7
cargo.sh
@ -11,14 +11,7 @@ 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
|
||||
|
||||
34
cargo_build.sh
Executable file
34
cargo_build.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/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
|
||||
@ -1,5 +1,4 @@
|
||||
# German layout by Mark Müller
|
||||
# Version 2019111700
|
||||
# Maintained by: Mark Müller <markmueller86@gmail.com>
|
||||
---
|
||||
bounds: { x: 0, y: 1, width: 360, height: 208 }
|
||||
|
||||
@ -83,7 +82,7 @@ buttons:
|
||||
space:
|
||||
outline: "spaceline"
|
||||
label: " "
|
||||
keysym: "space"
|
||||
text: " "
|
||||
Return:
|
||||
outline: "altline"
|
||||
icon: "key-enter"
|
||||
|
||||
88
data/keyboards/de_wide.yaml
Normal file
88
data/keyboards/de_wide.yaml
Normal file
@ -0,0 +1,88 @@
|
||||
# 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"
|
||||
@ -53,6 +53,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "altline"
|
||||
@ -86,111 +87,112 @@ buttons:
|
||||
label: "αι"
|
||||
period:
|
||||
outline: "altline"
|
||||
label: "."
|
||||
text: "."
|
||||
space:
|
||||
outline: spaceline
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
aring:
|
||||
label: "å"
|
||||
text: "å"
|
||||
Aring:
|
||||
label: "Å"
|
||||
text: "Å"
|
||||
oslash:
|
||||
label: "ø"
|
||||
text: "ø"
|
||||
Oslash:
|
||||
label: "Ø"
|
||||
text: "Ø"
|
||||
ae:
|
||||
label: "æ"
|
||||
text: "æ"
|
||||
AE:
|
||||
label: "Æ"
|
||||
text: "Æ"
|
||||
asterisk:
|
||||
label: "*"
|
||||
text: "*"
|
||||
asciitilde:
|
||||
label: "~"
|
||||
text: "~"
|
||||
quoteleft:
|
||||
label: "`"
|
||||
text: "`"
|
||||
bar:
|
||||
label: "|"
|
||||
text: "|"
|
||||
U00B7:
|
||||
label: "·"
|
||||
text: "·"
|
||||
squareroot:
|
||||
label: "√"
|
||||
text: "√"
|
||||
Greek_pi:
|
||||
label: "π"
|
||||
text: "π"
|
||||
division:
|
||||
label: "÷"
|
||||
text: "÷"
|
||||
multiply:
|
||||
label: "×"
|
||||
text: "×"
|
||||
paragraph:
|
||||
label: "¶"
|
||||
text: "¶"
|
||||
Greek_tau:
|
||||
label: "τ"
|
||||
text: "τ"
|
||||
copyright:
|
||||
label: "©"
|
||||
text: "©"
|
||||
numbersign:
|
||||
label: "#"
|
||||
text: "#"
|
||||
U00AE:
|
||||
label: "®"
|
||||
text: "®"
|
||||
at:
|
||||
label: "@"
|
||||
text: "@"
|
||||
dollar:
|
||||
label: "$"
|
||||
text: "$"
|
||||
U00A3:
|
||||
label: "£"
|
||||
text: "£"
|
||||
percent:
|
||||
label: "%"
|
||||
text: "%"
|
||||
EuroSign:
|
||||
label: "€"
|
||||
text: "€"
|
||||
ampersand:
|
||||
label: "&"
|
||||
text: "&"
|
||||
U00A5:
|
||||
label: "¥"
|
||||
text: "¥"
|
||||
minus:
|
||||
label: "-"
|
||||
text: "-"
|
||||
asciicircum:
|
||||
label: "^"
|
||||
text: "^"
|
||||
underscore:
|
||||
label: "_"
|
||||
text: "_"
|
||||
degree:
|
||||
label: "°"
|
||||
text: "°"
|
||||
plus:
|
||||
label: "+"
|
||||
text: "+"
|
||||
equal:
|
||||
label: "="
|
||||
text: "="
|
||||
parenleft:
|
||||
label: "("
|
||||
text: "("
|
||||
parenright:
|
||||
label: ")"
|
||||
text: ")"
|
||||
braceleft:
|
||||
label: "{"
|
||||
text: "{"
|
||||
braceright:
|
||||
label: "}"
|
||||
text: "}"
|
||||
comma:
|
||||
label: ","
|
||||
text: ","
|
||||
backslash:
|
||||
label: "\\"
|
||||
text: "\\"
|
||||
slash:
|
||||
label: "/"
|
||||
text: "/"
|
||||
quotedbl:
|
||||
label: "\""
|
||||
text: "\""
|
||||
quoteright:
|
||||
label: "'"
|
||||
text: "'"
|
||||
less:
|
||||
label: "<"
|
||||
text: "<"
|
||||
greater:
|
||||
label: ">"
|
||||
text: ">"
|
||||
colon:
|
||||
label: ":"
|
||||
text: ":"
|
||||
semicolon:
|
||||
label: ";"
|
||||
text: ";"
|
||||
exclam:
|
||||
label: "!"
|
||||
text: "!"
|
||||
question:
|
||||
label: "?"
|
||||
text: "?"
|
||||
bracketleft:
|
||||
label: "["
|
||||
text: "["
|
||||
bracketright:
|
||||
label: "]"
|
||||
text: "]"
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "default"
|
||||
@ -80,14 +81,14 @@ buttons:
|
||||
|
||||
period:
|
||||
outline: "default"
|
||||
label: "."
|
||||
text: "."
|
||||
space:
|
||||
outline: "spaceline"
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "altline"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
colon:
|
||||
label: ":"
|
||||
"\"":
|
||||
keysym: "quotedbl"
|
||||
text: ":"
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "altline"
|
||||
@ -69,108 +70,109 @@ buttons:
|
||||
outline: altline
|
||||
space:
|
||||
outline: spaceline
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
aring:
|
||||
label: "å"
|
||||
text: "å"
|
||||
Aring:
|
||||
label: "Å"
|
||||
text: "Å"
|
||||
ouml:
|
||||
label: "ö"
|
||||
text: "ö"
|
||||
Ouml:
|
||||
label: "Ö"
|
||||
text: "Ö"
|
||||
auml:
|
||||
label: "ä"
|
||||
text: "ä"
|
||||
Auml:
|
||||
label: "Ä"
|
||||
text: "Ä"
|
||||
asterisk:
|
||||
label: "*"
|
||||
text: "*"
|
||||
asciitilde:
|
||||
label: "~"
|
||||
text: "~"
|
||||
quoteleft:
|
||||
label: "`"
|
||||
text: "`"
|
||||
bar:
|
||||
label: "|"
|
||||
text: "|"
|
||||
U00B7:
|
||||
label: "·"
|
||||
text: "·"
|
||||
squareroot:
|
||||
label: "√"
|
||||
text: "√"
|
||||
Greek_pi:
|
||||
label: "π"
|
||||
text: "π"
|
||||
division:
|
||||
label: "÷"
|
||||
text: "÷"
|
||||
multiply:
|
||||
label: "×"
|
||||
text: "×"
|
||||
paragraph:
|
||||
label: "¶"
|
||||
text: "¶"
|
||||
Greek_tau:
|
||||
label: "τ"
|
||||
text: "τ"
|
||||
copyright:
|
||||
label: "©"
|
||||
text: "©"
|
||||
numbersign:
|
||||
label: "#"
|
||||
text: "#"
|
||||
U00AE:
|
||||
label: "®"
|
||||
text: "®"
|
||||
at:
|
||||
label: "@"
|
||||
text: "@"
|
||||
dollar:
|
||||
label: "$"
|
||||
text: "$"
|
||||
U00A3:
|
||||
label: "£"
|
||||
text: "£"
|
||||
percent:
|
||||
label: "%"
|
||||
text: "%"
|
||||
EuroSign:
|
||||
label: "€"
|
||||
text: "€"
|
||||
ampersand:
|
||||
label: "&"
|
||||
text: "&"
|
||||
U00A5:
|
||||
label: "¥"
|
||||
text: "¥"
|
||||
minus:
|
||||
label: "-"
|
||||
text: "-"
|
||||
asciicircum:
|
||||
label: "^"
|
||||
text: "^"
|
||||
underscore:
|
||||
label: "_"
|
||||
text: "_"
|
||||
degree:
|
||||
label: "°"
|
||||
text: "°"
|
||||
plus:
|
||||
label: "+"
|
||||
text: "+"
|
||||
equal:
|
||||
label: "="
|
||||
text: "="
|
||||
parenleft:
|
||||
label: "("
|
||||
text: "("
|
||||
parenright:
|
||||
label: ")"
|
||||
text: ")"
|
||||
braceleft:
|
||||
label: "{"
|
||||
text: "{"
|
||||
braceright:
|
||||
label: "}"
|
||||
text: "}"
|
||||
comma:
|
||||
label: ","
|
||||
text: ","
|
||||
backslash:
|
||||
label: "\\"
|
||||
text: "\\"
|
||||
slash:
|
||||
label: "/"
|
||||
text: "/"
|
||||
quotedbl:
|
||||
label: "\""
|
||||
text: "\""
|
||||
quoteright:
|
||||
label: "'"
|
||||
text: "'"
|
||||
less:
|
||||
label: "<"
|
||||
text: "<"
|
||||
greater:
|
||||
label: ">"
|
||||
text: ">"
|
||||
colon:
|
||||
label: ":"
|
||||
text: ":"
|
||||
semicolon:
|
||||
label: ";"
|
||||
text: ";"
|
||||
exclam:
|
||||
label: "!"
|
||||
text: "!"
|
||||
question:
|
||||
label: "?"
|
||||
text: "?"
|
||||
bracketleft:
|
||||
label: "["
|
||||
text: "["
|
||||
bracketright:
|
||||
label: "]"
|
||||
text: "]"
|
||||
|
||||
|
||||
@ -53,6 +53,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "default"
|
||||
@ -91,6 +92,7 @@ buttons:
|
||||
Return:
|
||||
outline: "altline"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
colon:
|
||||
label: ":"
|
||||
"\"":
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Japanese Kana layout by Mark Müller
|
||||
# Version 2019111800
|
||||
# Maintained by: Mark Müller <markmueller86@gmail.com>
|
||||
---
|
||||
bounds: { x: 0, y: 1, width: 360, height: 208 }
|
||||
|
||||
@ -221,11 +220,11 @@ buttons:
|
||||
action: "show_prefs"
|
||||
outline: "special"
|
||||
icon: "keyboard-mode-symbolic"
|
||||
# space button with unicode keysym for ideographic space
|
||||
# space button using text tag for ideographic space
|
||||
space:
|
||||
outline: "default-wide"
|
||||
label: "␣"
|
||||
keysym: "U3000"
|
||||
text: " "
|
||||
# switch to number view
|
||||
numbers:
|
||||
action:
|
||||
|
||||
@ -26,13 +26,13 @@ views:
|
||||
- "show_numbers preferences space . Return"
|
||||
numbers:
|
||||
- "1 2 3 4 5 6 7 8 9 0"
|
||||
- "at numbersign dollar percent ampersand minus underscore plus parenleft parenright"
|
||||
- "show_symbols comma quotedbl quoteright colon semicolon exclam question BackSpace"
|
||||
- "@ # $ % & - _ + ( )"
|
||||
- "show_symbols , \" ' : ; ! ? BackSpace"
|
||||
- "show_letters preferences space . Return"
|
||||
symbols:
|
||||
- "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"
|
||||
- "~ ` | U00B7 squareroot Greek_pi Greek_tau division multiply paragraph"
|
||||
- "copyright U00AE U00A3 EuroSign U00A5 asciicircum degree * { }"
|
||||
- "show_numbers \\ / < > = [ ] BackSpace"
|
||||
- "show_letters preferences space . Return"
|
||||
|
||||
buttons:
|
||||
@ -46,6 +46,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "altline"
|
||||
@ -69,108 +70,37 @@ buttons:
|
||||
outline: altline
|
||||
space:
|
||||
outline: spaceline
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
aring:
|
||||
label: "å"
|
||||
Aring:
|
||||
label: "Å"
|
||||
oslash:
|
||||
label: "ø"
|
||||
Oslash:
|
||||
label: "Ø"
|
||||
ae:
|
||||
label: "æ"
|
||||
AE:
|
||||
label: "Æ"
|
||||
asterisk:
|
||||
label: "*"
|
||||
asciitilde:
|
||||
label: "~"
|
||||
quoteleft:
|
||||
label: "`"
|
||||
bar:
|
||||
label: "|"
|
||||
keysym: "Return"
|
||||
U00B7:
|
||||
label: "·"
|
||||
text: "·"
|
||||
squareroot:
|
||||
label: "√"
|
||||
text: "√"
|
||||
Greek_pi:
|
||||
label: "π"
|
||||
text: "π"
|
||||
division:
|
||||
label: "÷"
|
||||
text: "÷"
|
||||
multiply:
|
||||
label: "×"
|
||||
text: "×"
|
||||
paragraph:
|
||||
label: "¶"
|
||||
text: "¶"
|
||||
Greek_tau:
|
||||
label: "τ"
|
||||
text: "τ"
|
||||
copyright:
|
||||
label: "©"
|
||||
numbersign:
|
||||
label: "#"
|
||||
text: "©"
|
||||
U00AE:
|
||||
label: "®"
|
||||
at:
|
||||
label: "@"
|
||||
dollar:
|
||||
label: "$"
|
||||
text: "®"
|
||||
U00A3:
|
||||
label: "£"
|
||||
percent:
|
||||
label: "%"
|
||||
text: "£"
|
||||
EuroSign:
|
||||
label: "€"
|
||||
ampersand:
|
||||
label: "&"
|
||||
text: "€"
|
||||
U00A5:
|
||||
label: "¥"
|
||||
minus:
|
||||
label: "-"
|
||||
text: "¥"
|
||||
asciicircum:
|
||||
label: "^"
|
||||
underscore:
|
||||
label: "_"
|
||||
text: "^"
|
||||
degree:
|
||||
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: "]"
|
||||
text: "°"
|
||||
|
||||
|
||||
@ -22,22 +22,24 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
space:
|
||||
outline: spaceline
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: outline7
|
||||
icon: "key-enter"
|
||||
keysym: "BackSpace"
|
||||
asterisk:
|
||||
label: "*"
|
||||
text: "*"
|
||||
numbersign:
|
||||
label: "#"
|
||||
text: "#"
|
||||
minus:
|
||||
label: "-"
|
||||
text: "-"
|
||||
plus:
|
||||
label: "+"
|
||||
text: "+"
|
||||
parenleft:
|
||||
label: "("
|
||||
text: "("
|
||||
parenright:
|
||||
label: ")"
|
||||
text: ")"
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "altline"
|
||||
@ -69,96 +70,97 @@ buttons:
|
||||
outline: altline
|
||||
space:
|
||||
outline: spaceline
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
asterisk:
|
||||
label: "*"
|
||||
text: "*"
|
||||
asciitilde:
|
||||
label: "~"
|
||||
text: "~"
|
||||
quoteleft:
|
||||
label: "`"
|
||||
text: "`"
|
||||
bar:
|
||||
label: "|"
|
||||
text: "|"
|
||||
U00B7:
|
||||
label: "·"
|
||||
text: "·"
|
||||
squareroot:
|
||||
label: "√"
|
||||
text: "√"
|
||||
Greek_pi:
|
||||
label: "π"
|
||||
text: "π"
|
||||
division:
|
||||
label: "÷"
|
||||
text: "÷"
|
||||
multiply:
|
||||
label: "×"
|
||||
text: "×"
|
||||
paragraph:
|
||||
label: "¶"
|
||||
text: "¶"
|
||||
Greek_tau:
|
||||
label: "τ"
|
||||
text: "τ"
|
||||
copyright:
|
||||
label: "©"
|
||||
text: "©"
|
||||
numbersign:
|
||||
label: "#"
|
||||
text: "#"
|
||||
U00AE:
|
||||
label: "®"
|
||||
text: "®"
|
||||
at:
|
||||
label: "@"
|
||||
text: "@"
|
||||
dollar:
|
||||
label: "$"
|
||||
text: "$"
|
||||
U00A3:
|
||||
label: "£"
|
||||
text: "£"
|
||||
percent:
|
||||
label: "%"
|
||||
text: "%"
|
||||
EuroSign:
|
||||
label: "€"
|
||||
text: "€"
|
||||
ampersand:
|
||||
label: "&"
|
||||
text: "&"
|
||||
U00A5:
|
||||
label: "¥"
|
||||
text: "¥"
|
||||
minus:
|
||||
label: "-"
|
||||
text: "-"
|
||||
asciicircum:
|
||||
label: "^"
|
||||
text: "^"
|
||||
underscore:
|
||||
label: "_"
|
||||
text: "_"
|
||||
degree:
|
||||
label: "°"
|
||||
text: "°"
|
||||
plus:
|
||||
label: "+"
|
||||
text: "+"
|
||||
equal:
|
||||
label: "="
|
||||
text: "="
|
||||
parenleft:
|
||||
label: "("
|
||||
text: "("
|
||||
parenright:
|
||||
label: ")"
|
||||
text: ")"
|
||||
braceleft:
|
||||
label: "{"
|
||||
text: "{"
|
||||
braceright:
|
||||
label: "}"
|
||||
text: "}"
|
||||
comma:
|
||||
label: ","
|
||||
text: ","
|
||||
backslash:
|
||||
label: "\\"
|
||||
text: "\\"
|
||||
slash:
|
||||
label: "/"
|
||||
text: "/"
|
||||
quotedbl:
|
||||
label: "\""
|
||||
text: "\""
|
||||
quoteright:
|
||||
label: "'"
|
||||
text: "'"
|
||||
less:
|
||||
label: "<"
|
||||
text: "<"
|
||||
greater:
|
||||
label: ">"
|
||||
text: ">"
|
||||
colon:
|
||||
label: ":"
|
||||
text: ":"
|
||||
semicolon:
|
||||
label: ";"
|
||||
text: ";"
|
||||
exclam:
|
||||
label: "!"
|
||||
text: "!"
|
||||
question:
|
||||
label: "?"
|
||||
text: "?"
|
||||
bracketleft:
|
||||
label: "["
|
||||
text: "["
|
||||
bracketright:
|
||||
label: "]"
|
||||
text: "]"
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "special"
|
||||
@ -72,14 +73,13 @@ buttons:
|
||||
label: "*/="
|
||||
period:
|
||||
outline: "special"
|
||||
label: "."
|
||||
text: "."
|
||||
space:
|
||||
outline: "spaceline"
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
colon:
|
||||
label: ":"
|
||||
"\"":
|
||||
keysym: "quotedbl"
|
||||
text: ":"
|
||||
|
||||
@ -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 period Return"
|
||||
- "show_numbers preferences space . Return"
|
||||
upper:
|
||||
- "Q W E R T Y U I O P"
|
||||
- "A S D F G H J K L"
|
||||
- "Shift_L Z X C V B N M BackSpace"
|
||||
- "show_numbers preferences space period Return"
|
||||
- "show_numbers preferences space . Return"
|
||||
numbers:
|
||||
- "1 2 3 4 5 6 7 8 9 0"
|
||||
- "@ # $ % & - _ + ( )"
|
||||
- "show_symbols , \" ' colon ; ! ? BackSpace"
|
||||
- "show_letters preferences space period Return"
|
||||
- "show_letters preferences space . Return"
|
||||
symbols:
|
||||
- "~ ` | · √ π τ ÷ × ¶"
|
||||
- "© ® £ € ¥ ^ ° * { }"
|
||||
- "show_numbers_from_symbols \\ / < > = [ ] BackSpace"
|
||||
- "show_letters preferences space period Return"
|
||||
- "show_letters preferences space . Return"
|
||||
|
||||
buttons:
|
||||
Shift_L:
|
||||
@ -46,6 +46,7 @@ buttons:
|
||||
BackSpace:
|
||||
outline: "altline"
|
||||
icon: "edit-clear-symbolic"
|
||||
keysym: "BackSpace"
|
||||
preferences:
|
||||
action: "show_prefs"
|
||||
outline: "special"
|
||||
@ -70,16 +71,15 @@ buttons:
|
||||
set_view: "symbols"
|
||||
outline: "altline"
|
||||
label: "*/="
|
||||
period:
|
||||
".":
|
||||
outline: "special"
|
||||
label: "."
|
||||
text: "."
|
||||
space:
|
||||
outline: "spaceline"
|
||||
label: " "
|
||||
text: " "
|
||||
Return:
|
||||
outline: "wide"
|
||||
icon: "key-enter"
|
||||
keysym: "Return"
|
||||
colon:
|
||||
label: ":"
|
||||
"\"":
|
||||
keysym: "quotedbl"
|
||||
text: ":"
|
||||
|
||||
8
data/langs/ja-JP.txt
Normal file
8
data/langs/ja-JP.txt
Normal file
@ -0,0 +1,8 @@
|
||||
us 英語 (US)
|
||||
de ドイツ語
|
||||
el ギリシャ語
|
||||
es スペイン語
|
||||
it イタリア語
|
||||
jp+kana 日本語 (かな)
|
||||
nb ノルウェー語
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<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>
|
||||
|
||||
46
data/style-Adwaita:dark.css
Normal file
46
data/style-Adwaita:dark.css
Normal file
@ -0,0 +1,46 @@
|
||||
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;
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
sq_view {
|
||||
background-color: rgba(0, 0, 0, 255);
|
||||
color: #ffffff;
|
||||
background-color: @theme_base_color; /*rgba(0, 0, 0, 255);*/
|
||||
color: @theme_text_color; /*#ffffff;*/
|
||||
font-family: cantarell, sans-serif;
|
||||
}
|
||||
|
||||
sq_view sq_button {
|
||||
color: #deddda;
|
||||
background: #464448;
|
||||
color: @theme_fg_color; /*#deddda;*/
|
||||
background: mix(@theme_bg_color, @theme_base_color, -0.5); /* #464448; */
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #5e5c64;
|
||||
border-color: @borders; /* #5e5c64;*/
|
||||
border-radius: 3px;
|
||||
margin: 4px 2px 4px 2px;
|
||||
}
|
||||
@ -18,29 +18,32 @@ sq_view.wide sq_button {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
|
||||
sq_button:active {
|
||||
background: #747077;
|
||||
border-color: #96949d;
|
||||
sq_button:active,
|
||||
sq_button.altline:active,
|
||||
sq_button.special:active,
|
||||
sq_button.wide:active {
|
||||
background: mix(@theme_bg_color, @theme_selected_bg_color, 0.4);/* #747077; */
|
||||
border-color: mix(@borders, @theme_selected_fg_color, 0.5);/* #96949d; */
|
||||
}
|
||||
|
||||
sq_button.altline,
|
||||
sq_button.special,
|
||||
sq_button.wide {
|
||||
background: #2b292f;
|
||||
border-color: #3e3a44;
|
||||
background: mix(@theme_bg_color, @theme_base_color, 0.5); /*#2b292f;*/
|
||||
border-color: @borders; /* #3e3a44; */
|
||||
}
|
||||
|
||||
sq_button.locked {
|
||||
background: #ffffff;
|
||||
color: #2b292f;
|
||||
background: @theme_fg_color; /*#ffffff;*/
|
||||
color: @theme_bg_color; /*#2b292f;*/
|
||||
}
|
||||
|
||||
#Return {
|
||||
background: #1c71d8;
|
||||
border-color: #1a5fb4;
|
||||
background: @theme_selected_bg_color; /* #1c71d8; */
|
||||
border-color: @borders; /*#1a5fb4;*/
|
||||
}
|
||||
|
||||
#Return:active {
|
||||
background: #1c71d8;
|
||||
border-color: #3584e4;
|
||||
background: mix(@theme_selected_bg_color, @theme_bg_color, 0.4); /*#1c71d8;*/
|
||||
border-color: @borders; /*#3584e4;*/
|
||||
}
|
||||
|
||||
7
debian/changelog
vendored
7
debian/changelog
vendored
@ -1,3 +1,10 @@
|
||||
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
|
||||
|
||||
11
debian/control
vendored
11
debian/control
vendored
@ -12,6 +12,7 @@ Build-Depends:
|
||||
libgtk-3-dev,
|
||||
libcroco3-dev,
|
||||
librust-bitflags-1-dev (>= 1.0),
|
||||
librust-clap-2+default-dev (>= 2.32),
|
||||
librust-gio+v2-44-dev,
|
||||
librust-glib+v2-44-dev,
|
||||
librust-glib-sys-dev,
|
||||
@ -40,3 +41,13 @@ 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
|
||||
|
||||
1
debian/squeekboard-devel.install
vendored
Normal file
1
debian/squeekboard-devel.install
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/bin/squeekboard-test-layout /usr/bin
|
||||
2
debian/squeekboard.install
vendored
Normal file
2
debian/squeekboard.install
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
tools/squeekboard-restyled usr/bin
|
||||
usr/bin/squeekboard /usr/bin
|
||||
2
debian/squeekboard.lintian-overrides
vendored
2
debian/squeekboard.lintian-overrides
vendored
@ -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-real: libyaml
|
||||
squeekboard binary: embedded-library usr/bin/squeekboard: libyaml
|
||||
|
||||
@ -158,6 +158,8 @@ 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,
|
||||
@ -170,6 +172,18 @@ 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)
|
||||
@ -279,6 +293,9 @@ 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;
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
|
||||
#include "eek-keyboard.h"
|
||||
#include "eek-renderer.h"
|
||||
#include "src/style.h"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
@ -623,10 +624,7 @@ eek_renderer_init (EekRenderer *self)
|
||||
|
||||
gtk_icon_theme_add_resource_path (theme, "/sm/puri/squeekboard/icons");
|
||||
|
||||
/* 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");
|
||||
priv->css_provider = squeek_load_style();
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@ -207,22 +207,8 @@ static void
|
||||
settings_get_layout(GSettings *settings, char **type, char **layout)
|
||||
{
|
||||
GVariant *inputs = g_settings_get_value(settings, "sources");
|
||||
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);
|
||||
// current layout is always first
|
||||
g_variant_get_child(inputs, 0, "(ss)", type, layout);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@ -1,52 +1,10 @@
|
||||
extern crate rs;
|
||||
extern crate xkbcommon;
|
||||
|
||||
use rs::tests::check_builtin_layout;
|
||||
use std::env;
|
||||
|
||||
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());
|
||||
check_builtin_layout(
|
||||
env::args().nth(1).expect("No argument given").as_str()
|
||||
);
|
||||
}
|
||||
|
||||
10
meson.build
10
meson.build
@ -1,7 +1,7 @@
|
||||
project(
|
||||
'squeekboard',
|
||||
'c', 'rust',
|
||||
version: '1.3.2',
|
||||
version: '1.4.0',
|
||||
license: 'GPLv3',
|
||||
meson_version: '>=0.51.0',
|
||||
default_options: [
|
||||
@ -33,6 +33,11 @@ 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'))
|
||||
@ -54,8 +59,9 @@ summary = [
|
||||
]
|
||||
message('\n'.join(summary))
|
||||
|
||||
cargo = find_program('cargo')
|
||||
dep_cargo = find_program('cargo')
|
||||
cargo_script = find_program('cargo.sh')
|
||||
cargo_build = find_program('cargo_build.sh')
|
||||
|
||||
subdir('data')
|
||||
subdir('protocols')
|
||||
|
||||
16
src/bin/test_layout.rs
Normal file
16
src/bin/test_layout.rs
Normal file
@ -0,0 +1,16 @@
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
282
src/data.rs
282
src/data.rs
@ -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,21 +151,30 @@ 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().map_err(LoadError::BadKeyMap)
|
||||
layout.build(handler).0.map_err(LoadError::BadKeyMap)
|
||||
)
|
||||
},
|
||||
DataSource::Resource(name) => {
|
||||
Layout::from_resource(&name)
|
||||
.and_then(|layout|
|
||||
layout.build().map_err(LoadError::BadKeyMap)
|
||||
layout.build(handler).0.map_err(LoadError::BadKeyMap)
|
||||
)
|
||||
},
|
||||
}
|
||||
@ -225,22 +234,28 @@ 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 {
|
||||
/// Action other than keysym (conflicts with keysym)
|
||||
/// Special action to perform on activation. Conflicts with keysym, text.
|
||||
action: Option<Action>,
|
||||
/// The name of the outline. If not present, will be "default"
|
||||
outline: Option<String>,
|
||||
/// FIXME: start using it
|
||||
/// The name of the XKB keysym to emit on activation.
|
||||
/// Conflicts with action, text
|
||||
keysym: Option<String>,
|
||||
/// If not present, will be derived from the button ID
|
||||
/// 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
|
||||
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)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
enum Action {
|
||||
#[serde(rename="locking")]
|
||||
@ -296,7 +311,7 @@ impl Layout {
|
||||
.map_err(LoadError::BadResource)
|
||||
}
|
||||
|
||||
fn from_file(path: PathBuf) -> Result<Layout, Error> {
|
||||
pub fn from_file(path: PathBuf) -> Result<Layout, Error> {
|
||||
let infile = BufReader::new(
|
||||
fs::OpenOptions::new()
|
||||
.read(true)
|
||||
@ -305,8 +320,8 @@ impl Layout {
|
||||
serde_yaml::from_reader(infile).map_err(Error::Yaml)
|
||||
}
|
||||
|
||||
pub fn build(self)
|
||||
-> Result<::layout::LayoutData, FormattingError>
|
||||
pub fn build<H: WarningHandler>(self, mut warning_handler: H)
|
||||
-> (Result<::layout::LayoutData, FormattingError>, H)
|
||||
{
|
||||
let button_names = self.views.values()
|
||||
.flat_map(|rows| {
|
||||
@ -323,7 +338,8 @@ impl Layout {
|
||||
create_action(
|
||||
&self.buttons,
|
||||
name,
|
||||
self.views.keys().collect()
|
||||
self.views.keys().collect(),
|
||||
&mut warning_handler,
|
||||
)
|
||||
)}).collect();
|
||||
|
||||
@ -368,13 +384,15 @@ 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 = generate_keymap(&button_states)?;
|
||||
let keymap_str = match generate_keymap(&button_states) {
|
||||
Err(e) => { return (Err(e), warning_handler) },
|
||||
Ok(v) => v,
|
||||
};
|
||||
|
||||
let button_states_cache = hash_map_map(
|
||||
button_states,
|
||||
@ -405,7 +423,8 @@ impl Layout {
|
||||
name,
|
||||
button_states_cache.get(name.into())
|
||||
.expect("Button state not created")
|
||||
.clone()
|
||||
.clone(),
|
||||
&mut warning_handler,
|
||||
))
|
||||
}).collect(),
|
||||
})
|
||||
@ -414,116 +433,148 @@ impl Layout {
|
||||
)})
|
||||
);
|
||||
|
||||
Ok(::layout::LayoutData {
|
||||
views: views,
|
||||
keymap_str: {
|
||||
CString::new(keymap_str)
|
||||
.expect("Invalid keymap string generated")
|
||||
},
|
||||
})
|
||||
(
|
||||
Ok(::layout::LayoutData {
|
||||
views: views,
|
||||
keymap_str: {
|
||||
CString::new(keymap_str)
|
||||
.expect("Invalid keymap string generated")
|
||||
},
|
||||
}),
|
||||
warning_handler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_action(
|
||||
fn create_action<H: WarningHandler>(
|
||||
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 filter_view_name(
|
||||
button_name: &str,
|
||||
view_name: String,
|
||||
view_names: &Vec<&String>
|
||||
) -> String {
|
||||
if view_names.contains(&&view_name) {
|
||||
view_name
|
||||
} else {
|
||||
eprintln!(
|
||||
"Button {} switches to missing view {}",
|
||||
button_name,
|
||||
view_name
|
||||
);
|
||||
"base".into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
enum SubmitData {
|
||||
Action(Action),
|
||||
Text(String),
|
||||
Keysym(String),
|
||||
};
|
||||
|
||||
match &symbol_meta.action {
|
||||
Some(Action::SetView(view_name)) => ::action::Action::SetLevel(
|
||||
filter_view_name(name, view_name.clone(), &view_names)
|
||||
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>(
|
||||
button_name: &str,
|
||||
view_name: String,
|
||||
view_names: &Vec<&String>,
|
||||
warning_handler: &mut H,
|
||||
) -> String {
|
||||
if view_names.contains(&&view_name) {
|
||||
view_name
|
||||
} else {
|
||||
warning_handler.handle(&format!("Button {} switches to missing view {}",
|
||||
button_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,
|
||||
)
|
||||
),
|
||||
Some(Action::Locking {
|
||||
SubmitData::Action(Action::Locking {
|
||||
lock_view, unlock_view
|
||||
}) => ::action::Action::LockLevel {
|
||||
lock: filter_view_name(name, lock_view.clone(), &view_names),
|
||||
lock: filter_view_name(
|
||||
name,
|
||||
lock_view.clone(),
|
||||
&view_names,
|
||||
warning_handler,
|
||||
),
|
||||
unlock: filter_view_name(
|
||||
name,
|
||||
unlock_view.clone(),
|
||||
&view_names
|
||||
&view_names,
|
||||
warning_handler,
|
||||
),
|
||||
},
|
||||
Some(Action::ShowPrefs) => ::action::Action::ShowPreferences,
|
||||
None => ::action::Action::Submit {
|
||||
SubmitData::Action(
|
||||
Action::ShowPrefs
|
||||
) => ::action::Action::ShowPreferences,
|
||||
SubmitData::Keysym(keysym) => ::action::Action::Submit {
|
||||
text: None,
|
||||
keys: keysyms.into_iter().map(::action::KeySym).collect(),
|
||||
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
|
||||
},
|
||||
}
|
||||
)),
|
||||
},
|
||||
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(
|
||||
fn create_button<H: WarningHandler>(
|
||||
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");
|
||||
@ -539,6 +590,18 @@ fn create_button(
|
||||
} 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())
|
||||
};
|
||||
@ -548,7 +611,7 @@ fn create_button(
|
||||
if outlines.contains_key(outline) {
|
||||
outline.clone()
|
||||
} else {
|
||||
eprintln!("Outline named {} does not exist! Using default for button {}", outline, name);
|
||||
warning_handler.handle(&format!("Outline named {} does not exist! Using default for button {}", outline, name));
|
||||
"default".into()
|
||||
}
|
||||
}
|
||||
@ -558,7 +621,9 @@ fn create_button(
|
||||
let outline = outlines.get(&outline_name)
|
||||
.map(|outline| (*outline).clone())
|
||||
.unwrap_or_else(|| {
|
||||
eprintln!("No default outline defied Using 1x1!");
|
||||
warning_handler.handle(
|
||||
&format!("No default outline defined! Using 1x1!")
|
||||
);
|
||||
Outline {
|
||||
bounds: Bounds { x: 0f64, y: 0f64, width: 1f64, height: 1f64 },
|
||||
}
|
||||
@ -585,6 +650,14 @@ 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!(
|
||||
@ -599,6 +672,7 @@ mod tests {
|
||||
icon: None,
|
||||
keysym: None,
|
||||
action: None,
|
||||
text: None,
|
||||
label: Some("test".into()),
|
||||
outline: None,
|
||||
}
|
||||
@ -656,7 +730,7 @@ mod tests {
|
||||
fn test_layout_punctuation() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key1.yaml"))
|
||||
.unwrap()
|
||||
.build()
|
||||
.build(PanicWarn).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -671,7 +745,7 @@ mod tests {
|
||||
fn test_layout_unicode() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key2.yaml"))
|
||||
.unwrap()
|
||||
.build()
|
||||
.build(PanicWarn).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -687,7 +761,7 @@ mod tests {
|
||||
fn test_layout_unicode_multi() {
|
||||
let out = Layout::from_file(PathBuf::from("tests/layout_key3.yaml"))
|
||||
.unwrap()
|
||||
.build()
|
||||
.build(PanicWarn).0
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
out.views["base"]
|
||||
@ -702,7 +776,7 @@ mod tests {
|
||||
#[test]
|
||||
fn parsing_fallback() {
|
||||
assert!(Layout::from_resource(FALLBACK_LAYOUT_NAME)
|
||||
.and_then(|layout| layout.build().map_err(LoadError::BadKeyMap))
|
||||
.map(|layout| layout.build(PanicWarn).0.unwrap())
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
@ -742,18 +816,20 @@ mod tests {
|
||||
".".into() => ButtonMeta {
|
||||
icon: None,
|
||||
keysym: None,
|
||||
text: None,
|
||||
action: None,
|
||||
label: Some("test".into()),
|
||||
outline: None,
|
||||
}
|
||||
},
|
||||
".",
|
||||
Vec::new()
|
||||
Vec::new(),
|
||||
&mut PanicWarn,
|
||||
),
|
||||
::action::Action::Submit {
|
||||
text: None,
|
||||
text: Some(CString::new(".").unwrap()),
|
||||
keys: vec!(::action::KeySym("U002E".into())),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,15 @@ 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
|
||||
@ -58,7 +67,8 @@ pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
|
||||
key_names: C
|
||||
) -> HashMap<String, u32> {
|
||||
HashMap::from_iter(
|
||||
key_names.into_iter()
|
||||
// sort to remove a source of indeterminism in keycode assignment
|
||||
sorted(key_names.into_iter())
|
||||
.map(|name| String::from(name))
|
||||
.zip(9..)
|
||||
)
|
||||
|
||||
@ -24,5 +24,7 @@ mod outputs;
|
||||
mod popover;
|
||||
mod resources;
|
||||
mod submission;
|
||||
mod util;
|
||||
mod style;
|
||||
pub mod tests;
|
||||
pub mod util;
|
||||
mod xdg;
|
||||
|
||||
@ -58,7 +58,7 @@ rslibs = custom_target(
|
||||
output: ['librs.a'],
|
||||
install: false,
|
||||
console: true,
|
||||
command: [cargo_script, '@OUTPUT@', 'build']
|
||||
command: [cargo_build] + cargo_build_flags + ['@OUTPUT@', '--lib']
|
||||
)
|
||||
|
||||
build_rstests = custom_target(
|
||||
@ -72,14 +72,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,20 +97,7 @@ libsqueekboard = static_library('libsqueekboard',
|
||||
'-DEEK_COMPILATION=1'],
|
||||
)
|
||||
|
||||
# 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',
|
||||
squeekboard = executable('squeekboard',
|
||||
'server-main.c',
|
||||
wl_proto_sources,
|
||||
squeekboard_resources,
|
||||
@ -124,3 +111,17 @@ squeekboard = executable('squeekboard-real',
|
||||
'-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,
|
||||
)
|
||||
|
||||
115
src/popover.rs
115
src/popover.rs
@ -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,8 +18,11 @@ 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;
|
||||
|
||||
@ -49,6 +52,44 @@ 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 {
|
||||
@ -88,12 +129,15 @@ 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 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);
|
||||
}
|
||||
}
|
||||
let current = (kind.clone(), name.clone());
|
||||
let inputs = variants::get_tuples(inputs).into_iter()
|
||||
.filter(|t| t != ¤t);
|
||||
let inputs = vec![(kind, name)].into_iter()
|
||||
.chain(inputs).collect();
|
||||
settings.set_value(
|
||||
"sources",
|
||||
&variants::ArrayPairString(inputs).to_variant()
|
||||
);
|
||||
settings.apply();
|
||||
}
|
||||
|
||||
@ -103,7 +147,6 @@ 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()
|
||||
@ -152,39 +195,35 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
|
||||
height: position.width.floor() as i32,
|
||||
});
|
||||
|
||||
let initial_state = input_names[current].to_variant();
|
||||
if let Some(current_name) = input_names.get(0) {
|
||||
let current_name = current_name.to_variant();
|
||||
|
||||
let layout_action = gio::SimpleAction::new_stateful(
|
||||
"layout",
|
||||
Some(initial_state.type_()),
|
||||
&initial_state,
|
||||
);
|
||||
let layout_action = gio::SimpleAction::new_stateful(
|
||||
"layout",
|
||||
Some(current_name.type_()),
|
||||
¤t_name,
|
||||
);
|
||||
|
||||
let action_group = gio::SimpleActionGroup::new();
|
||||
action_group.add_action(&layout_action);
|
||||
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));
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ 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")),
|
||||
@ -42,6 +43,7 @@ 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")),
|
||||
];
|
||||
|
||||
|
||||
7
src/style.h
Normal file
7
src/style.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef __STYLE_H
|
||||
#define __STYLE_H
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
GtkCssProvider *squeek_load_style();
|
||||
|
||||
#endif
|
||||
124
src/style.rs
Normal file
124
src/style.rs
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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: >k::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(),
|
||||
}
|
||||
)
|
||||
}
|
||||
78
src/tests.rs
Normal file
78
src/tests.rs
Normal file
@ -0,0 +1,78 @@
|
||||
/*! 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");
|
||||
}
|
||||
}
|
||||
21
src/util.rs
21
src/util.rs
@ -177,6 +177,27 @@ 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::*;
|
||||
|
||||
@ -49,7 +49,7 @@ endforeach
|
||||
# and the need to call it manually
|
||||
foreach layout : [
|
||||
'us', 'us_wide',
|
||||
'de',
|
||||
'de', 'de_wide',
|
||||
'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
|
||||
|
||||
|
||||
@ -12,4 +12,4 @@ for DIR in ${DIRS}; do
|
||||
fi;
|
||||
done;
|
||||
|
||||
exec @bindir@/squeekboard-real
|
||||
exec $(which squeekboard)
|
||||
Reference in New Issue
Block a user