lisgd + wvkbd

This commit is contained in:
Aleksandr Lebedev 2026-01-27 23:42:01 +01:00
parent adbe4541cb
commit 6d345f43d4
36 changed files with 31637 additions and 0 deletions

2
packages/lisgd/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
lisgd
lisgd.o

21
packages/lisgd/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT/X Consortium License
© 2020 Miles Alan <m@milesalan.com>
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.

56
packages/lisgd/Makefile Normal file
View file

@ -0,0 +1,56 @@
PREFIX = /usr
SRC = lisgd.c
OBJ = ${SRC:.c=.o}
LDFLAGS = -g
LIBS = -linput -lm
X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib
ifndef WITHOUT_X11
CPPFLAGS += -I${X11INC} -DWITH_X11
LIBS += -L${X11LIB} -lX11
endif
ifndef WITHOUT_WAYLAND
CPPFLAGS += -DWITH_WAYLAND
LIBS += -lwayland-client
endif
all: options lisgd
options:
@echo lisgd build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "CPPFLAGS = ${CPPFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "LIBS = ${LIBS}"
@echo "CC = ${CC}"
.c.o:
${CC} -c ${CFLAGS} ${CPPFLAGS} $<
${OBJ}: config.h
config.h:
cp config.def.h $@
lisgd: ${OBJ}
${CC} -o $@ ${OBJ} ${LDFLAGS} ${LIBS}
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f lisgd ${DESTDIR}${PREFIX}/bin
chmod 755 ${DESTDIR}${PREFIX}/bin/lisgd
mkdir -p ${DESTDIR}${PREFIX}/share/man/man1
cp lisgd.1 ${DESTDIR}${PREFIX}/share/man/man1
chmod 755 ${DESTDIR}${PREFIX}/share/man/man1
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/lisgd ${DESTDIR}${PREFIX}/share/man/man1/lisgd.1
clean:
rm -f lisgd.o lisgd
.PHONY: all options install clean

72
packages/lisgd/README.md Normal file
View file

@ -0,0 +1,72 @@
# 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. 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.
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](https://www.pine64.org/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
Copy the example `config.def.h` configuration to `config.h`.
### Commandline flags based configuration
Flags:
- **-d [devicenodepath]**: Defines the dev filesystem device to monitor
- Example: `lisgd -d /dev/input/input1`
- **-g [nfingers,gesture,edge,distance,actmode,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). actmode is R (release) for normal mode and P (pressed) for pressed mode (but this field may be omitted entirely for backward
compatibility), 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,*,*,R,notify-send swiped lr"`
- Multiple Gestures Example: `lisgd -g "1,LR,*,*,R,notify-send swiped lr" -g "1,RL,R,*,R,notify-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
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
between 165-195 degrees.
- Example: `lisgd -r 20`
- **-t [threshold_units]**: Threshold in libinput units (pixels) after which a
gesture registers. Defaults to 125.
- Example: `lisgd -t 125`
- **-T [threshold_units]**: Threshold in libinput units (pixels) after which a
gesture registers for 'pressed' gestures where fingers are not lifted.
Defaults to 60.
- Example: `lisgd -t 60`
- **-w [screenwidth]**: Width of screen used for edge-based gestures. Use in
conjunction with -h. If unset dynamic X/Wayland screen geometry detection is
used.
- Example: `lisgd -w 600`
- **-h [screenheight]**: Height of screen used for edge-based gestures. Use in
conjunction with -w. If unset dynamic X/Wayland screen geometry detection is
used.
- Example: `lisgd -h 500`
- **-v**: Verbose mode, useful for debugging
- Example: `lisgd -v`

View file

@ -0,0 +1,39 @@
/*
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
verbose: 1=enabled, 0=disabled; helpful for debugging
device: Path to the /dev/ filesystem device events should be read from
gestures: Array of gestures; binds num of fingers / gesturetypes to commands
Supported gestures: SwipeLR, SwipeRL, SwipeDU, SwipeUD,
SwipeDLUR, SwipeURDL, SwipeDRUL, SwipeULDR
*/
unsigned int distancethreshold = 125;
unsigned int distancethreshold_pressed = 60;
unsigned int degreesleniency = 15;
unsigned int timeoutms = 800;
unsigned int orientation = 0;
unsigned int verbose = 0;
double edgesizeleft = 50.0;
double edgesizetop = 50.0;
double edgesizeright = 50.0;
double edgesizebottom = 50.0;
double edgessizecaling = 1.0;
char *device = "/dev/input/touchscreen";
//Gestures can also be specified interactively from the command line using -g
Gesture gestures[] = {
/* nfingers gesturetype command */
{ 1, SwipeLR, EdgeAny, DistanceAny, ActModeReleased, "xdotool key --clearmodifiers Alt+Shift+e" },
{ 1, SwipeRL, EdgeAny, DistanceAny, ActModeReleased, "xdotool key --clearmodifiers Alt+Shift+r" },
{ 1, SwipeDLUR, EdgeAny, DistanceAny, ActModeReleased, "sxmo_vol.sh up" },
{ 1, SwipeURDL, EdgeAny, DistanceAny, ActModeReleased, "sxmo_vol.sh down" },
{ 1, SwipeDRUL, EdgeAny, DistanceAny, ActModeReleased, "sxmo_brightness.sh up" },
{ 1, SwipeULDR, EdgeAny, DistanceAny, ActModeReleased, "sxmo_brightness.sh down" },
{ 2, SwipeLR, EdgeAny, DistanceAny, ActModeReleased, "xdotool key --clearmodifiers Alt+e" },
{ 2, SwipeRL, EdgeAny, DistanceAny, ActModeReleased, "xdotool key --clearmodifiers Alt+r" },
{ 2, SwipeDU, EdgeAny, DistanceAny, ActModeReleased, "pidof $KEYBOARD || $KEYBOARD &" },
{ 2, SwipeUD, EdgeAny, DistanceAny, ActModeReleased, "pkill -9 -f $KEYBOARD" },
};

60
packages/lisgd/config.h Normal file
View file

@ -0,0 +1,60 @@
/*
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
verbose: 1=enabled, 0=disabled; helpful for debugging
device: Path to the /dev/ filesystem device events should be read from
gestures: Array of gestures; binds num of fingers / gesturetypes to commands
Supported gestures: SwipeLR, SwipeRL, SwipeDU, SwipeUD,
SwipeDLUR, SwipeURDL, SwipeDRUL, SwipeULDR
*/
unsigned int distancethreshold = 125;
unsigned int distancethreshold_pressed = 60;
unsigned int degreesleniency = 15;
unsigned int timeoutms = 800;
unsigned int orientation = 0;
unsigned int verbose = 1;
double edgesizeleft = 50.0;
double edgesizetop = 50.0;
double edgesizeright = 50.0;
double edgesizebottom = 50.0;
double edgessizecaling = 2.0;
char *device = "/dev/touchscreen";
// Gestures can also be specified interactively from the command line using -g
Gesture gestures[] = {
{1, SwipeRL, EdgeRight, DistanceAny, ActModeReleased,
"niri msg action focus-column-right"},
{1, SwipeLR, EdgeLeft, DistanceAny, ActModeReleased,
"niri msg action focus-column-left"},
{1, SwipeDU, CornerBottomRight, DistanceMedium, ActModeReleased,
"niri msg action focus-workspace-down"},
{1, SwipeUD, CornerTopRight, DistanceMedium, ActModeReleased,
"niri msg action focus-workspace-up"},
{1, SwipeDU, CornerBottomLeft, DistanceShort, ActModeReleased,
"niri msg action switch-preset-column-width"},
//{1, SwipeUD, EdgeTop, DistanceAny, ActModeReleased, "nwggrid -o 0.98"},
//"pkill -SIGRTMIN -f wvkbd"},
//{2, SwipeUD, EdgeAny, DistanceAny, ActModeReleased,
//"sway-interactive-screenshot -s focused-output"},
//{3, SwipeLR, EdgeAny, DistanceAny, ActModeReleased,
//"swaymsg layout tabbed"},
//{3, SwipeRL, EdgeAny, DistanceAny, ActModeReleased,
//"swaymsg layout toggle split"},
{2, SwipeUD, EdgeLeft, DistanceShort, ActModePressed,
"niri msg action fullscreen-window"},
{2, SwipeUD, EdgeRight, DistanceMedium, ActModeReleased,
"niri msg action close-window"},
{2, SwipeDU, EdgeBottom, DistanceAny, ActModeReleased,
"pkill -34 -f wvkbd"},
//{3, SwipeUD, EdgeTop, DistanceLong, ActModeReleased,
// "systemctl --user restart desktop-shell.service"},
{2, SwipeUD, EdgeTop, DistanceLong, ActModeReleased,
"grim -g \"$(slurp -w 0)\" -t ppm - | satty --early-exit --copy-command 'wl-copy' --filename='-' -o '~/Pictures/Screenshots/Screenshot-%Y-%m-%d_%H:%M:%S.png' --initial-tool brush"},
//{2, SwipeUD, EdgeBottom, DistanceAny, ActModeReleased,
//"pkill -9 -f wvkbd-mobintl"},
{3, SwipeDU, EdgeAny, DistanceAny, ActModeReleased,
"niri msg action toggle-overview"},
};

118
packages/lisgd/lisgd.1 Normal file
View file

@ -0,0 +1,118 @@
.TH LISGD 1
.SH NAME
lisgd \- libinput synthetic gesture daemon
.SH SYNOPSIS
.B lisgd
[\fB\-d\fR \fIdevicepath\fR]
[\fB\-g\fR \fIgesturespec\fR]...
[\fB\-t\fR \fIthreshold\fR]
[\fB\-m\fR \fItimeoutms\fR]
[\fB\-o\fR \fIorientation\fR]
[\fB\-w\fR \fIwidth\fR]
[\fB\-h\fR \fIheight\fR]
[\fB\-r\fR \fIdegreesofleniency\fR]
[\fB\-v]
.SH DESCRIPTION
.B lisgd
(or 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
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
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.
.SH OPTIONS
.TP
.BR \-d ", " \-d\ devicepath\fR
Path of the dev filesystem device to monitor (like /dev/input/event1).
.TP
.BR \-g ", " \-g\ nfingers,gesture,edge,distance,actmode,command\fR
Allows you to bind a gesture wherein nfingers is an integer, gesture is
one of {LR,RL,DU,UD,DLUR,DRUL,URDL,ULDR}, 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), actmode is R (release) for normal mode and P (pressed) for
pressed mode (but this field may be omitted entirely for backward
compatibility), command is the shell command to be executed.
The -g option can be used multiple times to bind multiple gestures.
.TP
.BR \-m ", " \-m\ timeoutms\fR
Number of milliseconds gestures must be performed within to be registered. After
the timeoutms value; the gesture won't be registered.
.TP
.BR \-o ", " \-o\ orientation\fR
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.
.TP
.BR \-r ", " \-r\ degreesofleniency\fR
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.
.TP
.BR \-t ", " \-t\ distancethreshold\fR
Threshold in libinput units (pixels) after which a gesture registers. Defaults
to 125.
.TP
.BR \-T ", " \-T\ distancethreshold_pressed\fR
Threshold in libinput units (pixels) after which a gesture registers when fingers
are not lifted. Defaults to 60.
.TP
.BR \-w ", " \-w\ screnwidth\fR
Allows you to specify the width of the screen area to be used for
edge-based gestures. Should be used in conjunction with -h. If unset,
and either the DISPLAY or WAYLAND_DISPLAY env var is set, X/Wayland
dynamic screen geometry detection will be used instead.
.TP
.BR \-h ", " \-h\ screenheight\fR
Allows you to specify the height of the screen area to be used for
edge-based gestures. Should be used in conjunction with -w. If unset,
and either the DISPLAY or WAYLAND_DISPLAY env var is set, X/Wayland
dynamic screen geometry detection will be used instead.
.TP
.BR \-s ", " \-s\ edgessizecaling\fR
Scale the edge sizes with this value. It is very usefull with very hidpi screens,
to help detecting edge gestures.
.TP
.BR \-v \fR
Enables verbose mode which prints debugging messages.
.SH SEE ALSO
lisgd was built as part of Sxmo; an project to create a Pinephone UI out of
simple and suckless programs. See: http://sr.ht/mil/Sxmo
.SH AUTHOR
.BR lisgd
is written by Miles Alan <m@milesalan.com>
.SH CONTRIBUTING
Bugs and feature dicussions can be sent to ~mil/sxmo-devel@lists.sr.ht

674
packages/lisgd/lisgd.c Normal file
View file

@ -0,0 +1,674 @@
#include <errno.h>
#include <fcntl.h>
#include <libinput.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#ifdef WITH_X11
# include <X11/Xlib.h>
#endif
#ifdef WITH_WAYLAND
# include <wayland-client.h>
#endif
/* Defines */
#define MAXSLOTS 20
#define NOMOTION -999999
/* Types */
enum {
SwipeDU,
SwipeUD,
SwipeLR,
SwipeRL,
SwipeDLUR,
SwipeDRUL,
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;
enum {
ActModeReleased, //action triggers when fingers are released
ActModePressed, //action triggers while finger is not lifted yet (as soon as a swipe is completed)
};
typedef int ActMode;
typedef struct {
int nfswipe;
Swipe swipe;
Edge edge;
Distance distance;
ActMode actmode;
char *command;
} Gesture;
/* Config */
#include "config.h"
/* Globals */
Gesture *gestsarr;
int gestsarrlen;
int have_actmode_pressed = 0; //do we have gestures using actmode pressed?
Swipe pendingswipe;
Edge pendingedge;
Distance pendingdistance;
double xstart[MAXSLOTS], xend[MAXSLOTS], ystart[MAXSLOTS], yend[MAXSLOTS];
unsigned nfdown = 0, nfpendingswipe = 0;
struct timespec timedown;
static int screen;
#ifdef WITH_WAYLAND
struct wl_display *wl_display;
struct wl_registry *wl_registry;
struct wl_output *wl_output;
#endif
static int screenwidth = 0, screenheight = 0;
void
die(char * msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
int
gesturecalculateswipewithindegrees(double gestdegrees, double wantdegrees) {
return (
gestdegrees >= wantdegrees - degreesleniency &&
gestdegrees <= wantdegrees + degreesleniency
);
}
Swipe
gesturecalculateswipe(double x0, double y0, double x1, double y1, int mindistance) {
double t, degrees, dist;
t = atan2(x1 - x0, y0 - y1);
degrees = 57.2957795130823209 * (t < 0 ? t + 6.2831853071795865 : t);
dist = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
if (verbose)
fprintf(stderr, "Swipe distance=[%.2f]; degrees=[%.2f]\n", dist, degrees);
if (dist < mindistance) return -1;
else if (gesturecalculateswipewithindegrees(degrees, 0)) return SwipeDU;
else if (gesturecalculateswipewithindegrees(degrees, 45)) return SwipeDLUR;
else if (gesturecalculateswipewithindegrees(degrees, 90)) return SwipeLR;
else if (gesturecalculateswipewithindegrees(degrees, 135)) return SwipeULDR;
else if (gesturecalculateswipewithindegrees(degrees, 180)) return SwipeUD;
else if (gesturecalculateswipewithindegrees(degrees, 225)) return SwipeURDL;
else if (gesturecalculateswipewithindegrees(degrees, 270)) return SwipeRL;
else if (gesturecalculateswipewithindegrees(degrees, 315)) return SwipeDRUL;
else if (gesturecalculateswipewithindegrees(degrees, 360)) return SwipeDU;
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 <= edgesizeleft * edgessizecaling) {
horizontal = EdgeLeft;
} else if (x0 >= screenwidth - edgesizeright * edgessizecaling) {
horizontal = EdgeRight;
} else if (x1 <= edgesizeleft * edgessizecaling) {
horizontal = EdgeLeft;
} else if (x1 >= screenwidth - edgesizeright * edgessizecaling) {
horizontal = EdgeRight;
}
if (y0 <= edgesizetop * edgessizecaling) {
vertical = EdgeTop;
} else if (y0 >= screenheight - edgesizebottom * edgessizecaling) {
vertical = EdgeBottom;
} else if (y1 <= edgesizetop * edgessizecaling) {
vertical = EdgeTop;
} else if (y1 >= screenheight - edgesizebottom * edgessizecaling) {
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;
}
}
int
gestureexecute(Swipe swipe, int nfingers, Edge edge, Distance distance, ActMode actmode) {
int i;
for (i = 0; i < gestsarrlen; i++) {
if (verbose) {
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
&& 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)
)
&& (actmode == ActModeReleased || gestsarr[i].actmode == actmode)
) {
if (verbose) fprintf(stderr, "Execute %s\n", gestsarr[i].command);
system(gestsarr[i].command);
return 1; //execute first match only
}
}
return 0;
}
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);
}
Swipe
swipereorient(Swipe swipe, int orientation) {
while (orientation > 0) {
switch(swipe) {
// 90deg per turn so: L->U, R->D, U->R, D->L
case SwipeDU: swipe = SwipeLR; break;
case SwipeDLUR: swipe = SwipeULDR; break;
case SwipeLR: swipe = SwipeUD; break;
case SwipeULDR: swipe = SwipeURDL; break;
case SwipeUD: swipe = SwipeRL; break;
case SwipeURDL: swipe = SwipeDRUL; break;
case SwipeRL: swipe = SwipeDU; break;
case SwipeDRUL: swipe = SwipeDLUR; break;
}
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)
{
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_transformed(tevent, screenwidth);
ystart[slot] = libinput_event_touch_get_y_transformed(tevent, screenheight);
if (nfdown == 0) clock_gettime(CLOCK_MONOTONIC_RAW, &timedown);
nfdown++;
}
void
resetslot(int slot) {
xend[slot] = NOMOTION;
yend[slot] = NOMOTION;
xstart[slot] = NOMOTION;
ystart[slot] = NOMOTION;
}
void
touchmotion(struct libinput_event *e)
{
struct libinput_event_touch *tevent;
struct timespec now;
int slot;
tevent = libinput_event_get_touch_event(e);
slot = libinput_event_touch_get_slot(tevent);
xend[slot] = libinput_event_touch_get_x_transformed(tevent, screenwidth);
yend[slot] = libinput_event_touch_get_y_transformed(tevent, screenheight);
if (have_actmode_pressed) {
Swipe swipe = gesturecalculateswipe(
xstart[slot], ystart[slot], xend[slot], yend[slot], distancethreshold_pressed
);
if (swipe != -1) {
Edge edge = gesturecalculateedge(
xstart[slot], ystart[slot], xend[slot], yend[slot]
);
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
if (
timeoutms >
((now.tv_sec - timedown.tv_sec) * 1000000 + (now.tv_nsec - timedown.tv_nsec) / 1000) / 1000
) {
if (verbose) fprintf(stderr, "(Attempting to find matching pressed gesture)\n");
if (gestureexecute(swipe, nfdown, edge, DistanceAny, ActModePressed)) {
//we found and executed a matching gesture, reset the slot
if (verbose) fprintf(stderr, "(Pressed gestured Executed)\n");
xstart[slot] = xend[slot];
ystart[slot] = yend[slot];
timedown = now;
}
}
}
}
}
void
touchup(struct libinput_event *e)
{
int slot;
struct libinput_event_touch *tevent;
struct timespec now;
tevent = libinput_event_get_touch_event(e);
slot = libinput_event_touch_get_slot(tevent);
nfdown--;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
// E.g. invalid motion, it didn't begin/end from anywhere
if (
xstart[slot] == NOMOTION || ystart[slot] == NOMOTION ||
xend[slot] == NOMOTION || yend[slot] == NOMOTION
) return;
Swipe swipe = gesturecalculateswipe(
xstart[slot], ystart[slot], xend[slot], yend[slot], distancethreshold
);
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 millisecond limit, exec, & reset
if (nfdown == 0) {
if (
timeoutms >
((now.tv_sec - timedown.tv_sec) * 1000000 + (now.tv_nsec - timedown.tv_nsec) / 1000) / 1000
) gestureexecute(swipe, nfpendingswipe, edge, distance, ActModeReleased);
nfpendingswipe = 0;
}
}
void
run(void)
{
int i;
struct libinput *li;
struct libinput_event *event;
struct libinput_device *d;
int selectresult;
fd_set fdset;
const static struct libinput_interface interface = {
.open_restricted = libinputopenrestricted,
.close_restricted = libinputcloserestricted,
};
if ((li = libinput_path_create_context(&interface, NULL)) == NULL)
die("Failed to initialize context");
if ((d = libinput_path_add_device(li, device)) == NULL) {
die("Couldn't bind event from dev filesystem");
} 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");
}
// E.g. initially invalidate every slot
for (i = 0; i < MAXSLOTS; i++) {
xend[i] = NOMOTION;
yend[i] = NOMOTION;
xstart[i] = NOMOTION;
ystart[i] = NOMOTION;
}
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?");
} 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);
}
#ifdef WITH_WAYLAND
static void
display_handle_geometry(void *data, struct wl_output *wl_output, int x, int y, int physical_width, int physical_height, int subpixel, const char *make, const char *model, int transform)
{
orientation = transform;
if (orientation == 1) {
orientation = 3;
} else if (orientation == 3) {
orientation = 1;
}
}
static void
display_handle_done(void *data, struct wl_output *wl_output)
{
}
static void
display_handle_scale(void *data, struct wl_output *wl_output, int32_t scale)
{
}
static void
display_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int width, int height, int refresh)
{
screenwidth = width;
screenheight = height;
}
static const struct wl_output_listener output_listener = {
.geometry = display_handle_geometry,
.mode = display_handle_mode,
.done = display_handle_done,
.scale = display_handle_scale
};
static void
registry_global(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version)
{
if (strcmp(interface, "wl_output") == 0) {
if (!wl_output) {
wl_output = wl_registry_bind(wl_registry, name, &wl_output_interface, 3);
wl_output_add_listener(wl_output, &output_listener, NULL);
}
};
}
static void
registry_global_remove(void *data,
struct wl_registry *wl_registry, uint32_t name)
{
}
static const struct
wl_registry_listener wl_registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
#endif
int
main(int argc, char *argv[])
{
int i, j;
char *gestpt;
gestsarr = NULL;
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")) {
if (i == argc - 1) die("option -d expects a value");
device = argv[++i];
} else if (!strcmp(argv[i], "-t")) {
if (i == argc - 1) die("option -t expects a value");
distancethreshold = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-T")) {
if (i == argc - 1) die("option -T expects a value");
distancethreshold_pressed = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-r")) {
if (i == argc - 1) die("option -r expects a value");
degreesleniency = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-m")) {
if (i == argc - 1) die("option -m expects a value");
timeoutms = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-o")) {
if (i == argc - 1) die("option -o expects a value");
orientation = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-h")) {
if (i == argc - 1) die("option -h expects a value");
screenheight = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-w")) {
if (i == argc - 1) die("option -w expects a value");
screenwidth = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-s")) {
if (i == argc - 1) die("option -s expects a value");
edgessizecaling = atof(argv[++i]);
} else if (!strcmp(argv[i], "-g")) {
if (i == argc - 1) die("option -g expects a value");
gestsarrlen++;
gestsarr = realloc(gestsarr, (gestsarrlen * sizeof(Gesture)));
if (gestsarr == NULL) {
perror("Could not allocate memory");
exit(EXIT_FAILURE);
}
gestpt = strtok(argv[++i], ",");
for (j = 0; gestpt != NULL && j < 6; gestpt = strtok(NULL, ","), j++) {
switch(j) {
case 0: gestsarr[gestsarrlen - 1].nfswipe = atoi(gestpt); break;
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;
if (!strcmp(gestpt, "UD")) gestsarr[gestsarrlen-1].swipe = SwipeUD;
if (!strcmp(gestpt, "DLUR")) gestsarr[gestsarrlen-1].swipe = SwipeDLUR;
if (!strcmp(gestpt, "URDL")) gestsarr[gestsarrlen-1].swipe = SwipeURDL;
if (!strcmp(gestpt, "ULDR")) gestsarr[gestsarrlen-1].swipe = SwipeULDR;
if (!strcmp(gestpt, "DRUL")) gestsarr[gestsarrlen-1].swipe = SwipeDRUL;
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:
if (!strcmp(gestpt, "P")) {
gestsarr[gestsarrlen-1].actmode = ActModePressed;
} else {
gestsarr[gestsarrlen-1].actmode = ActModeReleased;
if (strcmp(gestpt, "R") != 0) {
//for backward compatibility, allow fourth field to hold command
gestsarr[gestsarrlen - 1].command = gestpt;
}
}
break;
case 5: 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,L,*,R,notify-send swiped left to right from left edge']\n");
exit(1);
}
}
// Get display size (if not set with -w/-h)
if (screenwidth == 0 && screenheight == 0) {
if (getenv("WAYLAND_DISPLAY")) {
#ifdef WITH_WAYLAND
wl_display = wl_display_connect(NULL);
wl_registry = wl_display_get_registry(wl_display);
wl_registry_add_listener(wl_registry, &wl_registry_listener, NULL);
wl_display_roundtrip(wl_display);
wl_display_dispatch(wl_display);
#else
die("Wayland environment detected but support for it is not enabled");
#endif
} else if (getenv("DISPLAY")) {
#ifdef WITH_X11
Display *dpy;
if (!(dpy = XOpenDisplay(0))) {
die("Cannot open X display");
}
screen = DefaultScreen(dpy);
if (0 == orientation % 2) {
screenwidth = DisplayWidth(dpy, screen);
screenheight = DisplayHeight(dpy, screen);
} else {
screenwidth = DisplayHeight(dpy, screen);
screenheight = DisplayWidth(dpy, screen);
}
#else
die("X11 environment detected but support for it is not enabled");
#endif
} else {
die("Cannot detect display environment ($DISPLAY and $WAYLAND_DISPLAY unset); and no -w / -h screen geometry parameter options set");
}
}
// E.g. no gestures passed on CLI - used gestures defined in config.def.h
if (gestsarrlen == 0) {
gestsarr = malloc(sizeof(gestures));
if (gestsarr == NULL) {
perror("Could not allocate memory");
exit(EXIT_FAILURE);
}
gestsarrlen = sizeof(gestures) / sizeof(Gesture);
memcpy(gestsarr, gestures, sizeof(gestures));
}
// 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);
//Detect whether ActMode pressed is used
if (gestsarr[i].actmode == ActModePressed) have_actmode_pressed++;
}
run();
return 0;
}