Simple utility to turn swipes into words -- "plugin" for wvkbd to enable swipe-typing under wayland SXMO.
-rw-r--r--.gitignore5
-rw-r--r--Makefile37
-rw-r--r--README.md67
-rwxr-xr-xaddWord.sh3
-rwxr-xr-xcompletelyTypeWord.sh2
-rw-r--r--functions.sh6
-rwxr-xr-xmakeDir.sh10
-rw-r--r--map.qwerty.noapos.tsv (renamed from map.qwerty.simplegrid.tsv)2
-rw-r--r--mapScore.1.scd15
-rw-r--r--mapScore.c80
-rwxr-xr-xmapScore.py24
-rw-r--r--swipeGuess.1.scd29
-rw-r--r--swipeGuess.c86
-rwxr-xr-xswipeGuess.sh34
14 files changed, 327 insertions, 73 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1d9369d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+mapScore
+swipeGuess
+swipeGuess.1
+mapScore.1
+words-qwerty-en
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..eeb0d6c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+PREFIX?=/usr/local
+
+all: swipeGuess mapScore docs words-qwerty-en
+swipeGuess: swipeGuess.c
+ $(CC) swipeGuess.c -o swipeGuess
+mapScore: mapScore.c
+ $(CC) mapScore.c -o mapScore
+docs: swipeGuess.1 mapScore.1
+swipeGuess.1: swipeGuess.1.scd
+ scdoc < swipeGuess.1.scd > $@.tmp
+ mv $@.tmp $@
+mapScore.1: mapScore.1.scd
+ scdoc < mapScore.1.scd > $@.tmp
+ mv $@.tmp $@
+
+words-qwerty-en: /usr/share/dict/american-english mapScore
+ grep .. /usr/share/dict/american-english | ./mapScore map.qwerty.noapos.tsv bee | sort -nr | cut -f2 > words-qwerty-en
+/usr/share/dict/american-english:
+ apk add words-en
+
+test: words-qwerty-en swipeGuess
+ test "`echo "asdfghjkl" | ./swipeGuess words-qwerty-en`" = "all"
+ test "`echo "dfghuiokmnhyt" | ./swipeGuess words-qwerty-en 1 "'"`" = "don't"
+ test "`echo "tyuiopoiuytrewertyuiuytrer" | ./swipeGuess words-qwerty-en 2`" = "`printf "typewriter\ttorturer"`"
+
+install: all
+ install -m755 swipeGuess -D -t "$(DESTDIR)/$(PREFIX)/bin/"
+ install -m755 mapScore -D -t "$(DESTDIR)/$(PREFIX)/bin/"
+ install -m644 swipeGuess.1 -D -t "$(DESTDIR)/$(PREFIX)/share/man/man1/"
+ install -m644 mapScore.1 -D -t "$(DESTDIR)/$(PREFIX)/share/man/man1/"
+ install -m644 words-qwerty-en -D -t "$(DESTDIR)/$(PREFIX)/share/swipeGuess/words/"
+uninstall:
+ rm -f "$(DESTDIR)/$(PREFIX)/bin/swipeGuess"
+ rm -f "$(DESTDIR)/$(PREFIX)/bin/mapScore"
+ rm -f "$(DESTDIR)/$(PREFIX)/share/man/man1/swipeGuess.1"
+ rm -f "$(DESTDIR)/$(PREFIX)/share/man/man1/mapScore.1"
+ rm -f "$(DESTDIR)/$(PREFIX)/share/swipeGuess/words/words-qwerty-en"
diff --git a/README.md b/README.md
index 5dfadda..1137291 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,15 @@ swipeGuess is a completion program intended to be used as a plugin for touchscre
For each line input from stdin, it looks through a wordlist and outputs the first possible match for that gesture.
-it's run like `input-program | swipeGuess.sh wordlist | output-program`
+it's run like `input-program | swipeGuess wordlist.txt | output-program`
+
+swipeGuess also provides options for returning multiple results and ignoring certain characters. See [the man page](https://git.sr.ht/~earboxer/swipeGuess/tree/master/item/swipeGuess.1.scd) for more information.
## input-program
The input program should output a stream of letters "swiped through", then a newline.
-There's a WIP Merge Request towards proycon's wvkbd for an example of this.
-
-=> https://github.com/proycon/wvkbd/pull/1
+This is supported by [wvkbd](https://github.com/proycon/wvkbd) since version 0.6 and [phosh-osk-stub](https://gitlab.gnome.org/guidog/phosh-osk-stub) since 0.28.0.
## wordlist
@@ -27,6 +27,7 @@ Good starting points can be found on the web, based on your language.
* https://raw.githubusercontent.com/dwyl/english-words/master/words.txt - 466,550 words, including many proper nouns like names.
* https://norvig.com/ngrams/count_1w.txt - 333,333 words, abbreviations, etc, sorted by popularity.
* https://www.keithv.com/software/wlist/index.php - page with several links providing intersections between english word lists (26,680 - 1,516,998 words)
+* Your computer may have a list installed already in /usr/share/dict/, or one may be installable through your package manager.
### sorting tips
@@ -38,18 +39,18 @@ ls $PATH | awk '{ print length, $0 }' | sort -nr | cut -d" " -f2- > binsSorted.t
```
-Smarter sorting would be keyboard-layout aware. mapscore.py can do that for you
+Smarter sorting would be keyboard-layout aware. mapScore can do that for you
```sh
-./mapScore.py map.tsv <words.txt | sort -nr | cut -f2 > wordsSorted.txt
+./mapScore map.tsv <words.txt | sort -nr | cut -f2 > wordsSorted.txt
```
-map.tsv uses tabs and newlines to create the grid-based layout. See `map.qwerty.simplegrid.tsv` for a sample of how to format this file.
+map.tsv uses tabs and newlines to create the grid-based layout. See `map.qwerty.noapos.tsv` for a sample of how to format this file.
-### alternate formats
+If your keys are in a hexagonal layout, use mapScore like
+`./mapScore map.simple.tsv bee`.
-Alternatively, for performance, you can use a directory with the following format: each file is named with the first and last letters of the contained words.
-The script `makeDir.sh` is provided to help create these.
+(`mapScore.py` was the old version of this, and probably should be removed, being slower and with less features.).
## output-program
@@ -57,7 +58,45 @@ The script `makeDir.sh` is provided to help create these.
# Installation/Usage with wvkbd
-1. Be using a wayland-based graphical shell (such as sway)
-2. copy swipeGuess.sh and completelyTypeWord.sh into your $PATH (`~/.local/bin/` or `/usr/local/bin/` for example)
-3. `wvkbd-mobintl -O | swipeGuess.sh /path/to/words.txt | completelyTypeWord.sh`
- * In SXMO, `KEYBOARD_ARGS='-O | swipeGuess.sh /path/to/words.txt | completelyTypeWord.sh'` can be added to your ~/.profile to enable this (effective on restart).
+1. Be using a wayland-based graphical shell (such as sway), and have wtype installed.
+2. Compile with your favorite C compiler: `gcc swipeGuess.c -o swipeGuess`.
+2. copy swipeGuess and completelyTypeWord.sh into your $PATH (`~/.local/bin/` or `/usr/local/bin/` for example)
+3. `wvkbd-mobintl -O | swipeGuess /path/to/words.txt | completelyTypeWord.sh`
+ * In SXMO, `KEYBOARD_ARGS='-O | swipeGuess /path/to/words.txt | completelyTypeWord.sh'` can be added to your ~/.profile to enable this (effective on restart).
+
+# Usage with phosh-osk-stub
+
+```
+gsettings set sm.puri.phosh.osk osk-features "['key-drag']"
+gsettings set sm.puri.phosh.osk.Completers.Pipe command 'swipeGuess /usr/share/swipeGuess/words/words-qwerty-en 5 | tr "\t" "\n"'
+gsettings set sm.puri.phosh.osk.Completers default pipe
+gsettings set sm.puri.phosh.osk completion-mode "['manual','hint']"
+```
+
+## Multiple suggestions
+
+phosh-osk-stub's pipe completer accepts multiple suggestions, newline separated. This can be scripted like
+
+```
+gsettings set sm.puri.phosh.osk.Completers.Pipe command 'sh -c "swipeGuess /usr/share/swipeGuess/words/words-qwerty-en 5 | tr \"\t\" \"\n\""'
+```
+
+# Extended information
+
+[SwipeBehaviors](https://git.sr.ht/~earboxer/SwipeBehaviors) is a project that uses swipeGuess and provides more advanced functionality, like presenting several choices that can be picked with [suggpicker](https://git.sr.ht/~earboxer/suggpicker).
+
+# Contributing
+
+swipeGuess is maintained by [Zach DeCook](https://zachdecook.com/), who may or may not be reached directly for related inquiries.
+
+Patches and long-form discussions for this project are also accepted on my swipeKeyboard mailing list on sourcehut: ([email](mailto:~earboxer/swipeKeyboard@lists.sr.ht)/[archive](https://lists.sr.ht/~earboxer/swipeKeyboard)).
+
+e.g.
+
+```
+git config sendemail.to '~earboxer/swipeKeyboard@lists.sr.ht'
+git config format.subjectPrefix 'PATCH swipeGuess'
+git send-email HEAD~1
+```
+
+(See `man git-send-email` or https://git-send-email.io for more information)
diff --git a/addWord.sh b/addWord.sh
new file mode 100755
index 0000000..9c4168c
--- /dev/null
+++ b/addWord.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+cat ~/.local/share/sxmo/words.txt - > /tmp/unsortedwords.txt
+mapScore ~/.local/share/sxmo/keyboard.map.tsv </tmp/unsortedwords.txt | sort -nr | cut -f2 > ~/.local/share/sxmo/words.txt
diff --git a/completelyTypeWord.sh b/completelyTypeWord.sh
index de13a92..4c862c4 100755
--- a/completelyTypeWord.sh
+++ b/completelyTypeWord.sh
@@ -1,5 +1,5 @@
#!/bin/sh
type=wtype
while read -r word; do
- test "${word:1}" && $type "${word:1}"
+ test "${word#?}" && $type "${word#?}"
done
diff --git a/functions.sh b/functions.sh
deleted file mode 100644
index 440219b..0000000
--- a/functions.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-firstLetter(){
- echo "$1"|grep -o '^.'
-}
-lastLetter(){
- echo "$1"|grep -o '.$'
-}
diff --git a/makeDir.sh b/makeDir.sh
deleted file mode 100755
index 5c2d5c0..0000000
--- a/makeDir.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-dir="$1"
-mkdir -p "$dir"
-rm "$dir/"*
-source "$(dirname "$0")/functions.sh"
-while read -r line; do
- fl=$(firstLetter "$line")
- ll=$(lastLetter "$line")
- echo "$line" >> "$dir/$fl$ll"
-done
diff --git a/map.qwerty.simplegrid.tsv b/map.qwerty.noapos.tsv
index 542ec1b..905bb74 100644
--- a/map.qwerty.simplegrid.tsv
+++ b/map.qwerty.noapos.tsv
@@ -1,4 +1,4 @@
q w e r t y u i o p
-a s d f g h j k l '
+a s d f g h j k l
z x c v b n m
, .
diff --git a/mapScore.1.scd b/mapScore.1.scd
new file mode 100644
index 0000000..8bf97b6
--- /dev/null
+++ b/mapScore.1.scd
@@ -0,0 +1,15 @@
+mapScore(1)
+
+# NAME
+mapScore - score words by path length through 2D letter map
+
+# SYNOPSIS
+
+*mapScore* _map.tsv_ [_bee_] < _words.txt_ > _scored_words.tsv_
+
+# OPTIONS
+_bee_
+ Calculates score differently by treating _map.tsv_ as a hex-grid (even lines indented halfway)
+
+# SEE ALSO
+*swipeGuess*(1)
diff --git a/mapScore.c b/mapScore.c
new file mode 100644
index 0000000..1135c2b
--- /dev/null
+++ b/mapScore.c
@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#define BUFSIZE 1024
+char buffer[BUFSIZE];
+// TODO: Support UTF-8.
+int map[256][3];
+
+void makeMap(FILE *f) {
+ int x = 0; int y = 0;
+ int c = 0;
+ while ((c = fgetc(f)) != EOF) {
+ switch (c) {
+ case '\t': x++; break;
+ case '\n': y++; x=0; break;
+ default:
+ explicit_label:
+ map[c][0] = x;
+ map[c][1] = y;
+ map[c][2] = 1;
+ c = toupper(c);
+ if (c < 256 && map[c][2] == 0) goto explicit_label;
+ break;
+ }
+ }
+}
+
+int scoreWord(char *word, int (*fun)(int*,int*)) {
+ char *c = word;
+ int score = 0;
+ char *p = c;
+ for (c++;*c;c++) {
+ if (*c == '\n') break;
+ // pass over ignore unset chars.
+ if(map[(unsigned char)*c][3] == 0) continue;
+ score += (*fun)(map[(unsigned char)*p],map[(unsigned char)*c]);
+ p=c;
+ }
+ return score;
+}
+
+int manhattanDist(int *p1,int *p2) {
+ return abs(p1[0]-p2[0]) + abs(p1[1]-p2[1]);
+}
+int beeDist(int *p1, int *p2) {
+ const int yd = p1[1] - p2[1];
+ const int xd = p1[0] - p2[0];
+ const int ayd = abs(yd);
+ int axd = abs(xd);
+ if (ayd % 2) {
+ if ((p1[1]/2 + p2[1]/2) % 2) {
+ if (xd * yd > 0) axd--;
+ } else {
+ if (xd * yd < 0) axd--;
+ }
+ }
+ axd -= ayd / 2;
+ if (axd < 0) axd = 0;
+ return axd + ayd;
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Usage: mapScore map.tsv <words.txt >scoredWords.tsv\n");
+ return 1;
+ }
+
+int (*fun)(int*,int*) = &manhattanDist;
+ if (argc >= 3) {
+ fun = &beeDist;
+ }
+
+ FILE *f = fopen(argv[1], "r");
+ makeMap(f);
+ fclose(f);
+ while (fgets(buffer, BUFSIZE, stdin)) {
+ printf("%d\t%s", scoreWord(buffer, fun), buffer);
+ }
+}
diff --git a/mapScore.py b/mapScore.py
index 154142f..430feb8 100755
--- a/mapScore.py
+++ b/mapScore.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import sys
+import math
def makeMap(filename):
l= [(0,0) for x in range(0,127)]
@@ -10,25 +11,34 @@ def makeMap(filename):
if c == '\t':
x=x+1
elif c == '\n':
- x=0;y=y+1
+ y=y+1
+ x = (y%2)/2
elif c:
l[ord(c)] =(x,y)
else:
break
return l
-def scoreWord(word,mm):
+def scoreWord(word,mm,fun):
pc=word[0] or ' '
s=0
for c in word:
- #manhattan dist * 10
- s+=abs(mm[ord(pc)][0]-mm[ord(c)][0])*10
- s+=abs(mm[ord(pc)][1]-mm[ord(c)][1])*10
- s+=1 # longer word bias
+ s += fun(mm[ord(pc)],mm[ord(c)])
pc=c
return s
+def manhattan_dist(p1, p2):
+ return abs(int(p1[0])-int(p2[0])) + abs(p1[1]-p2[1])
+
+def bee_dist(p1, p2):
+ yd = abs(p1[1] - p2[1])
+ xd = max(0, math.floor(abs(p1[0]-p2[0])) - yd//2)
+ return xd+yd
+
def main(argv):
+ fun = manhattan_dist
+ if len(argv) >= 3 and argv[2] == 'bee':
+ fun = bee_dist
mm=makeMap(argv[1])
while 1:
line = sys.stdin.readline()
@@ -36,7 +46,7 @@ def main(argv):
break
w=line.strip()
if w:
- print(scoreWord(w,mm),w,sep='\t')
+ print(scoreWord(w,mm,fun),w,sep='\t')
if __name__ == '__main__':
main(sys.argv)
diff --git a/swipeGuess.1.scd b/swipeGuess.1.scd
new file mode 100644
index 0000000..25c58bb
--- /dev/null
+++ b/swipeGuess.1.scd
@@ -0,0 +1,29 @@
+swipeGuess(1)
+
+# NAME
+swipeGuess - infer the word you meant from a swipe
+
+# SYNOPSIS
+
+*swipeGuess* _words.txt_ [_n_] [_ignorechars_] < _input_swipes.txt_ > _output_guesses.tsv_
+
+wvkbd -O | *swipeGuess* _words.txt_ | completelyTypeWord.sh
+
+wvkbd -O | *swipeGuess* _words.txt_ 5 | suggpicker | completelyTypeWord.sh
+
+wvkbd -O | *swipeGuess* _words.txt_ 1 "'" | completelyTypeWord.sh
+
+# OPTIONS
+_n_
+ Number of results that should be returned (default 1).
+
+_ignorechars_
+ Characters that exist in _words.txt_, but won't exist in your swipes, that you still want to type.
+
+# DESCRIPTION
+
+For each line of stdin, outputs the first _n_ results from _words.txt_ which could be an ordered subset of that line, separated by tabs.
+If _ignorechars_ is given, the results may have these characters inside them even when not present in the input.
+
+# SEE ALSO
+*suggpicker*(1) *wvkbd*(1) *SwipeBehavior*(7)
diff --git a/swipeGuess.c b/swipeGuess.c
new file mode 100644
index 0000000..b13e5b5
--- /dev/null
+++ b/swipeGuess.c
@@ -0,0 +1,86 @@
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#define BUFSIZE 1024
+char wordBuff[BUFSIZE];
+char swipeBuff[BUFSIZE];
+bool ignorechars[256] = {false};
+
+// fgets, but without the newline.
+char *fgetst(char *restrict s, int size, FILE *restrict stream) {
+ char *r = fgets(s, size, stream);
+ if (r) {
+ char * sp = s;
+ for(; sp[0] != '\0'; sp++) {
+ if (sp[0] == '\n') {
+ sp[0] = '\0';
+ }
+ }
+ }
+ return r;
+}
+
+bool charcmp(char a, char b) {
+ return tolower(a) == tolower(b);
+}
+
+bool swipeCompare(char *swipe, char *word) {
+ if (! charcmp(swipe[0], word[0])) {
+ return false;
+ }
+ char *swipeP = swipe;
+ char *wordP = word;
+ wordP++;
+ bool lastMatch = false;
+ for(swipeP++; swipeP[0]; swipeP++) {
+ lastMatch = false;
+ while (charcmp(swipeP[0], wordP[0]) || ignorechars[wordP[0]]) {
+ wordP++;
+ lastMatch = true;
+ }
+ }
+
+ if (wordP[0] == '\0' && lastMatch) {
+ return true;
+ }
+ return false;
+}
+void query(FILE *wordFile, char *swipe, int n) {
+ // TODO: UTF-8 len checking?
+ if (swipe[0] == '\0' || swipe[1] == '\0') return;
+ fseek(wordFile, 0, SEEK_SET);
+ int found = 0;
+
+ while (found < n && fgetst(wordBuff, BUFSIZE, wordFile)) {
+ if (swipeCompare(swipe, wordBuff)) {
+ if (found > 0) printf("\t");
+ printf("%s", wordBuff);
+ found++;
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "Usage: swipeGuess words.txt [n]\n");
+ exit(1);
+ }
+ int n = 1;
+ if (argc >= 3) {
+ n = atoi(argv[2]);
+ }
+ if (argc >= 4) {
+ for(int i=0; argv[3][i];i++){
+ ignorechars[(int)argv[3][0]] = true;
+ }
+ }
+ FILE *wordFile = fopen(argv[1], "r");
+ while (fgetst(swipeBuff, BUFSIZE, stdin)) {
+ query(wordFile, swipeBuff, n);
+ printf("\n");
+ fflush(stdout);
+ }
+ fclose(wordFile);
+}
diff --git a/swipeGuess.sh b/swipeGuess.sh
deleted file mode 100755
index 71e811d..0000000
--- a/swipeGuess.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/functions.sh"
-
-swipeToQuery(){
- swipe=$(echo "$1" | tr -d ".\*\"\\^$\(\)")
- printf '^'
- printf '%s\\+%s' "${swipe:0:1}" "${swipe:1:1}"
- if test "${swipe:2}"; then
- printf "${swipe:2}" |grep -o . | xargs -I{} printf '*%s' "{}"
- fi
- printf '\+$'
-}
-
-query(){
- swipe="$2"
- wordlist="$1"
- wordfile="$wordlist"
- if test -d "$wordlist"; then
- wordfile=/dev/null
- fl=$(firstLetter "$swipe")
- ll=$(lastLetter "$swipe")
- test -f "$wordlist/$fl$ll" && wordfile="$wordlist/$fl$ll"
- fi
-
- query=$(swipeToQuery "$swipe")
- echo "query: $query" > /dev/stderr
- # -m 1: just give first result
- grep -i -m 1 "$query" "$wordfile"
-}
-
-while read -r line; do
- test "$line" && query "$1" "$line" && printf '\n'
-done