~jpsamaroo/lisgd

53b97c1d4705a62018eda878907e93cd27eec300 — Maarten van Gompel 1 year, 5 months ago ee47ac2
implementing support for gestures with edge and corner detection
5 files changed, 233 insertions(+), 54 deletions(-)

M Makefile
M README.md
M config.def.h
M lisgd.1
M lisgd.c
M Makefile => Makefile +4 -1
@@ 2,6 2,9 @@ SRC = lisgd.c
OBJ = ${SRC:.c=.o}
LDFLAGS = -linput -lm

X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib

all: options lisgd

options:


@@ 19,7 22,7 @@ config.h:
	cp config.def.h $@

lisgd: ${OBJ}
	${CC} -g -o $@ ${OBJ} ${LDFLAGS}
	${CC} -g -o $@ ${OBJ} -I${X11INC} -lX11 ${LDFLAGS}

install: all
	mkdir -p ${DESTDIR}${PREFIX}/bin

M README.md => README.md +12 -13
@@ 4,7 4,7 @@ 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. Directional L-R, R-L, U-D, and D-U gestures and
diagnol LD-RU, RD-LU, UR-DL, UL-DR gestures are supported with 1 through 
diagnol LD-RU, RD-LU, UR-DL, UL-DR gestures are supported with 1 through
n fingers.

Unlike other libinput gesture daemons, lisgd uses touch events to


@@ 32,25 32,24 @@ Flags:

- **-d [devicenodepath]**: Defines the dev filesystem device to monitor
  - Example: `lisgd -d /dev/input/input1`
- **-g [nfingers,gesture,command]**: Allows you to bind a gesture wherein 
  nfingers is an integer, gesture is one of {LR,RL,DU,UD,DLUR,URDL,ULDR,DLUR},
  and command is the shell command to be executed. The -g option can be used
- **-g [nfingers,gesture,edge,distance,command]**: Allows you to bind a gesture wherein nfingers is an integer, gesture is
one of {LR,RL,DU,UD,DLUR,URDL,ULDR,DLUR}, edge is one of * (any), N (none), L (left), R (right), T (top), B (bottom), TL (top left), TR (top right), BL (bottom left), BR (bottom right) and distance is one of * (any), S (short), M (medium), L (large). command is the shell command to be executed. The -g option can be used
  multiple times to bind multiple gestures.
  - Single Gesture Example: `lisgd -g "1,LR,notify-send swiped lr"`
  - Multiple Gestures Example: `lisgd -g "1,LR,notify-send swiped lr" -g "1,RL,noitfy-send swiped rl"`
- **-m [timeoutms]**: Number of milliseconds gestures must be performed within 
  - Single Gesture Example: `lisgd -g "1,LR,*,*,notify-send swiped lr"`
  - Multiple Gestures Example: `lisgd -g "1,LR,*,*,notify-send swiped lr" -g "1,RL,R,*,noitfy-send swiped rl from right edge"`
- **-m [timeoutms]**: Number of milliseconds gestures must be performed within
    to be registered. After the timeoutms value; the gesture won't be registered.
  - Example: `lisgd -m 1200`
- **-o [orientation]**: Number of 90-degree rotations to translate gestures by. 
  Can be set to 0-3. For example using 1; a L-R gesture would become a U-D 
- **-o [orientation]**: Number of 90-degree rotations to translate gestures by.
  Can be set to 0-3. For example using 1; a L-R gesture would become a U-D
  gesture. Meant to be used for screen-rotation.
  - Example `lisgd -o 1`
- **-r [degrees]**: Number of degrees offset each 45-degree interval may still 
  be recognized within. Maximum value is 45. Default value is 15. E.g. U-D 
  is a 180 degree gesture but with 15 degrees of leniency will be recognized 
- **-r [degrees]**: Number of degrees offset each 45-degree interval may still
  be recognized within. Maximum value is 45. Default value is 15. E.g. U-D
  is a 180 degree gesture but with 15 degrees of leniency will be recognized
  between 165-195 degrees.
  - Example: `lisgd -r 20`
- **-t [threshold_units]**: Threshold in libinput units (pixels) after which a 
- **-t [threshold_units]**: Threshold in libinput units (pixels) after which a
  gesture registers. Defaults to 300.
  - Example: `lisgd -t 400`
- **-v**: Verbose mode, useful for debugging

M config.def.h => config.def.h +14 -12
@@ 1,5 1,5 @@
/* 
  distancethreshold: Minimum cutoff for a gestures to take effect 
/*
  distancethreshold: Minimum cutoff for a gestures to take effect
  degreesleniency: Offset degrees within which gesture is recognized (max=45)
  timeoutms: Maximum duration for a gesture to take place in miliseconds
  orientation: Number of 90 degree turns to shift gestures by


@@ 15,18 15,20 @@ unsigned int degreesleniency = 15;
unsigned int timeoutms = 800;
unsigned int orientation = 0;
unsigned int verbose = 0;
double edgesizex = 50.0;
double edgesizey = 50.0;
char *device = "/dev/input/event1";

Gesture gestures[] = {
	/* nfingers  gesturetype  command */
	{ 1,         SwipeLR,     "xdotool key --clearmodifiers Alt+Shift+e" },
	{ 1,         SwipeRL,     "xdotool key --clearmodifiers Alt+Shift+r" },
	{ 1,         SwipeDLUR,   "sxmo_vol.sh up" },
	{ 1,         SwipeURDL,   "sxmo_vol.sh down" },
	{ 1,         SwipeDRUL,   "sxmo_brightness.sh up" },
	{ 1,         SwipeULDR,   "sxmo_brightness.sh down" },
	{ 2,         SwipeLR,     "xdotool key --clearmodifiers Alt+e" },
	{ 2,         SwipeRL,     "xdotool key --clearmodifiers Alt+r" },
	{ 2,         SwipeDU,     "pidof svkbd-sxmo || svkbd-sxmo &" },
	{ 2,         SwipeUD,     "pkill -9 svkbd-sxmo" },
	{ 1,         SwipeLR,     EdgeAny, DistanceAny, "xdotool key --clearmodifiers Alt+Shift+e" },
	{ 1,         SwipeRL,     EdgeAny, DistanceAny, "xdotool key --clearmodifiers Alt+Shift+r" },
	{ 1,         SwipeDLUR,   EdgeAny, DistanceAny, "sxmo_vol.sh up" },
	{ 1,         SwipeURDL,   EdgeAny, DistanceAny, "sxmo_vol.sh down" },
	{ 1,         SwipeDRUL,   EdgeAny, DistanceAny, "sxmo_brightness.sh up" },
	{ 1,         SwipeULDR,   EdgeAny, DistanceAny, "sxmo_brightness.sh down" },
	{ 2,         SwipeLR,     EdgeAny, DistanceAny, "xdotool key --clearmodifiers Alt+e" },
	{ 2,         SwipeRL,     EdgeAny, DistanceAny, "xdotool key --clearmodifiers Alt+r" },
	{ 2,         SwipeDU,     EdgeAny, DistanceAny, "pidof svkbd-sxmo || svkbd-sxmo &" },
	{ 2,         SwipeUD,     EdgeAny, DistanceAny, "pkill -9 svkbd-sxmo" },
};

M lisgd.1 => lisgd.1 +9 -8
@@ 20,8 20,9 @@ lisgd \- libinput synthetic gesture daemon
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. Directional L-R, R-L, U-D, and D-U gestures and
diagnol LD-RU, RD-LU, UR-DL, UL-DR gestures are supported with 1 through 
n fingers.
diagnol LD-RU, RD-LU, UR-DL, UL-DR gestures are supported with 1 through
n fingers and can be bound to the screen's edges and/or made sensitive to
the distance of the gesture.

Unlike other libinput gesture daemons, lisgd uses touch events to
recognize synthetic swipe gestures rather than using the libinput's


@@ 29,9 30,9 @@ 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 
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.




@@ 41,9 42,9 @@ you're using.
Path of the dev filesystem device to monitor (like /dev/input/event1).

.TP
.BR \-g ", " \-g\ nfingers,gesture,command\fR
Allow you to bind a gesture wherein nfingers is an integer, gesture is 
one of {LR,RL,DU,UD,DLUR,URDL,ULDR,DLUR}, and the shell command to be executed.
.BR \-g ", " \-g\ nfingers,gesture,edge,distance,command\fR
Allows you to bind a gesture wherein nfingers is an integer, gesture is
one of {LR,RL,DU,UD,DLUR,URDL,ULDR,DLUR}, edge is one of * (any), N (none), L (left), R (right), T (top), B (bottom), TL (top left), TR (top right), BL (bottom left), BR (bottom right) and distance is one of * (any), S (short), M (medium), L (large). command is the shell command to be executed.

The -g option can be used multiple times to bind multiple gestures.


M lisgd.c => lisgd.c +194 -20
@@ 10,6 10,7 @@
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>

/* Defines */
#define MAXSLOTS 20


@@ 26,11 27,35 @@ enum {
  SwipeURDL,
  SwipeULDR
};

typedef int Swipe;

enum {
	EdgeAny,
	EdgeNone,
	EdgeLeft,
	EdgeRight,
	EdgeTop,
	EdgeBottom,
	CornerTopLeft,
	CornerTopRight,
	CornerBottomLeft,
	CornerBottomRight,
};
typedef int Edge;

enum {
	DistanceAny,
	DistanceShort,
	DistanceMedium,
	DistanceLong,
};
typedef int Distance;

typedef struct {
	int nfswipe;
	Swipe swipe;
	Edge edge;
	Distance distance;
	char *command;
} Gesture;



@@ 41,9 66,14 @@ typedef struct {
Gesture *gestsarr;
int gestsarrlen;
Swipe pendingswipe;
Edge pendingedge;
Distance pendingdistance;
double xstart[MAXSLOTS], xend[MAXSLOTS], ystart[MAXSLOTS], yend[MAXSLOTS];
unsigned nfdown = 0, nfpendingswipe = 0;
struct timespec timedown;
static Display *dpy;
static int screen;
static int screenwidth, screenheight;

void
die(char * msg)


@@ 91,31 121,119 @@ gesturecalculateswipe(double x0, double y0, double x1, double y1) {
	return -1;
}

Distance
gesturecalculatedistance(double x0, double y0, double x1, double y1, Swipe swipe) {
	double dist = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
	double diag = sqrt(pow(screenwidth,2) + pow(screenheight,2));
	switch (swipe) {
		case SwipeDU:
		case SwipeUD:
			if (dist >= screenheight * 0.66) {
				return DistanceLong;
			} else if (dist >= screenheight * 0.33) {
				return DistanceMedium;
			} else {
				return DistanceShort;
			}
			break;
		case SwipeLR:
		case SwipeRL:
			if (dist >= screenwidth * 0.66) {
				return DistanceLong;
			} else if (dist >= screenwidth * 0.33) {
				return DistanceMedium;
			} else {
				return DistanceShort;
			}
			break;
		case SwipeULDR:
		case SwipeDRUL:
		case SwipeDLUR:
		case SwipeURDL:
			if (dist >= diag * 0.66) {
				return DistanceLong;
			} else if (dist >= diag * 0.33) {
				return DistanceMedium;
			} else {
				return DistanceShort;
			}
			break;
	}

	return 0; //shouldn't happen
}

Edge
gesturecalculateedge(double x0, double y0, double x1, double y1) {
		Edge horizontal = EdgeNone;
		Edge vertical = EdgeNone;
		if (x0 <= edgesizex) {
			horizontal = EdgeLeft;
		} else if (x0 >= screenwidth - edgesizex) {
			horizontal = EdgeRight;
		} else if (x1 <= edgesizex) {
			horizontal = EdgeLeft;
		} else if (x1 >= screenwidth - edgesizex) {
			horizontal = EdgeRight;
		}
		if (y0 <= edgesizey) {
			vertical = EdgeTop;
		} else if (y0 >= screenheight - edgesizey) {
			vertical = EdgeBottom;
		} else if (y1 <= edgesizey) {
			vertical = EdgeTop;
		} else if (y1 >= screenheight - edgesizey) {
			vertical = EdgeBottom;
		}
		if (horizontal == EdgeLeft && vertical == EdgeTop) {
			return CornerTopLeft;
		} else if (horizontal == EdgeRight && vertical == EdgeTop) {
			return CornerTopRight;
		} else if (horizontal == EdgeLeft && vertical == EdgeBottom) {
			return CornerBottomLeft;
		} else if (horizontal == EdgeRight && vertical == EdgeBottom) {
			return CornerBottomRight;
		} else if (horizontal != EdgeNone) {
			return horizontal;
		} else {
			return vertical;
		}
}

void
gestureexecute(Swipe swipe, int nfingers) {
gestureexecute(Swipe swipe, int nfingers, Edge edge, Distance distance) {
	int i;

	for (i = 0; i < gestsarrlen; i++) {
		if (verbose) {
			fprintf(stderr, 
				"[Nfswipe/SwipeId]: Cfg (%d/%d) <=> Evt (%d/%d)\n", 
				gestsarr[i].nfswipe, gestsarr[i].swipe, nfingers, swipe
			fprintf(stderr,
				"[swipe]: Cfg(f=%d/s=%d/e=%d/d=%d) <=> Evt(f=%d/s=%d/e=%d/d=%d)\n",
				gestsarr[i].nfswipe, gestsarr[i].swipe, gestsarr[i].edge, gestsarr[i].distance, nfingers, swipe, edge, distance
			);
		}
		if (gestsarr[i].nfswipe == nfingers && gestsarr[i].swipe == swipe) {
		if (gestsarr[i].nfswipe == nfingers && gestsarr[i].swipe == swipe
			&& gestsarr[i].distance <= distance
			&& (gestsarr[i].edge == EdgeAny || gestsarr[i].edge == edge ||
				((edge == CornerTopLeft || edge == CornerTopRight) && gestsarr[i].edge == EdgeTop) ||
				((edge == CornerBottomLeft || edge == CornerBottomRight) && gestsarr[i].edge == EdgeBottom) ||
				((edge == CornerTopLeft || edge == CornerBottomLeft) && gestsarr[i].edge == EdgeLeft) ||
				((edge == CornerTopRight || edge == CornerBottomRight) && gestsarr[i].edge == EdgeRight)
			   )
			) {
			if (verbose) fprintf(stderr, "Execute %s\n", gestsarr[i].command);
			execcommand(gestsarr[i].command);
			break; //execute first match only
		}
	}
}

static int 
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)
{


@@ 141,6 259,25 @@ swipereorient(Swipe swipe, int orientation) {
	return swipe;
}

Edge
edgereorient(Edge edge, int orientation) {
	while (orientation > 0) {
		switch(edge) {
			// 90deg per turn
			case EdgeLeft:   edge = EdgeTop; break;
			case EdgeRight:  edge = EdgeBottom; break;
			case EdgeTop:    edge = EdgeRight; break;
			case EdgeBottom: edge = EdgeLeft; break;
			case CornerTopLeft: edge = CornerTopRight; break;
			case CornerTopRight:   edge = CornerBottomRight; break;
			case CornerBottomLeft: edge = CornerTopLeft; break;
			case CornerBottomRight: edge = CornerBottomLeft; break;
		}
		orientation--;
	}
	return edge;
}

void
touchdown(struct libinput_event *e)
{


@@ 197,17 334,27 @@ touchup(struct libinput_event *e)
	Swipe swipe = gesturecalculateswipe(
		xstart[slot], ystart[slot], xend[slot], yend[slot]
	);
	if (nfpendingswipe == 0) pendingswipe = swipe;
	Edge edge = gesturecalculateedge(
		xstart[slot], ystart[slot], xend[slot], yend[slot]
	);
	Distance distance = gesturecalculatedistance(
		xstart[slot], ystart[slot], xend[slot], yend[slot], swipe
	);
	if (nfpendingswipe == 0) {
		pendingswipe = swipe;
		pendingedge = edge;
		pendingdistance = distance;
	}
	if (pendingswipe == swipe) nfpendingswipe++;
	resetslot(slot);

	// All fingers up - check if within milisecond limit, exec, & reset
	if (nfdown == 0) {
		if (
			timeoutms > 
			timeoutms >
			((now.tv_sec - timedown.tv_sec) * 1000000 + (now.tv_nsec - timedown.tv_nsec) / 1000) / 1000
		) gestureexecute(swipe, nfpendingswipe);
		
		) gestureexecute(swipe, nfpendingswipe, edge, distance);

		nfpendingswipe = 0;
	}
}


@@ 238,7 385,7 @@ run()
		die("Couldn't set mode to capture events");
	}

	// E.g. initially invalidate every slot 
	// E.g. initially invalidate every slot
	for (i = 0; i < MAXSLOTS; i++) {
		xend[i] = NOMOTION;
		yend[i] = NOMOTION;


@@ 266,7 413,7 @@ run()
	}
	libinput_unref(li);
}
 

int
main(int argc, char *argv[])
{


@@ 300,10 447,10 @@ main(int argc, char *argv[])
				exit(EXIT_FAILURE);
			}
			gestpt = strtok(argv[++i], ",");
			for (j = 0; gestpt != NULL && j < 3;	gestpt = strtok(NULL, ","), j++) {
			for (j = 0; gestpt != NULL && j < 5;	gestpt = strtok(NULL, ","), j++) {
				switch(j) {
					case 0: gestsarr[gestsarrlen - 1].nfswipe = atoi(gestpt); break;
					case 1: 
					case 1:
						if (!strcmp(gestpt, "LR")) gestsarr[gestsarrlen-1].swipe = SwipeLR;
						if (!strcmp(gestpt, "RL")) gestsarr[gestsarrlen-1].swipe = SwipeRL;
						if (!strcmp(gestpt, "DU")) gestsarr[gestsarrlen-1].swipe = SwipeDU;


@@ 313,15 460,40 @@ main(int argc, char *argv[])
						if (!strcmp(gestpt, "ULDR")) gestsarr[gestsarrlen-1].swipe = SwipeULDR;
						if (!strcmp(gestpt, "DRUL")) gestsarr[gestsarrlen-1].swipe = SwipeDRUL;
						break;
					case 2: gestsarr[gestsarrlen - 1].command = gestpt; break;
					case 2:
						if (!strcmp(gestpt, "L")) gestsarr[gestsarrlen-1].edge = EdgeLeft;
						if (!strcmp(gestpt, "R")) gestsarr[gestsarrlen-1].edge = EdgeRight;
						if (!strcmp(gestpt, "T")) gestsarr[gestsarrlen-1].edge = EdgeTop;
						if (!strcmp(gestpt, "B")) gestsarr[gestsarrlen-1].edge = EdgeBottom;
						if (!strcmp(gestpt, "TL")) gestsarr[gestsarrlen-1].edge = CornerTopLeft;
						if (!strcmp(gestpt, "TR")) gestsarr[gestsarrlen-1].edge = CornerTopRight;
						if (!strcmp(gestpt, "BL")) gestsarr[gestsarrlen-1].edge = CornerBottomLeft;
						if (!strcmp(gestpt, "BR")) gestsarr[gestsarrlen-1].edge = CornerBottomRight;
						if (!strcmp(gestpt, "N")) gestsarr[gestsarrlen-1].edge = EdgeNone;
						if (!strcmp(gestpt, "*")) gestsarr[gestsarrlen-1].edge = EdgeAny;
						break;
					case 3:
						if (!strcmp(gestpt, "L")) gestsarr[gestsarrlen-1].distance = DistanceLong;
						if (!strcmp(gestpt, "M")) gestsarr[gestsarrlen-1].distance = DistanceMedium;
						if (!strcmp(gestpt, "S")) gestsarr[gestsarrlen-1].distance = DistanceShort;
						if (!strcmp(gestpt, "*")) gestsarr[gestsarrlen-1].distance = DistanceAny;
						break;
					case 4: gestsarr[gestsarrlen - 1].command = gestpt; break;
				}
			}
		} else {
			fprintf(stderr, "lisgd [-v] [-d /dev/input/0] [-o 0] [-t 200] [-r 20] [-m 400] [-g '1,LR,notify-send swiped left to right']\n");
			fprintf(stderr, "lisgd [-v] [-d /dev/input/0] [-o 0] [-t 200] [-r 20] [-m 400] [-g '1,LR,L,notify-send swiped left to right from left edge']\n");
			exit(1);
		}
	}

	//get display size
	if (!(dpy = XOpenDisplay(0)))
		die("cannot open display");
	screen = DefaultScreen(dpy);
	screenwidth = DisplayWidth(dpy, screen);
	screenheight = DisplayHeight(dpy, screen);

	// E.g. no gestures passed on CLI - used gestures defined in config.def.h
	if (gestsarrlen == 0) {
		gestsarr = malloc(sizeof(gestures));


@@ 329,9 501,11 @@ main(int argc, char *argv[])
		memcpy(gestsarr, gestures, sizeof(gestures));
	}

  // Modify gestures swipes based on orientation provided
	for (i = 0; i < gestsarrlen; i++)
	// Modify gestures swipes based on orientation provided
	for (i = 0; i < gestsarrlen; i++) {
		gestsarr[i].swipe = swipereorient(gestsarr[i].swipe, orientation);
		gestsarr[i].edge = edgereorient(gestsarr[i].edge, orientation);
	}

	run();
	return 0;