aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile9
-rw-r--r--README.md3
-rw-r--r--go.mod8
-rw-r--r--go.sum11
-rw-r--r--incantation.toml5
-rw-r--r--key_maps.go44
-rw-r--r--keyboard_reader.c80
-rw-r--r--keyboard_reader.h6
-rw-r--r--keyboard_writer.c39
-rw-r--r--keyboard_writer.h7
-rw-r--r--main.go115
12 files changed, 328 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8671268
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+incantation
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9ceb6d5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+.PHONY: incantation
+
+incantation:
+ go build
+
+clean:
+ rm incantation
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ec2e8ed
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Incantation
+
+A Linux/X11 based text expander.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..ee9f086
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,8 @@
+module incantation
+
+go 1.12
+
+require (
+ github.com/BurntSushi/toml v0.3.1
+ github.com/sirupsen/logrus v1.4.2
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..e7b7b62
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,11 @@
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/incantation.toml b/incantation.toml
new file mode 100644
index 0000000..b765f25
--- /dev/null
+++ b/incantation.toml
@@ -0,0 +1,5 @@
+[bindings]
+
+[bindings.1]
+keypress = ",,hw"
+text = "hello world"
diff --git a/key_maps.go b/key_maps.go
new file mode 100644
index 0000000..699e9ee
--- /dev/null
+++ b/key_maps.go
@@ -0,0 +1,44 @@
+package main
+
+var x11Map = map[string]string{
+ " ": "space",
+ "!": "exclam",
+ "@": "at",
+ "#": "numbersign",
+ "$": "dollar",
+ "%": "percent",
+ "^": "asciicircum",
+ "&": "ampersand",
+ "*": "asterisk",
+ "(": "parenleft",
+ ")": "parenright",
+ "~": "asciitilde",
+ "`": "grave",
+ "-": "minus",
+ "_": "underscore",
+ "=": "equal",
+ "+": "plus",
+ "[": "bracketleft",
+ "]": "bracketright",
+ "{": "braceleft",
+ "}": "braceright",
+ "\\": "backslash",
+ "|": "bar",
+ ";": "semicolon",
+ ":": "colon",
+ "'": "apostrophe",
+ "\"": "quotedbl",
+ ",": "comma",
+ "<": "less",
+ ".": "period",
+ ">": "greater",
+ "/": "slash",
+ "?": "question",
+}
+
+var needsShift = []string{
+ "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+",
+ "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "|",
+ "A", "S", "D", "F", "G", "H", "J", "K", "L", "L", ":", "\"",
+ "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?",
+}
diff --git a/keyboard_reader.c b/keyboard_reader.c
new file mode 100644
index 0000000..319f702
--- /dev/null
+++ b/keyboard_reader.c
@@ -0,0 +1,80 @@
+// xkbcat: Logs X11 keypresses, globally.
+
+#include <X11/XKBlib.h>
+#include <X11/extensions/XInput2.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+const char * DEFAULT_DISPLAY = ":0";
+const bool DEFAULT_PRINT_UP = false;
+
+char * get_key() {
+
+ const char * hostname = DEFAULT_DISPLAY;
+ bool printKeyUps = DEFAULT_PRINT_UP;
+
+ // Set up X
+ Display * disp = XOpenDisplay(hostname);
+ if (NULL == disp) {
+ fprintf(stderr, "Cannot open X display: %s\n", hostname);
+ exit(1);
+ }
+
+ // Test for XInput 2 extension
+ int xi_opcode;
+ int queryEvent, queryError;
+ if (! XQueryExtension(disp, "XInputExtension", &xi_opcode,
+ &queryEvent, &queryError)) {
+ // XXX Test version >=2
+ fprintf(stderr, "X Input extension not available\n"); return "";
+ }
+
+ // Register events
+ Window root = DefaultRootWindow(disp);
+ XIEventMask m;
+ m.deviceid = XIAllMasterDevices;
+ m.mask_len = XIMaskLen(XI_LASTEVENT);
+ m.mask = calloc(m.mask_len, sizeof(char));
+ XISetMask(m.mask, XI_RawKeyPress);
+ XISetMask(m.mask, XI_RawKeyRelease);
+ XISelectEvents(disp, root, &m, 1);
+ XSync(disp, false);
+ free(m.mask);
+
+ while (1) { // Forever
+ XEvent event;
+ XGenericEventCookie *cookie = (XGenericEventCookie*)&event.xcookie;
+ XNextEvent(disp, &event);
+
+ if (XGetEventData(disp, cookie) &&
+ cookie->type == GenericEvent &&
+ cookie->extension == xi_opcode)
+ {
+ switch (cookie->evtype)
+ {
+ case XI_RawKeyRelease: if (!printKeyUps) continue;
+ case XI_RawKeyPress: {
+ XIRawEvent *ev = cookie->data;
+
+ // Ask X what it calls that key
+ KeySym s = XkbKeycodeToKeysym(disp, ev->detail, 0, 0);
+ if (NoSymbol == s) continue;
+ char *str = XKeysymToString(s);
+ if (NULL == str) continue;
+
+
+ if (printKeyUps) printf("%s", cookie->evtype == XI_RawKeyPress ? "+" : "-");
+ char *ret = malloc(1000);
+ sprintf(ret, "%s", str);
+ XCloseDisplay(disp);
+ return ret;
+ break;
+ }
+ }
+ }
+ fflush(stdout);
+ }
+}
diff --git a/keyboard_reader.h b/keyboard_reader.h
new file mode 100644
index 0000000..4eadc37
--- /dev/null
+++ b/keyboard_reader.h
@@ -0,0 +1,6 @@
+#ifndef KEYBOARD_READER_H
+#define KEYBOARD_READER_H
+
+char * get_key();
+
+#endif
diff --git a/keyboard_writer.c b/keyboard_writer.c
new file mode 100644
index 0000000..66196ba
--- /dev/null
+++ b/keyboard_writer.c
@@ -0,0 +1,39 @@
+#include "keyboard_writer.h"
+#include <stdio.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/extensions/XTest.h>
+
+
+int x11_key(char *zh){
+ Display *dpy;
+ dpy = XOpenDisplay(NULL);
+
+ KeySym sym = XStringToKeysym(zh);
+ KeyCode code = XKeysymToKeycode(dpy,sym);
+ XTestFakeKeyEvent(dpy, code, True, 1);
+ XTestFakeKeyEvent(dpy, code, False, 1);
+
+ XFlush( dpy );
+ XCloseDisplay( dpy );
+
+ return 0;
+}
+
+int x11_key_shift(char *zh){
+ Display *dpy;
+ dpy = XOpenDisplay(NULL);
+
+ KeySym sym = XStringToKeysym(zh);
+ KeyCode code = XKeysymToKeycode(dpy,sym);
+ XTestFakeKeyEvent(dpy, 50, True, 1);
+ XTestFakeKeyEvent(dpy, code, True, 1);
+ XTestFakeKeyEvent(dpy, code, False, 1);
+ XTestFakeKeyEvent(dpy, 50, False, 1);
+
+ XFlush( dpy );
+ XCloseDisplay( dpy );
+
+ return 0;
+}
+
diff --git a/keyboard_writer.h b/keyboard_writer.h
new file mode 100644
index 0000000..995615f
--- /dev/null
+++ b/keyboard_writer.h
@@ -0,0 +1,7 @@
+#ifndef KEYBOARD_WRITER_H
+#define KEYBOARD_WRITER_H
+
+int x11_key(char *zh);
+int x11_key_shift(char *zh);
+
+#endif
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..5a9c9ea
--- /dev/null
+++ b/main.go
@@ -0,0 +1,115 @@
+package main
+
+// #cgo linux LDFLAGS: -lX11 -lXtst -lXi
+// #include "keyboard_writer.h"
+// #include "keyboard_reader.h"
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "github.com/BurntSushi/toml"
+ "github.com/sirupsen/logrus"
+ "reflect"
+ "strings"
+ "time"
+)
+
+type tomlConfig struct {
+ Bindings map[string]binding
+}
+
+type binding struct {
+ Keypress string
+ Text string
+}
+
+func main() {
+ // Startup Load the config
+ config, err := get_config()
+ if err != nil {
+ logrus.Println(err)
+ return
+ }
+ for {
+ expansion := wait_for_autocomplete(config)
+ logrus.Println(fmt.Sprintf("executing: %s", expansion))
+ time.Sleep(100 * time.Millisecond)
+ for _, v := range expansion {
+ cs := C.CString(char_to_x11(string(v)))
+ if stringInSlice(string(v), needsShift) {
+ C.x11_key_shift(cs)
+ } else {
+ C.x11_key(cs)
+ }
+ }
+ }
+}
+
+func get_config() (tomlConfig, error) {
+ var config tomlConfig
+ if _, err := toml.DecodeFile("incantation.toml", &config); err != nil {
+ return config, errors.New("Can't load config file")
+ }
+ return config, nil
+}
+
+func wait_for_autocomplete(config tomlConfig) string {
+ max_length := 10
+ buffer := make([]string, max_length)
+ for {
+ key_press := x11_to_char(C.GoString(C.get_key()))
+ // Cut down length
+ if len(buffer) == max_length {
+ buffer = buffer[1:]
+ }
+ buffer = append(buffer, key_press)
+ logrus.Println(strings.Join(buffer, ", "))
+ // Check for expansion matches, windowing through buffer
+ for _, binding := range config.Bindings {
+ // Build kepress string into slice for slice comparison
+ keypress_slice := make([]string, 0)
+ for _, v := range binding.Keypress {
+ keypress_slice = append(keypress_slice, string(v))
+ }
+ for i := 0; i < len(buffer)-len(keypress_slice)+1; i++ {
+ tmp_buf := buffer[i : i+len(keypress_slice)]
+ if reflect.DeepEqual(tmp_buf, keypress_slice) {
+ // Erase the keypresses
+ for range binding.Keypress {
+ cs := C.CString("BackSpace")
+ C.x11_key(cs)
+ }
+ return binding.Text
+ }
+ }
+ }
+ }
+}
+
+func char_to_x11(code string) string {
+ for k, v := range x11Map {
+ if k == code {
+ return v
+ }
+ }
+ return code
+}
+
+func x11_to_char(code string) string {
+ for k, v := range x11Map {
+ if v == code {
+ return k
+ }
+ }
+ return code
+}
+
+func stringInSlice(a string, list []string) bool {
+ for _, b := range list {
+ if b == a {
+ return true
+ }
+ }
+ return false
+}