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

Binary file not shown.

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;
}

View file

@ -0,0 +1,5 @@
BasedOnStyle: LLVM
IndentWidth: 4
AlwaysBreakAfterDefinitionReturnType: All
BreakBeforeBraces: Linux
SortIncludes: false

View file

@ -0,0 +1,9 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*.c]
indent_style = space
indent_size = 4
charset = utf-8

14
packages/wvkbd/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
*~
*\#*
*.o
*-client-protocol.h
include/config.h
result
.gdb_history
.envrc
.direnv
*.log
wvkbd
*.1
config.h
wvkbd-mobintl

60
packages/wvkbd/Makefile Normal file
View file

@ -0,0 +1,60 @@
include config.mk
NAME=wvkbd
BIN=${NAME}-${LAYOUT}
SRC=.
MAN1 = ${NAME}.1
PKGS = wayland-client xkbcommon pangocairo
WVKBD_SOURCES += $(wildcard $(SRC)/*.c)
WVKBD_HEADERS += $(wildcard $(SRC)/*.h)
PKG_CONFIG ?= pkg-config
CFLAGS += -std=gnu99 -Wall -g -DWITH_WAYLAND_SHM -DLAYOUT=\"layout.${LAYOUT}.h\" -DKEYMAP=\"keymap.${LAYOUT}.h\"
CFLAGS += $(shell $(PKG_CONFIG) --cflags $(PKGS))
LDFLAGS += $(shell $(PKG_CONFIG) --libs $(PKGS)) -lm -lutil -lrt
WAYLAND_HEADERS = $(wildcard proto/*.xml)
HDRS = $(WAYLAND_HEADERS:.xml=-client-protocol.h)
WAYLAND_SRC = $(HDRS:.h=.c)
SOURCES = $(WVKBD_SOURCES) $(WAYLAND_SRC)
SCDOC=scdoc
DOCS = wvkbd.1
OBJECTS = $(SOURCES:.c=.o)
all: ${BIN} ${DOCS}
config.h:
cp config.def.h config.h
proto/%-client-protocol.c: proto/%.xml
wayland-scanner code < $? > $@
proto/%-client-protocol.h: proto/%.xml
wayland-scanner client-header < $? > $@
$(OBJECTS): $(HDRS) $(WVKBD_HEADERS)
wvkbd-${LAYOUT}: config.h $(OBJECTS) layout.${LAYOUT}.h
$(CC) -o wvkbd $(OBJECTS) $(LDFLAGS)
clean:
rm -f $(OBJECTS) $(HDRS) $(WAYLAND_SRC) ${BIN} ${DOCS}
format:
clang-format -i $(WVKBD_SOURCES) $(WVKBD_HEADERS)
%: %.scd
$(SCDOC) < $< > $@
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f ${NAME} ${DESTDIR}${PREFIX}/bin
chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
mkdir -p "${DESTDIR}${MANPREFIX}/man1"
sed "s/VERSION/${VERSION}/g" < ${MAN1} > ${DESTDIR}${MANPREFIX}/man1/${MAN1}
chmod 644 ${DESTDIR}${MANPREFIX}/man1/${MAN1}

View file

@ -0,0 +1,44 @@
#ifndef config_def_h_INCLUDED
#define config_def_h_INCLUDED
#define DEFAULT_FONT "Jost* 12"
#define DEFAULT_ROUNDING 5
static const int transparency = 255;
struct clr_scheme schemes[] = {
{
/* colors */
.bg = {.bgra = {32, 32, 32, transparency}},
.fg = {.bgra = {59, 40, 36, transparency}},
.high = {.bgra = {214, 177, 169, transparency}},
.swipe = {.bgra = {100, 255, 100, 64}},
.text = {.color = UINT32_MAX},
.font = DEFAULT_FONT,
.rounding = DEFAULT_ROUNDING,
},
{
/* colors */
.bg = {.bgra = {32, 32, 32, transparency}},
.fg = {.bgra = {38, 27, 26, transparency}},
.high = {.bgra = {214, 177, 169, transparency}},
.swipe = {.bgra = {100, 255, 100, 64}},
.text = {.color = UINT32_MAX},
.font = DEFAULT_FONT,
.rounding = DEFAULT_ROUNDING,
}};
/* layers is an ordered list of layouts, used to cycle through */
static enum layout_id layers[] = {
Simple, // First layout is the default layout on startup
Cyrillic,
NumLayouts // signals the last item, may not be omitted
};
/* layers is an ordered list of layouts, used to cycle through */
static enum layout_id landscape_layers[] = {
Simple, // First layout is the default layout on startup
Cyrillic,
NumLayouts // signals the last item, may not be omitted
};
#endif // config_def_h_INCLUDED

5
packages/wvkbd/config.mk Normal file
View file

@ -0,0 +1,5 @@
VERSION = 0.17
CFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=700
PREFIX = /usr
MANPREFIX = ${PREFIX}/share/man
LAYOUT = vistath

285
packages/wvkbd/drw.c Normal file
View file

@ -0,0 +1,285 @@
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include "drw.h"
#include "shm_open.h"
#include "math.h"
void drwbuf_handle_release(void *data, struct wl_buffer *wl_buffer) {
struct drwsurf *ds = data;
ds->released = true;
};
const struct wl_buffer_listener buffer_listener = {
.release = drwbuf_handle_release
};
void drwsurf_handle_frame_cb(void* data, struct wl_callback* callback,
uint32_t time)
{
struct drwsurf *ds = data;
wl_callback_destroy(ds->frame_cb);
ds->frame_cb = NULL;
cairo_rectangle_int_t r = {0};
for (int i = 0; i < cairo_region_num_rectangles(ds->damage); i++) {
cairo_region_get_rectangle(ds->damage, i, &r);
wl_surface_damage(ds->surf, r.x, r.y, r.width, r.height);
};
cairo_region_subtract(ds->damage, ds->damage);
drwsurf_attach(ds);
}
const struct wl_callback_listener frame_listener = {
.done = drwsurf_handle_frame_cb
};
void drwsurf_register_frame_cb(struct drwsurf *ds)
{
if (ds->frame_cb)
return;
if (!ds->attached)
return;
ds->frame_cb = wl_surface_frame(ds->surf);
wl_callback_add_listener(ds->frame_cb, &frame_listener, ds);
wl_surface_commit(ds->surf);
}
void drwsurf_damage(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
cairo_rectangle_int_t rect = { x, y, w, h };
cairo_region_union_rectangle(ds->damage, &rect);
cairo_region_union_rectangle(ds->backport_damage, &rect);
drwsurf_register_frame_cb(ds);
}
void
drwsurf_resize(struct drwsurf *ds, uint32_t w, uint32_t h, double s)
{
ds->scale = s;
ds->width = ceil(w * s);
ds->height = ceil(h * s);
if (ds->damage)
cairo_region_destroy(ds->damage);
ds->damage = cairo_region_create();
if (ds->backport_damage)
cairo_region_destroy(ds->backport_damage);
ds->backport_damage = cairo_region_create();
ds->released = true;
setup_buffer(ds, ds->back_buffer);
setup_buffer(ds, ds->display_buffer);
}
void
drwsurf_backport(struct drwsurf *ds)
{
cairo_save(ds->back_buffer->cairo);
cairo_scale(ds->back_buffer->cairo, 1/ds->scale, 1/ds->scale);
cairo_set_operator(ds->back_buffer->cairo, CAIRO_OPERATOR_SOURCE);
cairo_rectangle_int_t r = {0};
for (int i = 0; i < cairo_region_num_rectangles(ds->backport_damage); i++) {
cairo_region_get_rectangle(ds->backport_damage, i, &r);
cairo_set_source_surface(ds->back_buffer->cairo, ds->display_buffer->cairo_surf, 0, 0);
cairo_rectangle(
ds->back_buffer->cairo,
r.x * ds->scale,
r.y * ds->scale,
r.width * ds->scale,
r.height * ds->scale
);
cairo_fill(ds->back_buffer->cairo);
};
cairo_restore(ds->back_buffer->cairo);
cairo_region_subtract(ds->backport_damage, ds->backport_damage);
}
void
drwsurf_attach(struct drwsurf *ds)
{
wl_surface_attach(ds->surf, ds->back_buffer->buf, 0, 0);
wl_surface_commit(ds->surf);
ds->released = false;
ds->attached = true;
}
void
drwsurf_flip(struct drwsurf *ds)
{
if (ds->released)
return;
ds->released = true;
struct drwbuf *tmp = ds->back_buffer;
ds->back_buffer = ds->display_buffer;
ds->display_buffer = tmp;
drwsurf_backport(ds);
}
void
drw_draw_text(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, uint32_t b, const char *label,
PangoFontDescription *font_description)
{
drwsurf_flip(ds);
struct drwbuf *d = ds->back_buffer;
drwsurf_damage(ds, x, y, w, h);
cairo_save(d->cairo);
pango_layout_set_font_description(d->layout, font_description);
cairo_set_source_rgba(
d->cairo, color.bgra[2] / (double)255, color.bgra[1] / (double)255,
color.bgra[0] / (double)255, color.bgra[3] / (double)255);
cairo_move_to(d->cairo, x + w / 2, y + h / 2);
pango_layout_set_text(d->layout, label, -1);
pango_layout_set_width(d->layout, (w - (b * 2)) * PANGO_SCALE);
pango_layout_set_height(d->layout, (h - (b * 2)) * PANGO_SCALE);
int width, height;
pango_layout_get_pixel_size(d->layout, &width, &height);
cairo_rel_move_to(d->cairo, -width / 2, -height / 2);
pango_cairo_show_layout(d->cairo, d->layout);
cairo_restore(d->cairo);
}
void
drw_do_clear(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
drwsurf_flip(ds);
struct drwbuf *d = ds->back_buffer;
drwsurf_damage(ds, x, y, w, h);
cairo_save(d->cairo);
cairo_set_operator(d->cairo, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(d->cairo, x, y, w, h);
cairo_fill(d->cairo);
cairo_restore(d->cairo);
}
void
drw_do_rectangle(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, bool over, int rounding)
{
drwsurf_flip(ds);
struct drwbuf *d = ds->back_buffer;
drwsurf_damage(ds, x, y, w, h);
cairo_save(d->cairo);
if (over) {
cairo_set_operator(d->cairo, CAIRO_OPERATOR_OVER);
} else {
cairo_set_operator(d->cairo, CAIRO_OPERATOR_SOURCE);
}
if (rounding > 0) {
double radius = rounding / 1.0;
double degrees = M_PI / 180.0;
cairo_new_sub_path (d->cairo);
cairo_arc (d->cairo, x + w - radius, y + radius, radius, -90 * degrees, 0 * degrees);
cairo_arc (d->cairo, x + w - radius, y + h - radius, radius, 0 * degrees, 90 * degrees);
cairo_arc (d->cairo, x + radius, y + h - radius, radius, 90 * degrees, 180 * degrees);
cairo_arc (d->cairo, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
cairo_close_path (d->cairo);
cairo_set_source_rgba(
d->cairo, color.bgra[2] / (double)255, color.bgra[1] / (double)255,
color.bgra[0] / (double)255, color.bgra[3] / (double)255);
cairo_fill (d->cairo);
cairo_set_source_rgba(d->cairo, 0, 0, 0, 0.9);
cairo_set_line_width(d->cairo, 1.0);
cairo_stroke(d->cairo);
cairo_restore(d->cairo);
}
else {
cairo_rectangle(d->cairo, x, y, w, h);
cairo_set_source_rgba(
d->cairo, color.bgra[2] / (double)255, color.bgra[1] / (double)255,
color.bgra[0] / (double)255, color.bgra[3] / (double)255);
cairo_fill(d->cairo);
cairo_restore(d->cairo);
}
}
void
drw_fill_rectangle(struct drwsurf *d, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, int rounding)
{
drw_do_rectangle(d, color, x, y, w, h, false, rounding);
}
void
drw_over_rectangle(struct drwsurf *d, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, int rounding)
{
drw_do_rectangle(d, color, x, y, w, h, true, rounding);
}
uint32_t
setup_buffer(struct drwsurf *drwsurf, struct drwbuf *drwbuf)
{
int prev_size = drwbuf->size;
int stride = drwsurf->width * 4;
drwbuf->size = stride * drwsurf->height;
int fd = allocate_shm_file(drwbuf->size);
if (fd == -1) {
return 1;
}
if (drwbuf->pool_data)
munmap(drwbuf->pool_data, prev_size);
drwbuf->pool_data =
mmap(NULL, drwbuf->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (drwbuf->pool_data == MAP_FAILED) {
close(fd);
return 1;
}
if (drwbuf->buf)
wl_buffer_destroy(drwbuf->buf);
struct wl_shm_pool *pool =
wl_shm_create_pool(drwsurf->ctx->shm, fd, drwbuf->size);
drwbuf->buf =
wl_shm_pool_create_buffer(pool, 0, drwsurf->width, drwsurf->height,
stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
wl_buffer_add_listener(drwbuf->buf, &buffer_listener, drwsurf);
if (drwbuf->cairo_surf)
cairo_surface_destroy(drwbuf->cairo_surf);
drwbuf->cairo_surf = cairo_image_surface_create_for_data(
drwbuf->pool_data, CAIRO_FORMAT_ARGB32, drwsurf->width,
drwsurf->height, stride);
if (drwbuf->cairo)
cairo_destroy(drwbuf->cairo);
drwbuf->cairo = cairo_create(drwbuf->cairo_surf);
cairo_scale(drwbuf->cairo, drwsurf->scale, drwsurf->scale);
cairo_set_antialias(drwbuf->cairo, CAIRO_ANTIALIAS_NONE);
drwbuf->layout = pango_cairo_create_layout(drwbuf->cairo);
pango_layout_set_auto_dir(drwbuf->layout, false);
cairo_save(drwbuf->cairo);
return 0;
}

59
packages/wvkbd/drw.h Normal file
View file

@ -0,0 +1,59 @@
#ifndef __DRW_H
#define __DRW_H
#include <pango/pangocairo.h>
#include <stdbool.h>
struct drw {
struct wl_shm *shm;
};
struct drwbuf {
uint32_t size;
struct wl_buffer *buf;
cairo_surface_t *cairo_surf;
cairo_t *cairo;
PangoLayout *layout;
unsigned char *pool_data;
};
struct drwsurf {
uint32_t width, height;
double scale;
struct drw *ctx;
struct wl_surface *surf;
struct wl_shm *shm;
struct wl_callback *frame_cb;
cairo_region_t *damage, *backport_damage;
bool attached;
bool released;
struct drwbuf *back_buffer;
struct drwbuf *display_buffer;
};
struct kbd;
void drwsurf_resize(struct drwsurf *ds, uint32_t w, uint32_t h, double s);
void drwsurf_attach(struct drwsurf *ds);
typedef union {
uint8_t bgra[4];
uint32_t color;
} Color;
void drw_do_clear(struct drwsurf *ds, uint32_t x, uint32_t y,
uint32_t w, uint32_t h);
void drw_do_rectangle(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, bool fill, int rounding);
void drw_fill_rectangle(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, int rounding);
void drw_over_rectangle(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, int rounding);
void drw_draw_text(struct drwsurf *ds, Color color, uint32_t x, uint32_t y,
uint32_t w, uint32_t h, uint32_t b, const char *label,
PangoFontDescription *font_description);
uint32_t setup_buffer(struct drwsurf *ds, struct drwbuf *db);
#endif

695
packages/wvkbd/keyboard.c Normal file
View file

@ -0,0 +1,695 @@
#include "proto/virtual-keyboard-unstable-v1-client-protocol.h"
#include <linux/input-event-codes.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include <ctype.h>
#include "keyboard.h"
#include "drw.h"
#include "os-compatibility.h"
#define MAX_LAYERS 25
/* lazy die macro */
#define die(...) \
fprintf(stderr, __VA_ARGS__); \
exit(1)
#ifndef KEYMAP
#error "make sure to define KEYMAP"
#endif
#include KEYMAP
void
kbd_switch_layout(struct kbd *kb, struct layout *l, size_t layer_index)
{
kb->prevlayout = kb->layout;
if ((kb->layer_index != kb->last_abc_index) && (kb->layout->abc)) {
kb->last_abc_layout = kb->layout;
kb->last_abc_index = kb->layer_index;
}
kb->layer_index = layer_index;
kb->layout = l;
if (kb->debug)
fprintf(stderr, "Switching to layout %s, layer_index %ld\n",
kb->layout->name, layer_index);
if (!l->keymap_name)
fprintf(stderr, "Layout has no keymap!"); // sanity check
if ((!kb->prevlayout) ||
(strcmp(kb->prevlayout->keymap_name, kb->layout->keymap_name) != 0)) {
fprintf(stderr, "Switching to keymap %s\n", kb->layout->keymap_name);
create_and_upload_keymap(kb, kb->layout->keymap_name, 0, 0);
}
kbd_draw_layout(kb);
}
void
kbd_next_layer(struct kbd *kb, struct key *k, bool invert)
{
size_t layer_index = kb->layer_index;
if ((kb->mods & Ctrl) || (kb->mods & Alt) || (kb->mods & AltGr) ||
((bool)kb->compose)) {
// with modifiers ctrl/alt/altgr: switch to the first layer
layer_index = 0;
kb->mods = 0;
} else if ((kb->mods & Shift) || (kb->mods & CapsLock) || (invert)) {
// with modifiers shift/capslock or invert set: switch to the previous
// layout in the layer sequence
if (layer_index > 0) {
layer_index--;
} else {
size_t layercount = 0;
for (size_t i = 0; layercount == 0; i++) {
if (kb->landscape) {
if (kb->landscape_layers[i] == NumLayouts)
layercount = i;
} else {
if (kb->layers[i] == NumLayouts)
layercount = i;
}
}
layer_index = layercount - 1;
}
if (!invert)
kb->mods ^= Shift;
} else {
// normal behaviour: switch to the next layout in the layer sequence
layer_index++;
}
size_t layercount = 0;
for (size_t i = 0; layercount == 0; i++) {
if (kb->landscape) {
if (kb->landscape_layers[i] == NumLayouts)
layercount = i;
} else {
if (kb->layers[i] == NumLayouts)
layercount = i;
}
}
if (layer_index >= layercount) {
if (kb->debug)
fprintf(stderr, "wrapping layer_index back to start\n");
layer_index = 0;
}
enum layout_id layer;
if (kb->landscape) {
layer = kb->landscape_layers[layer_index];
} else {
layer = kb->layers[layer_index];
}
if (((bool)kb->compose) && (k)) {
kb->compose = 0;
kbd_draw_key(kb, k, Unpress);
}
kbd_switch_layout(kb, &kb->layouts[layer], layer_index);
}
uint8_t
kbd_get_rows(struct layout *l)
{
uint8_t rows = 0;
struct key *k = l->keys;
while (k->type != Last) {
if (k->type == EndRow) {
rows++;
}
k++;
}
return rows + 1;
}
enum layout_id *
kbd_init_layers(char *layer_names_list)
{
enum layout_id *layers;
uint8_t numlayers = 0;
bool found;
char *s;
int i;
layers = malloc(MAX_LAYERS * sizeof(enum layout_id));
s = strtok(layer_names_list, ",");
while (s != NULL) {
if (numlayers + 1 == MAX_LAYERS) {
fprintf(stderr, "too many layers specified");
exit(3);
}
found = false;
for (i = 0; i < NumLayouts - 1; i++) {
if (layouts[i].name && strcmp(layouts[i].name, s) == 0) {
fprintf(stderr, "layer #%d = %s\n", numlayers + 1, s);
layers[numlayers++] = i;
found = true;
break;
}
}
if (!found) {
fprintf(stderr, "No such layer: %s\n", s);
exit(3);
}
s = strtok(NULL, ",");
}
layers[numlayers] = NumLayouts; // mark the end of the sequence
if (numlayers == 0) {
fprintf(stderr, "No layers defined\n");
exit(3);
}
return layers;
}
void
kbd_init(struct kbd *kb, struct layout *layouts, char *layer_names_list,
char *landscape_layer_names_list)
{
int i;
fprintf(stderr, "Initializing keyboard\n");
kb->layouts = layouts;
for (i = 0; i < NumLayouts - 1; i++)
;
fprintf(stderr, "Found %d layouts\n", i);
kb->layer_index = 0;
kb->last_abc_index = 0;
if (layer_names_list)
kb->layers = kbd_init_layers(layer_names_list);
if (landscape_layer_names_list)
kb->landscape_layers = kbd_init_layers(landscape_layer_names_list);
i = 0;
enum layout_id lid = kb->layers[0];
while (lid != NumLayouts) {
lid = kb->layers[++i];
}
fprintf(stderr, "Found %d layers\n", i);
enum layout_id layer;
if (kb->landscape) {
layer = kb->landscape_layers[kb->layer_index];
} else {
layer = kb->layers[kb->layer_index];
}
kb->layout = &kb->layouts[layer];
kb->last_abc_layout = &kb->layouts[layer];
/* upload keymap */
create_and_upload_keymap(kb, kb->layout->keymap_name, 0, 0);
}
void
kbd_init_layout(struct layout *l, uint32_t width, uint32_t height)
{
uint32_t x = 0, y = 0;
uint8_t rows = kbd_get_rows(l);
l->keyheight = height / rows;
struct key *k = l->keys;
double rowlength = kbd_get_row_length(k);
double rowwidth = 0.0;
while (k->type != Last) {
if (k->type == EndRow) {
y += l->keyheight;
x = 0;
rowwidth = 0.0;
rowlength = kbd_get_row_length(k + 1);
} else if (k->width > 0) {
k->x = x;
k->y = y;
k->w = ((double)width / rowlength) * k->width;
x += k->w;
rowwidth += k->width;
if (x < (rowwidth / rowlength) * (double)width) {
k->w++;
x++;
}
}
k->h = l->keyheight;
k++;
}
}
double
kbd_get_row_length(struct key *k)
{
double l = 0.0;
while ((k->type != Last) && (k->type != EndRow)) {
l += k->width;
k++;
}
return l;
}
struct key *
kbd_get_key(struct kbd *kb, uint32_t x, uint32_t y)
{
struct layout *l = kb->layout;
struct key *k = l->keys;
if (kb->debug)
fprintf(stderr, "get key: +%d+%d\n", x, y);
while (k->type != Last) {
if ((k->type != EndRow) && (k->type != Pad) && (k->type != Pad) &&
(x >= k->x) && (y >= k->y) && (x < k->x + k->w) &&
(y < k->y + k->h)) {
return k;
}
k++;
}
return NULL;
}
size_t
kbd_get_layer_index(struct kbd *kb, struct layout *l)
{
for (size_t i = 0; i < NumLayouts - 1; i++) {
if (l == &kb->layouts[i]) {
return i;
}
}
return 0;
}
void
kbd_unpress_key(struct kbd *kb, uint32_t time)
{
bool unlatch_shift, unlatch_ctrl, unlatch_alt, unlatch_super, unlatch_altgr;
unlatch_shift = unlatch_ctrl = unlatch_alt = unlatch_super = unlatch_altgr = false;
if (kb->last_press) {
unlatch_shift = (kb->mods & Shift) == Shift;
unlatch_ctrl = (kb->mods & Ctrl) == Ctrl;
unlatch_alt = (kb->mods & Alt) == Alt;
unlatch_super = (kb->mods & Super) == Super;
unlatch_altgr = (kb->mods & AltGr) == AltGr;
if (unlatch_shift) kb->mods ^= Shift;
if (unlatch_ctrl) kb->mods ^= Ctrl;
if (unlatch_alt) kb->mods ^= Alt;
if (unlatch_super) kb->mods ^= Super;
if (unlatch_altgr) kb->mods ^= AltGr;
if (unlatch_shift||unlatch_ctrl||unlatch_alt||unlatch_super||unlatch_altgr) {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
}
if (kb->last_press->type == Copy) {
zwp_virtual_keyboard_v1_key(kb->vkbd, time, 127, // COMP key
WL_KEYBOARD_KEY_STATE_RELEASED);
} else {
if ((kb->last_press->code == KEY_SPACE) && (unlatch_shift)) {
// shift + space is tab
zwp_virtual_keyboard_v1_key(kb->vkbd, time, KEY_TAB,
WL_KEYBOARD_KEY_STATE_RELEASED);
} else {
zwp_virtual_keyboard_v1_key(kb->vkbd, time,
kb->last_press->code,
WL_KEYBOARD_KEY_STATE_RELEASED);
}
}
if (kb->compose >= 2) {
kb->compose = 0;
kbd_switch_layout(kb, kb->last_abc_layout, kb->last_abc_index);
} else if (unlatch_shift||unlatch_ctrl||unlatch_alt||unlatch_super||unlatch_altgr) {
kbd_draw_layout(kb);
} else {
kbd_draw_key(kb, kb->last_press, Unpress);
}
kb->last_press = NULL;
}
}
void
kbd_release_key(struct kbd *kb, uint32_t time)
{
kbd_unpress_key(kb, time);
if (kb->print_intersect && kb->last_swipe) {
printf("\n");
// Important so autocompleted words get typed in time
fflush(stdout);
kbd_draw_layout(kb);
kb->last_swipe = NULL;
}
kbd_clear_last_popup(kb);
}
void
kbd_motion_key(struct kbd *kb, uint32_t time, uint32_t x, uint32_t y)
{
// Output intersecting keys
// (for external 'swiping'-based accelerators).
if (kb->print_intersect) {
if (kb->last_press) {
kbd_unpress_key(kb, time);
// Redraw last press as a swipe.
kbd_draw_key(kb, kb->last_swipe, Swipe);
}
struct key *intersect_key;
intersect_key = kbd_get_key(kb, x, y);
if (intersect_key && (!kb->last_swipe ||
intersect_key->label != kb->last_swipe->label)) {
kbd_print_key_stdout(kb, intersect_key);
kb->last_swipe = intersect_key;
kbd_draw_key(kb, kb->last_swipe, Swipe);
}
} else {
kbd_unpress_key(kb, time);
}
kbd_clear_last_popup(kb);
}
void
kbd_press_key(struct kbd *kb, struct key *k, uint32_t time)
{
if ((kb->compose == 1) && (k->type != Compose) && (k->type != Mod)) {
if ((k->type == NextLayer) || (k->type == BackLayer) ||
((k->type == Code) && (k->code == KEY_SPACE))) {
kb->compose = 0;
if (kb->debug)
fprintf(stderr, "showing layout index\n");
kbd_switch_layout(kb, &kb->layouts[Index], 0);
return;
} else if (k->layout) {
kb->compose++;
if (kb->debug)
fprintf(stderr, "showing compose %d\n", kb->compose);
kbd_switch_layout(kb, k->layout,
kbd_get_layer_index(kb, k->layout));
return;
} else {
return;
}
}
switch (k->type) {
case Code:
if (k->code_mod) {
if (k->reset_mod) {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, k->code_mod, 0, 0,
0);
} else {
zwp_virtual_keyboard_v1_modifiers(
kb->vkbd, kb->mods ^ k->code_mod, 0, 0, 0);
}
} else {
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
}
kb->last_swipe = kb->last_press = k;
kbd_draw_key(kb, k, Press);
if ((k->code == KEY_SPACE) && (kb->mods & Shift)) {
// shift space is tab
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, 0, 0, 0, 0);
zwp_virtual_keyboard_v1_key(kb->vkbd, time, KEY_TAB,
WL_KEYBOARD_KEY_STATE_PRESSED);
} else {
zwp_virtual_keyboard_v1_key(kb->vkbd, time, kb->last_press->code,
WL_KEYBOARD_KEY_STATE_PRESSED);
}
if (kb->print || kb->print_intersect)
kbd_print_key_stdout(kb, k);
if (kb->compose) {
if (kb->debug)
fprintf(stderr, "pressing composed key\n");
kb->compose++;
}
break;
case Mod:
kb->mods ^= k->code;
if ((k->code == Shift) || (k->code == CapsLock)) {
kbd_draw_layout(kb);
} else {
if (kb->mods & k->code) {
kbd_draw_key(kb, k, Press);
} else {
kbd_draw_key(kb, k, Unpress);
}
}
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
break;
case Layout:
// switch to the layout determined by the key
kbd_switch_layout(kb, k->layout, kbd_get_layer_index(kb, k->layout));
// reset previous layout to default/first so we don't get any weird
// cycles
kb->last_abc_index = 0;
if (kb->landscape) {
kb->last_abc_layout = &kb->layouts[kb->landscape_layers[0]];
} else {
kb->last_abc_layout = &kb->layouts[kb->layers[0]];
}
break;
case Compose:
// switch to the associated layout determined by the *next* keypress
if (kb->compose == 0) {
kb->compose = 1;
} else {
kb->compose = 0;
}
if ((bool)kb->compose) {
kbd_draw_key(kb, k, Press);
} else {
kbd_draw_key(kb, k, Unpress);
}
break;
case NextLayer: //(also handles previous layer when shift modifier is on, or
//"first layer" with other modifiers)
kbd_next_layer(kb, k, false);
break;
case BackLayer: // triggered when "Abc" keys are pressed
// switch to the last active alphabetical layout
if (kb->last_abc_layout) {
kb->compose = 0;
kbd_switch_layout(kb, kb->last_abc_layout, kb->last_abc_index);
// reset previous layout to default/first so we don't get any weird
// cycles
kb->last_abc_index = 0;
if (kb->landscape) {
kb->last_abc_layout = &kb->layouts[kb->landscape_layers[0]];
} else {
kb->last_abc_layout = &kb->layouts[kb->layers[0]];
}
}
break;
case Copy:
// copy code as unicode chr by setting a temporary keymap
kb->last_swipe = kb->last_press = k;
kbd_draw_key(kb, k, Press);
if (kb->debug)
fprintf(stderr, "pressing copy key\n");
create_and_upload_keymap(kb, kb->layout->keymap_name, k->code,
k->code_mod);
zwp_virtual_keyboard_v1_modifiers(kb->vkbd, kb->mods, 0, 0, 0);
zwp_virtual_keyboard_v1_key(kb->vkbd, time, 127, // COMP key
WL_KEYBOARD_KEY_STATE_PRESSED);
if (kb->print || kb->print_intersect)
kbd_print_key_stdout(kb, k);
break;
default:
break;
}
}
void
kbd_print_key_stdout(struct kbd *kb, struct key *k)
{
/* printed keys may slightly differ from the actual output
* we generally print what is on the key LABEL and only support the normal
* and shift layers. Other modifiers produce no output (Ctrl,Alt)
* */
bool handled = true;
if (k->type == Code) {
switch (k->code) {
case KEY_SPACE:
printf(" ");
break;
case KEY_ENTER:
printf("\n");
break;
case KEY_BACKSPACE:
printf("\b");
break;
case KEY_TAB:
printf("\t");
break;
default:
handled = false;
break;
}
} else if (k->type != Copy) {
return;
}
if (!handled) {
if ((kb->mods & Shift) ||
((kb->mods & CapsLock) & (strlen(k->label) == 1 && isalpha(k->label[0]))))
printf("%s", k->shift_label);
else if (!(kb->mods & Ctrl) && !(kb->mods & Alt) && !(kb->mods & Super))
printf("%s", k->label);
}
fflush(stdout);
}
void
kbd_clear_last_popup(struct kbd *kb)
{
if (kb->last_popup_w && kb->last_popup_h) {
drw_do_clear(kb->popup_surf, kb->last_popup_x, kb->last_popup_y,
kb->last_popup_w, kb->last_popup_h);
kb->last_popup_w = kb->last_popup_h = 0;
}
}
void
kbd_draw_key(struct kbd *kb, struct key *k, enum key_draw_type type)
{
const char *label = ((kb->mods & Shift)||((kb->mods & CapsLock) &&
strlen(k->label) == 1 && isalpha(k->label[0]))) ? k->shift_label : k->label;
if (kb->debug)
fprintf(stderr, "Draw key +%d+%d %dx%d -> %s\n", k->x, k->y, k->w, k->h,
label);
struct clr_scheme *scheme = &kb->schemes[k->scheme];
switch (type) {
case None:
case Unpress:
draw_inset(kb->surf, k->x, k->y, k->w, k->h, KBD_KEY_BORDER,
scheme->fg, scheme->rounding);
break;
case Press:
draw_inset(kb->surf, k->x, k->y, k->w, k->h, KBD_KEY_BORDER,
scheme->high, scheme->rounding);
break;
case Swipe:
draw_over_inset(kb->surf, k->x, k->y, k->w, k->h, KBD_KEY_BORDER,
scheme->swipe, scheme->rounding);
break;
}
drw_draw_text(kb->surf, scheme->text, k->x, k->y, k->w, k->h,
KBD_KEY_BORDER, label, scheme->font_description);
if (type == Press || type == Unpress) {
kbd_clear_last_popup(kb);
kb->last_popup_x = k->x;
kb->last_popup_y = kb->h + k->y - k->h;
kb->last_popup_w = k->w;
kb->last_popup_h = k->h;
drw_fill_rectangle(kb->popup_surf, scheme->bg, k->x,
kb->last_popup_y, k->w, k->h, scheme->rounding);
draw_inset(kb->popup_surf, k->x, kb->last_popup_y, k->w, k->h,
KBD_KEY_BORDER, scheme->high, scheme->rounding);
drw_draw_text(kb->popup_surf, scheme->text, k->x, kb->last_popup_y,
k->w, k->h, KBD_KEY_BORDER, label,
scheme->font_description);
}
}
void
kbd_draw_layout(struct kbd *kb)
{
struct drwsurf *d = kb->surf;
struct key *next_key = kb->layout->keys;
if (kb->debug)
fprintf(stderr, "Draw layout\n");
drw_fill_rectangle(d, kb->schemes[0].bg, 0, 0, kb->w, kb->h, 0);
while (next_key->type != Last) {
if ((next_key->type == Pad) || (next_key->type == EndRow)) {
next_key++;
continue;
}
if ((next_key->type == Mod && kb->mods & next_key->code) ||
(next_key->type == Compose && kb->compose)) {
kbd_draw_key(kb, next_key, Press);
} else {
kbd_draw_key(kb, next_key, None);
}
next_key++;
}
}
void
kbd_resize(struct kbd *kb, struct layout *layouts, uint8_t layoutcount)
{
fprintf(stderr, "Resize %dx%d %f, %d layouts\n", kb->w, kb->h, kb->scale,
layoutcount);
drwsurf_resize(kb->surf, kb->w, kb->h, kb->scale);
drwsurf_resize(kb->popup_surf, kb->w, kb->h * 2, kb->scale);
for (int i = 0; i < layoutcount; i++) {
if (kb->debug) {
if (layouts[i].name)
fprintf(stderr, "Initialising layout %s, keymap %s\n",
layouts[i].name, layouts[i].keymap_name);
else
fprintf(stderr, "Initialising unnamed layout %d, keymap %s\n",
i, layouts[i].keymap_name);
}
kbd_init_layout(&layouts[i], kb->w, kb->h);
}
kbd_draw_layout(kb);
}
void
draw_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color, int rounding)
{
drw_fill_rectangle(ds, color, x + border, y + border, width - (border * 2),
height - (border * 2), rounding);
}
void
draw_over_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color, int rounding)
{
drw_over_rectangle(ds, color, x + border, y + border, width - (border * 2),
height - (border * 2), rounding);
}
void
create_and_upload_keymap(struct kbd *kb, const char *name, uint32_t comp_unichr,
uint32_t comp_shift_unichr)
{
int keymap_index = -1;
for (int i = 0; i < NUMKEYMAPS; i++) {
if (!strcmp(keymap_names[i], name)) {
keymap_index = i;
}
}
if (keymap_index == -1) {
fprintf(stderr, "No such keymap defined: %s\n", name);
exit(9);
}
const char *keymap_template = keymaps[keymap_index];
size_t keymap_size = strlen(keymap_template) + 64;
char *keymap_str = malloc(keymap_size);
sprintf(keymap_str, keymap_template, comp_unichr, comp_shift_unichr);
keymap_size = strlen(keymap_str);
int keymap_fd = os_create_anonymous_file(keymap_size);
if (keymap_fd < 0) {
die("could not create keymap fd\n");
}
void *ptr = mmap(NULL, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
keymap_fd, 0);
if (ptr == (void *)-1) {
die("could not map keymap data\n");
}
if (kb->vkbd == NULL) {
die("kb.vkbd = NULL\n");
}
strcpy(ptr, keymap_str);
zwp_virtual_keyboard_v1_keymap(kb->vkbd, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
keymap_fd, keymap_size);
free((void *)keymap_str);
}

155
packages/wvkbd/keyboard.h Normal file
View file

@ -0,0 +1,155 @@
#ifndef __KEYBOARD_H
#define __KEYBOARD_H
#include "drw.h"
#define MAX_LAYERS 25
enum key_type;
enum key_modifier_type;
struct clr_scheme;
struct key;
struct layout;
struct kbd;
enum key_type {
Pad = 0, // Padding, not a pressable key
Code, // A normal key emitting a keycode
Mod, // A modifier key
Copy, // Copy key, copies the unicode value specified in code (creates and
// activates temporary keymap)
// used for keys that are not part of the keymap
Layout, // Layout switch to a specific layout
BackLayer, // Layout switch to the layout that was previously active
NextLayer, // Layout switch to the next layout in the layers sequence
Compose, // Compose modifier key, switches to a specific associated layout
// upon next keypress
EndRow, // Incidates the end of a key row
Last, // Indicated the end of a layout
};
/* Modifiers passed to the virtual_keyboard protocol. They are based on
* wayland's wl_keyboard, which doesn't document them.
*/
enum key_modifier_type {
NoMod = 0,
Shift = 1,
CapsLock = 2,
Ctrl = 4,
Alt = 8,
Super = 64,
AltGr = 128,
};
enum key_draw_type {
None = 0,
Unpress,
Press,
Swipe,
};
struct clr_scheme {
Color fg;
Color bg;
Color high;
Color swipe;
Color text;
char *font;
int rounding;
PangoFontDescription *font_description;
};
struct key {
const char *label; // primary label
const char *shift_label; // secondary label
const double width; // relative width (1.0)
const enum key_type type;
const uint32_t
code; /* code: key scancode or modifier name (see
* `/usr/include/linux/input-event-codes.h` for scancode names, and
* `keyboard.h` for modifiers)
* XKB keycodes are +8 */
struct layout *layout; // pointer back to the parent layout that holds this
// key
const uint32_t code_mod; /* modifier to force when this key is pressed */
uint8_t scheme; // index of the scheme to use
bool reset_mod; /* reset modifiers when clicked */
// actual coordinates on the surface (pixels), will be computed automatically
// for all keys
uint32_t x, y, w, h;
};
struct layout {
struct key *keys;
const char *keymap_name;
const char *name;
bool abc; //is this an alphabetical/abjad layout or not? (i.e. something that is a primary input layout)
uint32_t keyheight; // absolute height (pixels)
};
struct kbd {
bool debug;
struct layout *layout;
struct clr_scheme *schemes;
bool print;
bool print_intersect;
uint32_t w, h;
double scale;
double preferred_scale, preferred_fractional_scale;
bool landscape;
uint8_t mods;
uint8_t compose;
struct key *last_press;
struct key *last_swipe;
struct layout *prevlayout; //the previous layout, needed to keep track of keymap changes
size_t layer_index;
struct layout *last_abc_layout; //the last alphabetical layout to fall back to (may be further away than prevlayout)
size_t last_abc_index; //the layer index of the last alphabetical layout
struct layout *layouts;
enum layout_id *layers;
enum layout_id *landscape_layers;
struct drwsurf *surf;
struct drwsurf *popup_surf;
struct zwp_virtual_keyboard_v1 *vkbd;
uint32_t last_popup_x, last_popup_y, last_popup_w, last_popup_h;
};
void draw_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color, int rounding);
void draw_over_inset(struct drwsurf *ds, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t border, Color color, int rounding);
void kbd_init(struct kbd *kb, struct layout *layouts,
char *layer_names_list, char *landscape_layer_names_list);
void kbd_init_layout(struct layout *l, uint32_t width, uint32_t height);
struct key *kbd_get_key(struct kbd *kb, uint32_t x, uint32_t y);
size_t kbd_get_layer_index(struct kbd *kb, struct layout *l);
void kbd_unpress_key(struct kbd *kb, uint32_t time);
void kbd_release_key(struct kbd *kb, uint32_t time);
void kbd_motion_key(struct kbd *kb, uint32_t time, uint32_t x, uint32_t y);
void kbd_press_key(struct kbd *kb, struct key *k, uint32_t time);
void kbd_print_key_stdout(struct kbd *kb, struct key *k);
void kbd_clear_last_popup(struct kbd *kb);
void kbd_draw_key(struct kbd *kb, struct key *k, enum key_draw_type);
void kbd_draw_layout(struct kbd *kb);
void kbd_resize(struct kbd *kb, struct layout *layouts, uint8_t layoutcount);
uint8_t kbd_get_rows(struct layout *l);
double kbd_get_row_length(struct key *k);
void kbd_next_layer(struct kbd *kb, struct key *k, bool invert);
void kbd_switch_layout(struct kbd *kb, struct layout *l, size_t layer_index);
void create_and_upload_keymap(struct kbd *kb, const char *name,
uint32_t comp_unichr, uint32_t comp_shift_unichr);
#ifndef LAYOUT
#error "make sure to define LAYOUT"
#endif
#include LAYOUT
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1134
packages/wvkbd/main.c Normal file

File diff suppressed because it is too large Load diff

2
packages/wvkbd/make.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
make LAYOUT=vistath && mkdir -p -v ~/bin && mv -v ./wvkbd ~/bin

View file

@ -0,0 +1,208 @@
/*
* Copyright © 2012 Collabora, Ltd.
*
* 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 (including the
* next paragraph) 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.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "os-compatibility.h"
int
os_fd_set_cloexec(int fd)
{
long flags;
if (fd == -1)
return -1;
flags = fcntl(fd, F_GETFD);
if (flags == -1)
return -1;
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
return -1;
return 0;
}
static int
set_cloexec_or_close(int fd)
{
if (os_fd_set_cloexec(fd) != 0) {
close(fd);
return -1;
}
return fd;
}
int
os_socketpair_cloexec(int domain, int type, int protocol, int *sv)
{
int ret;
#ifdef SOCK_CLOEXEC
ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv);
if (ret == 0 || errno != EINVAL)
return ret;
#endif
ret = socketpair(domain, type, protocol, sv);
if (ret < 0)
return ret;
sv[0] = set_cloexec_or_close(sv[0]);
sv[1] = set_cloexec_or_close(sv[1]);
if (sv[0] != -1 && sv[1] != -1)
return 0;
close(sv[0]);
close(sv[1]);
return -1;
}
int
os_epoll_create_cloexec(void)
{
int fd;
#ifdef EPOLL_CLOEXEC
fd = epoll_create1(EPOLL_CLOEXEC);
if (fd >= 0)
return fd;
if (errno != EINVAL)
return -1;
#endif
fd = epoll_create(1);
return set_cloexec_or_close(fd);
}
static int
create_tmpfile_cloexec(char *tmpname)
{
int fd;
#ifdef HAVE_MKOSTEMP
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
#else
fd = mkstemp(tmpname);
if (fd >= 0) {
fd = set_cloexec_or_close(fd);
unlink(tmpname);
}
#endif
return fd;
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
* CLOEXEC. The file is immediately suitable for mmap()'ing
* the given size at offset zero.
*
* The file should not have a permanent backing store like a disk,
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
*
* The file name is deleted from the file system.
*
* The file is suitable for buffer sharing between processes by
* transmitting the file descriptor over Unix sockets using the
* SCM_RIGHTS methods.
*
* If the C library implements posix_fallocate(), it is used to
* guarantee that disk space is available for the file at the
* given size. If disk space is insufficient, errno is set to ENOSPC.
* If posix_fallocate() is not supported, program may receive
* SIGBUS on accessing mmap()'ed file contents instead.
*/
int
os_create_anonymous_file(off_t size)
{
static const char template[] = "/weston-shared-XXXXXX";
const char *path;
char *name;
int fd;
int ret;
path = getenv("XDG_RUNTIME_DIR");
if (!path) {
errno = ENOENT;
return -1;
}
name = malloc(strlen(path) + sizeof(template));
if (!name)
return -1;
strcpy(name, path);
strcat(name, template);
fd = create_tmpfile_cloexec(name);
free(name);
if (fd < 0)
return -1;
#ifdef HAVE_POSIX_FALLOCATE
do {
ret = posix_fallocate(fd, 0, size);
} while (ret == EINTR);
if (ret != 0) {
close(fd);
errno = ret;
return -1;
}
#else
do {
ret = ftruncate(fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
close(fd);
return -1;
}
#endif
return fd;
}
#ifndef MISSING_STRCHRNUL
char *
strchrnul(const char *s, int c)
{
while (*s && *s != c)
s++;
return (char *)s;
}
#endif

View file

@ -0,0 +1,52 @@
/*
* Copyright © 2012 Collabora, Ltd.
*
* 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 (including the
* next paragraph) 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.
*/
#ifndef OS_COMPATIBILITY_H
#define OS_COMPATIBILITY_H
#include <sys/types.h>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#else
static inline int
backtrace(void **buffer, int size) {
return 0;
}
#endif
int os_fd_set_cloexec(int fd);
int os_socketpair_cloexec(int domain, int type, int protocol, int *sv);
int os_epoll_create_cloexec(void);
int os_create_anonymous_file(off_t size);
#ifdef MISSING_STRCHRNUL
char *strchrnul(const char *s, int c);
#endif
#endif /* OS_COMPATIBILITY_H */

View file

@ -0,0 +1,52 @@
{
clangStdenv,
lib,
wayland-scanner,
wayland,
pango,
glib,
harfbuzz,
cairo,
pkg-config,
libxkbcommon,
scdoc,
layout ? "vistath",
}:
clangStdenv.mkDerivation {
pname = "wvkbd";
version = "0.16";
src = ./.;
postPatch = ''
substituteInPlace Makefile \
--replace-fail "pkg-config" "$PKG_CONFIG"
'';
nativeBuildInputs = [
pkg-config
scdoc
wayland-scanner
];
buildInputs = [
cairo
glib
harfbuzz
libxkbcommon
pango
wayland
];
installFlags = [ "PREFIX=$(out)" ];
makeFlags = [ "LAYOUT=${layout}" ];
strictDeps = true;
meta = with lib; {
homepage = "https://github.com/ckgxrg-salt/wvkbd";
description = "On-screen keyboard for wlroots";
platforms = platforms.linux;
license = licenses.gpl3Plus;
mainProgram = "wvkbd-vistath";
};
}

View file

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fractional_scale_v1">
<copyright>
Copyright © 2022 Kenny Levinsen
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 (including the next
paragraph) 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.
</copyright>
<description summary="Protocol for requesting fractional surface scales">
This protocol allows a compositor to suggest for surfaces to render at
fractional scales.
A client can submit scaled content by utilizing wp_viewport. This is done by
creating a wp_viewport object for the surface and setting the destination
rectangle to the surface size before the scale factor is applied.
The buffer size is calculated by multiplying the surface size by the
intended scale.
The wl_surface buffer scale should remain set to 1.
If a surface has a surface-local size of 100 px by 50 px and wishes to
submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
be used and the wp_viewport destination rectangle should be 100 px by 50 px.
For toplevel surfaces, the size is rounded halfway away from zero. The
rounding algorithm for subsurface position and size is not defined.
</description>
<interface name="wp_fractional_scale_manager_v1" version="1">
<description summary="fractional surface scale information">
A global interface for requesting surfaces to use fractional scales.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the fractional surface scale interface">
Informs the server that the client will not be using this protocol
object anymore. This does not affect any other objects,
wp_fractional_scale_v1 objects included.
</description>
</request>
<enum name="error">
<entry name="fractional_scale_exists" value="0"
summary="the surface already has a fractional_scale object associated"/>
</enum>
<request name="get_fractional_scale">
<description summary="extend surface interface for scale information">
Create an add-on object for the the wl_surface to let the compositor
request fractional scales. If the given wl_surface already has a
wp_fractional_scale_v1 object associated, the fractional_scale_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_fractional_scale_v1"
summary="the new surface scale info interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_fractional_scale_v1" version="1">
<description summary="fractional scale interface to a wl_surface">
An additional interface to a wl_surface object which allows the compositor
to inform the client of the preferred scale.
</description>
<request name="destroy" type="destructor">
<description summary="remove surface scale information for surface">
Destroy the fractional scale object. When this object is destroyed,
preferred_scale events will no longer be sent.
</description>
</request>
<event name="preferred_scale">
<description summary="notify of new preferred scale">
Notification of a new preferred scale for this surface that the
compositor suggests that the client should use.
The sent scale is the numerator of a fraction with a denominator of 120.
</description>
<arg name="scale" type="uint" summary="the new preferred scale"/>
</event>
</interface>
</protocol>

View file

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="viewporter">
<copyright>
Copyright © 2013-2016 Collabora, Ltd.
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 (including the next
paragraph) 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.
</copyright>
<interface name="wp_viewporter" version="1">
<description summary="surface cropping and scaling">
The global interface exposing surface cropping and scaling
capabilities is used to instantiate an interface extension for a
wl_surface object. This extended interface will then allow
cropping and scaling the surface contents, effectively
disconnecting the direct relationship between the buffer and the
surface size.
</description>
<request name="destroy" type="destructor">
<description summary="unbind from the cropping and scaling interface">
Informs the server that the client will not be using this
protocol object anymore. This does not affect any other objects,
wp_viewport objects included.
</description>
</request>
<enum name="error">
<entry name="viewport_exists" value="0"
summary="the surface already has a viewport object associated"/>
</enum>
<request name="get_viewport">
<description summary="extend surface interface for crop and scale">
Instantiate an interface extension for the given wl_surface to
crop and scale its content. If the given wl_surface already has
a wp_viewport object associated, the viewport_exists
protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_viewport"
summary="the new viewport interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="wp_viewport" version="1">
<description summary="crop and scale interface to a wl_surface">
An additional interface to a wl_surface object, which allows the
client to specify the cropping and scaling of the surface
contents.
This interface works with two concepts: the source rectangle (src_x,
src_y, src_width, src_height), and the destination size (dst_width,
dst_height). The contents of the source rectangle are scaled to the
destination size, and content outside the source rectangle is ignored.
This state is double-buffered, and is applied on the next
wl_surface.commit.
The two parts of crop and scale state are independent: the source
rectangle, and the destination size. Initially both are unset, that
is, no scaling is applied. The whole of the current wl_buffer is
used as the source, and the surface size is as defined in
wl_surface.attach.
If the destination size is set, it causes the surface size to become
dst_width, dst_height. The source (rectangle) is scaled to exactly
this size. This overrides whatever the attached wl_buffer size is,
unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
has no content and therefore no size. Otherwise, the size is always
at least 1x1 in surface local coordinates.
If the source rectangle is set, it defines what area of the wl_buffer is
taken as the source. If the source rectangle is set and the destination
size is not set, then src_width and src_height must be integers, and the
surface size becomes the source rectangle size. This results in cropping
without scaling. If src_width or src_height are not integers and
destination size is not set, the bad_size protocol error is raised when
the surface state is applied.
The coordinate transformations from buffer pixel coordinates up to
the surface-local coordinates happen in the following order:
1. buffer_transform (wl_surface.set_buffer_transform)
2. buffer_scale (wl_surface.set_buffer_scale)
3. crop and scale (wp_viewport.set*)
This means, that the source rectangle coordinates of crop and scale
are given in the coordinates after the buffer transform and scale,
i.e. in the coordinates that would be the surface-local coordinates
if the crop and scale was not applied.
If src_x or src_y are negative, the bad_value protocol error is raised.
Otherwise, if the source rectangle is partially or completely outside of
the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
when the surface state is applied. A NULL wl_buffer does not raise the
out_of_buffer error.
If the wl_surface associated with the wp_viewport is destroyed,
all wp_viewport requests except 'destroy' raise the protocol error
no_surface.
If the wp_viewport object is destroyed, the crop and scale
state is removed from the wl_surface. The change will be applied
on the next wl_surface.commit.
</description>
<request name="destroy" type="destructor">
<description summary="remove scaling and cropping from the surface">
The associated wl_surface's crop and scale state is removed.
The change is applied on the next wl_surface.commit.
</description>
</request>
<enum name="error">
<entry name="bad_value" value="0"
summary="negative or zero values in width or height"/>
<entry name="bad_size" value="1"
summary="destination size is not integer"/>
<entry name="out_of_buffer" value="2"
summary="source rectangle extends outside of the content area"/>
<entry name="no_surface" value="3"
summary="the wl_surface was destroyed"/>
</enum>
<request name="set_source">
<description summary="set the source rectangle for cropping">
Set the source rectangle of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If all of x, y, width and height are -1.0, the source rectangle is
unset instead. Any other set of values where width or height are zero
or negative, or x or y are negative, raise the bad_value protocol
error.
The crop and scale state is double-buffered state, and will be
applied on the next wl_surface.commit.
</description>
<arg name="x" type="fixed" summary="source rectangle x"/>
<arg name="y" type="fixed" summary="source rectangle y"/>
<arg name="width" type="fixed" summary="source rectangle width"/>
<arg name="height" type="fixed" summary="source rectangle height"/>
</request>
<request name="set_destination">
<description summary="set the surface size for scaling">
Set the destination size of the associated wl_surface. See
wp_viewport for the description, and relation to the wl_buffer
size.
If width is -1 and height is -1, the destination size is unset
instead. Any other pair of values for width and height that
contains zero or negative values raises the bad_value protocol
error.
The crop and scale state is double-buffered state, and will be
applied on the next wl_surface.commit.
</description>
<arg name="width" type="int" summary="surface width"/>
<arg name="height" type="int" summary="surface height"/>
</request>
</interface>
</protocol>

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="virtual_keyboard_unstable_v1">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation
Copyright © 2012-2013 Collabora, Ltd.
Copyright © 2018 Purism SPC
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 (including the next
paragraph) 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.
</copyright>
<interface name="zwp_virtual_keyboard_v1" version="1">
<description summary="virtual keyboard">
The virtual keyboard provides an application with requests which emulate
the behaviour of a physical keyboard.
This interface can be used by clients on its own to provide raw input
events, or it can accompany the input method protocol.
</description>
<request name="keymap">
<description summary="keyboard mapping">
Provide a file descriptor to the compositor which can be
memory-mapped to provide a keyboard mapping description.
Format carries a value from the keymap_format enumeration.
</description>
<arg name="format" type="uint" summary="keymap format"/>
<arg name="fd" type="fd" summary="keymap file descriptor"/>
<arg name="size" type="uint" summary="keymap size, in bytes"/>
</request>
<enum name="error">
<entry name="no_keymap" value="0" summary="No keymap was set"/>
</enum>
<request name="key">
<description summary="key event">
A key was pressed or released.
The time argument is a timestamp with millisecond granularity, with an
undefined base. All requests regarding a single object must share the
same clock.
Keymap must be set before issuing this request.
State carries a value from the key_state enumeration.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="key" type="uint" summary="key that produced the event"/>
<arg name="state" type="uint" summary="physical state of the key"/>
</request>
<request name="modifiers">
<description summary="modifier and group state">
Notifies the compositor that the modifier and/or group state has
changed, and it should update state.
The client should use wl_keyboard.modifiers event to synchronize its
internal state with seat state.
Keymap must be set before issuing this request.
</description>
<arg name="mods_depressed" type="uint" summary="depressed modifiers"/>
<arg name="mods_latched" type="uint" summary="latched modifiers"/>
<arg name="mods_locked" type="uint" summary="locked modifiers"/>
<arg name="group" type="uint" summary="keyboard layout"/>
</request>
<request name="destroy" type="destructor" since="1">
<description summary="destroy the virtual keyboard keyboard object"/>
</request>
</interface>
<interface name="zwp_virtual_keyboard_manager_v1" version="1">
<description summary="virtual keyboard manager">
A virtual keyboard manager allows an application to provide keyboard
input events as if they came from a physical keyboard.
</description>
<enum name="error">
<entry name="unauthorized" value="0" summary="client not authorized to use the interface"/>
</enum>
<request name="create_virtual_keyboard">
<description summary="Create a new virtual keyboard">
Creates a new virtual keyboard associated to a seat.
If the compositor enables a keyboard to perform arbitrary actions, it
should present an error when an untrusted client requests a new
keyboard.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="id" type="new_id" interface="zwp_virtual_keyboard_v1"/>
</request>
</interface>
</protocol>

View file

@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_unstable_v1">
<copyright>
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="4">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
</description>
<request name="get_layer_surface">
<description summary="create a layer_surface from a surface">
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer surface from a wl_surface which has a buffer attached
or committed is a client error, and any attempts by a client to attach
or manipulate a buffer prior to the first layer_surface.configure call
must also be treated as errors.
After creating a layer_surface object and setting it up, the client
must perform an initial commit without any buffer attached.
The compositor will reply with a layer_surface.configure event.
The client must acknowledge it and is then allowed to attach a buffer
to map the surface.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
</description>
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
</request>
<enum name="error">
<entry name="role" value="0" summary="wl_surface has another role"/>
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
</enum>
<enum name="layer">
<description summary="available layers for surfaces">
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
</description>
<entry name="background" value="0"/>
<entry name="bottom" value="1"/>
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="4">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
Attaching a null buffer to a layer surface unmaps it.
Unmapping a layer_surface means that the surface cannot be shown by the
compositor until it is explicitly mapped again. The layer_surface
returns to the state it had right after layer_shell.get_layer_surface.
The client can re-map the surface by performing a commit without any
buffer attached, waiting for a configure event and handling it as usual.
</description>
<request name="set_size">
<description summary="sets the size of the surface">
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</request>
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
</description>
<arg name="anchor" type="uint" enum="anchor"/>
</request>
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
</description>
<arg name="zone" type="int"/>
</request>
<request name="set_margin">
<description summary="sets a margin from the anchor point">
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
</description>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
</request>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
Types of keyboard interaction possible for layer shell surfaces. The
rationale for this is twofold: (1) some applications are not interested
in keyboard events and not allowing them to be focused can improve the
desktop experience; (2) some applications will want to take exclusive
keyboard focus.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
This value indicates that this surface is not interested in keyboard
events and the compositor should never assign it the keyboard focus.
This is the default value, set for newly created layer shell surfaces.
This is useful for e.g. desktop widgets that display information or
only have interaction with non-keyboard input devices.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
Request exclusive keyboard focus if this surface is above the shell surface layer.
For the top and overlay layers, the seat will always give
exclusive keyboard focus to the top-most layer which has keyboard
interactivity set to exclusive. If this layer contains multiple
surfaces with keyboard interactivity set to exclusive, the compositor
determines the one receiving keyboard events in an implementation-
defined manner. In this case, no guarantee is made when this surface
will receive keyboard focus (if ever).
For the bottom and background layers, the compositor is allowed to use
normal focus semantics.
This setting is mainly intended for applications that need to ensure
they receive all keyboard events, such as a lock screen or a password
prompt.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
This requests the compositor to allow this surface to be focused and
unfocused by the user in an implementation-defined manner. The user
should be able to unfocus this surface even regardless of the layer
it is on.
Typically, the compositor will want to use its normal mechanism to
manage keyboard focus between layer shell surfaces with this setting
and regular toplevels on the desktop layer (e.g. click to focus).
Nevertheless, it is possible for a compositor to require a special
interaction to focus or unfocus layer shell surfaces (e.g. requiring
a click even if focus follows the mouse normally, or providing a
keybinding to switch focus between layers).
This setting is mainly intended for desktop shell components (e.g.
panels) that allow keyboard interaction. Using this option can allow
implementing a desktop shell that can be fully usable without the
mouse.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set how keyboard events are delivered to this surface. By default,
layer shell surfaces do not receive keyboard events; this request can
be used to change this.
This setting is inherited by child surfaces set by the get_popup
request.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Keyboard interactivity is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</request>
<request name="get_popup">
<description summary="assign this layer_surface as an xdg_popup parent">
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="ack_configure">
<description summary="ack a configure event">
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
</description>
<arg name="serial" type="uint" summary="the serial from the configure event"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the layer_surface">
This request destroys the layer surface.
</description>
</request>
<event name="configure">
<description summary="suggest a surface change">
The configure event asks the client to resize its surface.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
</description>
<arg name="serial" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="closed">
<description summary="surface should be closed">
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
</description>
</event>
<enum name="error">
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
</enum>
<enum name="anchor" bitfield="true">
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load diff

54
packages/wvkbd/shm_open.c Normal file
View file

@ -0,0 +1,54 @@
#define _POSIX_C_SOURCE 200112L
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
static void
randname(char *buf)
{
struct timespec ts;
long r;
clock_gettime(CLOCK_REALTIME, &ts);
r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
buf[i] = 'A' + (r & 15) + (r & 16) * 2;
r >>= 5;
}
}
static int
create_shm_file(void)
{
int retries = 100;
int fd;
do {
char name[] = "/wl_shm-XXXXXX";
randname(name + sizeof(name) - 7);
--retries;
fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
int
allocate_shm_file(size_t size)
{
int fd = create_shm_file();
int ret;
if (fd < 0)
return -1;
do {
ret = ftruncate(fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
close(fd);
return -1;
}
return fd;
}

View file

@ -0,0 +1,8 @@
#ifndef shm_open_h_INCLUDED
#define shm_open_h_INCLUDED
void randname(char *buf);
int create_shm_file(void);
int allocate_shm_file(size_t size);
#endif // shm_open_h_INCLUDED

129
packages/wvkbd/wvkbd.1.scd Normal file
View file

@ -0,0 +1,129 @@
wvkbd(1)
# NAME
wvkbd - on-screen virtual keyboard for wayland compositors using wlroots
# SYNOPSIS
wvkbd-mobintl [OPTIONS]...
*NOTE*: Your binary may have a different suffix depending on which layout you compiled.
# DESCRIPTION
This project aims to deliver a minimal but practically usable implementation of
a wlroots on-screen keyboard in legible C. This will _only_ be a keyboard, not
a feedback buzzer, led blinker, or anything that requires more than what's
needed to input text quickly. The end product should be a static codebase that
can be patched to add new features.
## OPTIONS
*-D*
enable debug mode.
*-o*
print pressed keys to standard output.
*-O*
print intersected keys to standard output.
*-l* _layers_
comma separated list of layers in vertical/portrait mode.
*--landscape-layers* _layers_
comma separated list of layers used in horizontal/landscape mode.
*--list-layers*
prints a list of all available layers.
*-H* _pixels_
Height of the keyboard in pixels, for vertical/portrait mode.
*-L* _pixels_
Height of the keyboard in pixels, for horizontal/landscape mode
*--fn* _font_
set font and size (e.g. DejaVu Sans 20)
*--hidden*
Start hidden (send SIGUSR2 to show).
*--alpha* _int_
Set alpha value (i.e. transparency) for all colors [0-255]
*--bg* _rrggbb|aa_
Set color of background
*--fg* _rrggbb|aa_
Set color of keys
*--fg-sp* _rrggbb|aa_
Set color of special keys
*--press* _rrggbb|aa_
Set color of pressed keys
*--press-sp* _rrggbb|aa_
Set color of pressed special keys
*--swipe* _rrggbb|aa_
Set color of swiped keys
*--swipe-sp* _rrggbb|aa_
Set color of swiped special keys
*--text* _rrggbb|aa_
Set color text on keys
*--text-sp* _rrggbb|aa_
Set color text on special keys
*--version*
Print version information
*-h*, *--help*
Print usage help
# SIGNALS
You can send signals to wvkbd to hide/show it (e.g. using _kill_(1) with _-s_):
*SIGUSR1*
Hide the keyboard.
*SIGUSR2*
Show the keyboard
*SIGRTMIN*
Toggle visibility
# COMPOSE BUTTON
The default mobile international layout features a Compose button (*Cmp*)
which, when combined with another key, opens up a layout that offers variants
for that key. This is similar to functionality that other keyboards implemented
using a *long press* (wvkbd has no such notion, holding a key will repeat
it like on a physical keyboard).
For example, press Cmp + a to access variants with diacritics like á,à,â,ä, etc..
Most layouts also feature the following that are less obvious:
- Press Cmp and . to access more punctuation
- Press Cmp and - or , to access 'mathematical' symbols (+,-,=,etc)
- Press Cmp and ' or 0 or 9 to access more brackets and quotes
- Press Cmp and q to access emojis
Last, but not least, pressing Cmp + space or Cmp + ⌨ or Cmp + Abc opens up an
index that allows you to immediately jump to any layout by name, even layouts
not explicitly added to your layers on startup.
# AUTHORS
Created by John Sullivan <jsullivan@csumb.edu>, maintained by the Sxmo project
<https://sxmo.org> in collaboration with other open source contributors. For
more information about wvkbd development, see <https://git.sr.ht/~proycon/wvkbd>
or <https://github.com/jjsullivan5196/wvkbd>.