From 657331d4b9c9301484916c84a9f12612da23d7a7 Mon Sep 17 00:00:00 2001 From: Miles Alan Date: Sat, 18 Apr 2020 20:44:22 -0500 Subject: [PATCH] Initial commit --- LICENSE | 21 ++++ Makefile | 30 ++++++ README.md | 47 +++++++++ config.def.h | 27 ++++++ lisgd.c | 268 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 393 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.def.h create mode 100644 lisgd.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9046ebc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2020 Miles Alan + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e635b44 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +SRC = lisgd.c +OBJ = ${SRC:.c=.o} +LDFLAGS = -linput + +all: options lisgd + +options: + @echo lisgd build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + ${CC} -c ${CFLAGS} $< + +${OBJ}: config.h + +config.h: + cp config.def.h $@ + +lisgd: ${OBJ} + ${CC} -g -o $@ ${OBJ} ${LDFLAGS} + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f lisgd ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/lisgd + +clean: + rm -f config.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..1dc8236 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# lisgd + +Lisgd (libinput **synthetic** gesture daemon) lets you bind gestures based on +libinput touch events to run specific commands to execute. For example, +dragging left to right with one finger could execute a particular command +like launching a terminal. L-R, R-L, U-D, and D-U swipe gestures are +supported with 1 through n fingers. + +Unlike other libinput gesture daemons, lisgd uses touch events to +recognize **synthetic swipe gestures** rather than using the *libinput*'s +gesture events. The advantage of this is that the synthetic gestures +you define via lisgd can be used on touchscreens, which normal libinput +gestures don't support. + +This program was built for use on the [Pinephone](); however it could be +used in general for any device that supports touch events, like laptop +touchscreens or similar. You may want to adjust the threshold depending +on the device you're using. + +## Configuration +Configuration can be done in two ways: + +1. Through a suckless style `config.h`; see the `config.def.h` +2. Through commandline flags which override the default config.h values + +### Suckless-style config.h based configuration +Example `config.def.h` configuration -- e.g. copy this to `config.h`: + +### Commandline flags based configuration +Flags: +- **-g [fingers,start,end,command]**: Defines a gestures wherein fingers is a integer, start/end are {l,r,d,u}, and command is the command to execute + - Example: `lisgd -g "1,l,r,notify-send swiped lr" -g "1,r,l,noitfy-send swiped rl"` +- **-d [devicenodepath]**: Defines the dev filesystem device to monitor + - Example: `lisgd -d /dev/input/input1` +- **-t [threshold_units]**: Number of libinput units (number) minimum to recognize a gesture + - Example: `lisgd -t 400` +- **-v**: Verbose mode, useful for debugging + - Example: `lisgd -v` + +Full commandline-based configuration example: +``` +lisgd -d /dev/input/input1 -g "1,left,right,notify-send swiped lr" -t 200 -v +``` + +### TODO +- Diagnol swipe gestures +- Gestures recognition based on screenspace executed in diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..f9c24cd --- /dev/null +++ b/config.def.h @@ -0,0 +1,27 @@ +/* Minimum cutoff for a gestures to take effect */ +unsigned int threshold = 300; + +/* Verbose mode, 1 = enabled, 0 = disabled */ +int verbose = 0; + +/* Device libinput should read from */ +char *device = "/dev/input/event1"; + +/* Commands to execute upon recieving a swipe gesture */ +Gesture gestures[] = { + /* fingers start end command */ + { 1, Left, Right, "xdotool key --clearmodifiers Alt+Shift+h" }, + { 1, Right, Left, "xdotool key --clearmodifiers Alt+Shift+l" }, + + { 2, Left, Right, "xdotool key --clearmodifiers Alt+h" }, + { 2, Right, Left, "xdotool key --clearmodifiers Alt+l" }, + + { 2, Down, Up, "pidof svkbd-sxmo || svkbd-sxmo &" }, + { 2, Up, Down, "pkill -9 svkbd-sxmo" }, + + { 3, Down, Up, "sxmo_vol.sh up" }, + { 3, Up, Down, "sxmo_vol.sh down" }, + + { 4, Down, Up, "sxmo_brightness.sh up" }, + { 4, Up, Down, "sxmo_brightness.sh down" }, +}; diff --git a/lisgd.c b/lisgd.c new file mode 100644 index 0000000..e3a1c1c --- /dev/null +++ b/lisgd.c @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Defines */ +#define MAXSLOTS 20 +#define INVALID -999999 + +/* Types */ +enum { Left, Right, Down, Up }; +typedef int direction; +typedef struct { + int nfingers; + direction start; + direction end; + char *command; +} Gesture; + +/* Config */ +#include "config.h" + +/* Globals */ +Gesture *gestsarr; +int gestsarrlen; +direction pendingstart, pendingend; +double xstart[MAXSLOTS], xend[MAXSLOTS], ystart[MAXSLOTS], yend[MAXSLOTS]; +unsigned fingsdown = 0, fingspending = 0; + +void +die(char * msg) +{ + fprintf(stderr, msg); + exit(1); +} + +void +execcommand(char *c) +{ + system(c); +} + +static int +libinputopenrestricted(const char *path, int flags, void *user_data) +{ + int fd = open(path, flags); + return fd < 0 ? -errno : fd; +} + +static void +libinputcloserestricted(int fd, void *user_data) +{ + close(fd); +} + +void +touchdown(struct libinput_event *e) +{ + struct libinput_event_touch *tevent; + int slot; + + tevent = libinput_event_get_touch_event(e); + slot = libinput_event_touch_get_slot(tevent); + + xstart[slot] = libinput_event_touch_get_x(tevent); + ystart[slot] = libinput_event_touch_get_y(tevent); + fingsdown++; +} + +void +touchmotion(struct libinput_event *e) +{ + struct libinput_event_touch *tevent; + int slot; + + tevent = libinput_event_get_touch_event(e); + slot = libinput_event_touch_get_slot(tevent); + xend[slot] = libinput_event_touch_get_x(tevent); + yend[slot] = libinput_event_touch_get_y(tevent); +} + +void +touchup(struct libinput_event *e) +{ + struct libinput_event_touch *tevent; + direction start; + direction end; + int i; + int slot; + + tevent = libinput_event_get_touch_event(e); + slot = libinput_event_touch_get_slot(tevent); + + fingsdown--; + if (xend[slot] == INVALID || xstart[slot] == INVALID) return; + + if (verbose) { + fprintf( + stderr, + "(%d down fingers) (%d pending fingers) [start: x %lf y %lf] to [end x %lf y %lf]\n", + fingsdown, fingspending, xstart[slot], ystart[slot], xend[slot], yend[slot] + ); + } + + if (xend[slot] > xstart[slot] && fabs(xend[slot] - xstart[slot]) > threshold) { + start = Left; + end = Right; + } else if (xend[slot] < xstart[slot] && fabs(xend[slot] - xstart[slot]) > threshold) { + start = Right; + end = Left; + } else if (yend[slot] > ystart[slot] && fabs(yend[slot] - ystart[slot]) > threshold) { + start = Up; + end = Down; + } else if (yend[slot] < ystart[slot] && fabs(yend[slot] - ystart[slot]) > threshold) { + start = Down; + end = Up; + } else { + if (verbose) { + fprintf(stderr, "Input didn't match a known gesture\n"); + } + start = INVALID; + end = INVALID; + } + + if (fingspending == 0) { + pendingstart = start; + pendingend = end; + } + if (pendingstart == start && pendingend == end) { + fingspending++; + } + + xend[slot] = INVALID; + yend[slot] = INVALID; + xstart[slot] = INVALID; + ystart[slot] = INVALID; + + if (fingsdown == 0) { + for (i = 0; i < gestsarrlen; i++) { + if (verbose) { + fprintf(stderr, + "[Fingers/Start/End]: Cfg (%d/%d/%d) <=> Evt (%d/%d/%d)\n", + gestsarr[i].nfingers, gestsarr[i].start, gestsarr[i].end, + fingspending, pendingstart, pendingend + ); + } + if ( + gestsarr[i].nfingers == fingspending && + gestsarr[i].start == pendingstart && + gestsarr[i].end == pendingend + ) { + if (verbose) { + fprintf(stderr, "Execute %s\n", gestsarr[i].command); + } + execcommand(gestsarr[i].command); + } + } + fingspending = 0; + } +} + +void +run() +{ + struct libinput *li; + struct libinput_event *event; + struct libinput_event_touch *tevent; + struct libinput_device *d; + int selectresult; + fd_set fdset; + + const static struct libinput_interface interface = { + .open_restricted = libinputopenrestricted, + .close_restricted = libinputcloserestricted, + }; + + li = libinput_path_create_context(&interface, NULL); + + if ((d = libinput_path_add_device(li, device)) == NULL) { + die("Couldn't bind event from dev filesystem\n"); + } else if (LIBINPUT_CONFIG_STATUS_SUCCESS != libinput_device_config_send_events_set_mode( + d, LIBINPUT_CONFIG_SEND_EVENTS_ENABLED + )) { + die("Couldn't set mode to capture events\n"); + } + + FD_ZERO(&fdset); + FD_SET(libinput_get_fd(li), &fdset); + for (;;) { + selectresult = select(FD_SETSIZE, &fdset, NULL, NULL, NULL); + if (selectresult == -1) { + die("Can't select on device node?\n"); + } else { + libinput_dispatch(li); + while ((event = libinput_get_event(li)) != NULL) { + switch(libinput_event_get_type(event)) { + case LIBINPUT_EVENT_TOUCH_DOWN: touchdown(event); break; + case LIBINPUT_EVENT_TOUCH_UP: touchup(event); break; + case LIBINPUT_EVENT_TOUCH_MOTION: touchmotion(event); break; + } + libinput_event_destroy(event); + } + } + } + libinput_unref(li); +} + +int +main(int argc, char *argv[]) +{ + int i, j; + char *gestpt; + + gestsarr = malloc(0); + gestsarrlen = 0; + + prctl(PR_SET_PDEATHSIG, SIGTERM); + prctl(PR_SET_PDEATHSIG, SIGKILL); + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + verbose = 1; + } else if (!strcmp(argv[i], "-d")) { + device = argv[++i]; + } else if (!strcmp(argv[i], "-t")) { + threshold = atoi(argv[++i]); + } else if (!strcmp(argv[i], "-g")) { + gestsarrlen++; + realloc(gestsarr, (gestsarrlen * sizeof(Gesture))); + gestpt = strtok(argv[++i], ","); + for (j = 0; gestpt != NULL && j < 4; gestpt = strtok(NULL, ","), j++) { + switch(j) { + case 0: gestsarr[gestsarrlen - 1].nfingers = atoi(gestpt); break; + case 1: + if (!strcmp(gestpt, "l")) gestsarr[gestsarrlen-1].start = Left; + if (!strcmp(gestpt, "r")) gestsarr[gestsarrlen-1].start = Right; + if (!strcmp(gestpt, "d")) gestsarr[gestsarrlen-1].start = Down; + if (!strcmp(gestpt, "u")) gestsarr[gestsarrlen-1].start = Up; + break; + case 2: + if (!strcmp(gestpt, "l")) gestsarr[gestsarrlen-1].end = Left; + if (!strcmp(gestpt, "r")) gestsarr[gestsarrlen-1].end = Right; + if (!strcmp(gestpt, "d")) gestsarr[gestsarrlen-1].end = Down; + if (!strcmp(gestpt, "u")) gestsarr[gestsarrlen-1].end = Up; + break; + case 3: gestsarr[gestsarrlen - 1].command = gestpt; break; + } + } + } + } + + // E.g. no gestures passed on CLI - used gestures defined in config.def.h + if (gestsarrlen == 0) { + gestsarr = malloc(sizeof(gestures)); + gestsarrlen = sizeof(gestures) / sizeof(Gesture); + memcpy(gestsarr, gestures, sizeof(gestures)); + } + + run(); + return 0; +}