uploading ...

This commit is contained in:
zcake 2020-12-22 13:51:39 +08:00
commit f3f4efd63f
18 changed files with 1979 additions and 0 deletions

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
MIT/X Consortium License
© 2011 Christoph Lohmann <20h@r-36.net>
© 2008-2011 Enno Boland <g # s01 ' de>
© 2020 Maarten van Gompel <proycon@anaproy.nl>
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.

63
Makefile Normal file
View File

@ -0,0 +1,63 @@
# svkbd - simple virtual keyboard
# See LICENSE file for copyright and license details.
.POSIX:
NAME = svkbd
VERSION = 0.2.2
include config.mk
BIN = ${NAME}
SRC = drw.c ${NAME}.c util.c
OBJ = ${SRC:.c=.o}
MAN1 = ${NAME}.1
all: ${BIN}
options:
@echo svkbd build options:
@echo "CFLAGS = ${SVKBD_CFLAGS}"
@echo "CPPLAGS = ${SVKBD_CPPFLAGS}"
@echo "LDFLAGS = ${SVKBD_LDFLAGS}"
@echo "CC = ${CC}"
config.h:
cp config.def.h $@
.c.o:
${CC} ${SVKBD_CFLAGS} ${SVKBD_CPPFLAGS} -c $<
${OBJ}: config.h config.mk
${BIN}: ${OBJ}
${CC} -o ${BIN} ${OBJ} ${SVKBD_LDFLAGS}
clean:
rm -f ${NAME}-?? ${NAME}-??.o ${OBJ} ${BIN}
dist:
rm -rf "${NAME}-${VERSION}"
mkdir -p "${NAME}-${VERSION}"
cp LICENSE Makefile README.md config.def.h config.mk ${MAN1} \
drw.h util.h ${SRC} ${NAME}-${VERSION}
for i in layout.*.h; \
do \
cp $$i ${NAME}-${VERSION}; \
done
tar -cf - "${NAME}-${VERSION}" | \
gzip -c > "${NAME}-${VERSION}.tar.gz"
rm -rf "${NAME}-${VERSION}"
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
cp ${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}
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/${NAME}
rm -f ${DESTDIR}${MANPREFIX}/man1/${MAN1}
.PHONY: all clean dist options install uninstall

87
README.md Normal file
View File

@ -0,0 +1,87 @@
SVKBD: Simple Virtual Keyboard
=================================
This is a simple virtual keyboard, intended to be used in environments,
where no keyboard is available.
Installation
------------
$ make
$ make install
This will create by default `svkbd-intl`, which is svkbd using an international
layout with multiple layers and overlays, and optimised for mobile devices.
You can create svkbd for additional layouts by doing:
$ make LAYOUT=$layout
This will take the file `layout.$layout.h` and create `svkbd-$layout`.
`make install` will then pick up the new file and install it accordingly.
Layouts
---------
The following layouts are available:
* **Mobile Layouts:**
* ``mobile-intl`` - A small international layout optimised for mobile devices. This layout consists of multiple layers which
can be switched on the fly, and overlays that appear on long-press of certain keys, adding input ability for
diacritics and other variants, as well as some emoji. The layers are:
* a basic qwerty layer
* a layer for numeric input, arrows, and punctuation
* a layer for function keys, media keys, and arrows
* a cyrillic layer (ЙЦУКЕН based); the э key is moved to an overlay on е
* a dialer/numeric layer
* ``mobile-plain`` - This is a plain layout with only a qwerty layer and numeric/punctuation layer. It was
originally made for [sxmo](https://sr.ht/~mil/Sxmo/).
* **Traditional layouts**:
* ``en`` - An english layout without layers (QWERTY)
* ``de`` - A german layout (QWERTZ)
* ``ru`` - A russian layout (ЙЦУКЕН)
* ``sh`` - A serbo-croatian layout using latin script (QWERTZ)
Usage
-----
$ svkbd-mobile-intl
This will open svkbd at the bottom of the screen, showing the default
international layout.
$ svkbd-mobile-intl -d
This tells svkbd to announce itself being a dock window, which then
is managed differently between different window managers. If using dwm
and the dock patch, then this will make svkbd being managed by dwm and
some space of the screen being reserved for it.
$ svkbd-mobile-intl -g 400x200+1+1
This will start svkbd-intl with a size of 400x200 and at the upper left
window corner.
For layouts that consist of multiple layers, you can enable layers on program start through either the ``-l`` flag or
through the ``SVKBD_LAYERS`` environment variable. They both take a comma separated list of layer names (as defined in
your ``layout.*.h``). Use the ``↺`` button in the bottom-left to cycle through all the layers.
Some layouts come with overlays that will show when certain keys are hold pressed for a longer time. For
example, a long press on the ``a`` key will enable an overview showing all kinds of diacritic combinations for ``a``.
Overlay functionality interferes with the ability to hold a key and have it outputted repeatedly. You can disable
overlay functionality with the ``-O`` flag or by setting the environment variable ``SVKBD_ENABLEOVERLAYS=0``. There is
also a key on the function layer of the keyboard itself to enable/disable this behaviour on the fly. Its label shows
``≅`` when the overlay functionality is enabled and ``≇`` when not.
Notes
---------
This virtual keyboard does not actually modify the X keyboard layout, the ``mobile-intl``, ``mobile-plain`` and ``en`` layouts simply rely on a standard US QWERTY layout (setxkbmap us) being activated, the other layouts (``de``, ``ru``, ``sh``) require their respective XKB keymaps to be active.
If you use another XKB layout you will get unpredictable output that does not match the labels on the virtual keycaps!
Repository
----------
git clone https://git.suckless.org/svkbd

13
config.def.h Normal file
View File

@ -0,0 +1,13 @@
static const Bool wmborder = True;
static int fontsize = 20;
static double overlay_delay = 1.0;
static int heightfactor = 16; //one row of keys takes up 1/x of the screen height
static const char *fonts[] = {
"DejaVu Sans:bold:size=20"
};
static const char *colors[SchemeLast][2] = {
/* fg bg */
[SchemeNorm] = { "#ffffff", "#14313d" },
[SchemePress] = { "#ffffff", "#000000" },
[SchemeHighlight] = { "#58a7c6", "#005577" },
};

17
config.h Normal file
View File

@ -0,0 +1,17 @@
static const Bool wmborder = True;
static const char *fonts[] = {
"FiraCode Nerd Font:style=Light:size=10:antialias=true:autohint=true",
"Chiron Sans HK Light:style=Light:size=10",
"OpenMoji:style=Color",
};
static int fontsize = 10;
static double overlay_delay = 1.0;
static int heightfactor = 30; //one row of keys takes up 1/x of the screen height
static const char *colors[SchemeLast][2] = {
/* fg bg */
[SchemeNorm] = { "#ff5555", "#282a36" },
[SchemePress] = { "#50fa7b","#282a36" },
[SchemeHighlight] = { "#ff79c6", "#282a36" },
};

25
config.mk Normal file
View File

@ -0,0 +1,25 @@
# paths
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man
X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib
PKG_CONFIG = pkg-config
# Xinerama, comment if you don't want it
XINERAMALIBS = -L${X11LIB} -lXinerama
XINERAMAFLAGS = -DXINERAMA
# includes and libs
INCS = -I. -I./layouts -I${X11INC} \
`$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2`
LIBS = -L${X11LIB} -lX11 -lXtst -lXft ${XINERAMALIBS} \
`$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2`
# use system flags
SVKBD_CFLAGS = ${CFLAGS} ${INCS}
SVKBD_LDFLAGS = ${LDFLAGS} ${LIBS}
SVKBD_CPPFLAGS = ${CPPFLAGS} -D_DEFAULT_SOURCE -DVERSION=\"VERSION\" ${XINERAMAFLAGS} -DLAYOUT=\"layout.h\"

418
drw.c Normal file
View File

@ -0,0 +1,418 @@
/* See LICENSE file for copyright and license details. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include "drw.h"
#include "util.h"
#define UTF_INVALID 0xFFFD
#define UTF_SIZ 4
static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
static long
utf8decodebyte(const char c, size_t *i)
{
for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
return (unsigned char)c & ~utfmask[*i];
return 0;
}
static size_t
utf8validate(long *u, size_t i)
{
if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i)
;
return i;
}
static size_t
utf8decode(const char *c, long *u, size_t clen)
{
size_t i, j, len, type;
long udecoded;
*u = UTF_INVALID;
if (!clen)
return 0;
udecoded = utf8decodebyte(c[0], &len);
if (!BETWEEN(len, 1, UTF_SIZ))
return 1;
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
if (type)
return j;
}
if (j < len)
return 0;
*u = udecoded;
utf8validate(u, len);
return len;
}
Drw *
drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
{
Drw *drw = ecalloc(1, sizeof(Drw));
drw->dpy = dpy;
drw->screen = screen;
drw->root = root;
drw->w = w;
drw->h = h;
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
drw->gc = XCreateGC(dpy, root, 0, NULL);
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
return drw;
}
void
drw_resize(Drw *drw, unsigned int w, unsigned int h)
{
if (!drw)
return;
drw->w = w;
drw->h = h;
if (drw->drawable)
XFreePixmap(drw->dpy, drw->drawable);
drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
}
void
drw_free(Drw *drw)
{
XFreePixmap(drw->dpy, drw->drawable);
XFreeGC(drw->dpy, drw->gc);
drw_fontset_free(drw->fonts);
free(drw);
}
/* This function is an implementation detail. Library users should use
* drw_fontset_create instead.
*/
static Fnt *
xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
{
Fnt *font;
XftFont *xfont = NULL;
FcPattern *pattern = NULL;
if (fontname) {
/* Using the pattern found at font->xfont->pattern does not yield the
* same substitution results as using the pattern returned by
* FcNameParse; using the latter results in the desired fallback
* behaviour whereas the former just results in missing-character
* rectangles being drawn, at least with some fonts. */
if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
return NULL;
}
if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
XftFontClose(drw->dpy, xfont);
return NULL;
}
} else if (fontpattern) {
if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
fprintf(stderr, "error, cannot load font from pattern.\n");
return NULL;
}
} else {
die("no font specified.");
}
/* Do not allow using color fonts. This is a workaround for a BadLength
* error from Xft with color glyphs. Modelled on the Xterm workaround. See
* https://bugzilla.redhat.com/show_bug.cgi?id=1498269
* https://lists.suckless.org/dev/1701/30932.html
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
* and lots more all over the internet.
*/
FcBool iscol;
if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
XftFontClose(drw->dpy, xfont);
return NULL;
}
font = ecalloc(1, sizeof(Fnt));
font->xfont = xfont;
font->pattern = pattern;
font->h = xfont->ascent + xfont->descent;
font->dpy = drw->dpy;
return font;
}
static void
xfont_free(Fnt *font)
{
if (!font)
return;
if (font->pattern)
FcPatternDestroy(font->pattern);
XftFontClose(font->dpy, font->xfont);
free(font);
}
Fnt*
drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
{
Fnt *cur, *ret = NULL;
size_t i;
if (!drw || !fonts)
return NULL;
for (i = 1; i <= fontcount; i++) {
if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
cur->next = ret;
ret = cur;
}
}
return (drw->fonts = ret);
}
void
drw_fontset_free(Fnt *font)
{
if (font) {
drw_fontset_free(font->next);
xfont_free(font);
}
}
void
drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
{
if (!drw || !dest || !clrname)
return;
if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen),
clrname, dest))
die("error, cannot allocate color '%s'", clrname);
}
/* Wrapper to create color schemes. The caller has to call free(3) on the
* returned color scheme when done using it. */
Clr *
drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
{
size_t i;
Clr *ret;
/* need at least two colors for a scheme */
if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
die("error, cannot create color scheme (drw=%d) (clrcount=%d)", drw, clrcount);
for (i = 0; i < clrcount; i++)
drw_clr_create(drw, &ret[i], clrnames[i]);
return ret;
}
void
drw_setfontset(Drw *drw, Fnt *set)
{
if (drw)
drw->fonts = set;
}
void
drw_setscheme(Drw *drw, Clr *scm)
{
if (drw)
drw->scheme = scm;
}
void
drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert)
{
if (!drw || !drw->scheme)
return;
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
if (filled)
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
else
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
}
int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
{
char buf[1024];
int ty;
unsigned int ew;
XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont;
size_t i, len;
int utf8strlen, utf8charlen, render = x || y || w || h;
long utf8codepoint = 0;
const char *utf8str;
FcCharSet *fccharset;
FcPattern *fcpattern;
FcPattern *match;
XftResult result;
int charexists = 0;
if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
return 0;
if (!render) {
w = ~w;
} else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
d = XftDrawCreate(drw->dpy, drw->drawable,
DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen));
x += lpad;
w -= lpad;
}
usedfont = drw->fonts;
while (1) {
utf8strlen = 0;
utf8str = text;
nextfont = NULL;
while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) {
if (curfont == usedfont) {
utf8strlen += utf8charlen;
text += utf8charlen;
} else {
nextfont = curfont;
}
break;
}
}
if (!charexists || nextfont)
break;
else
charexists = 0;
}
if (utf8strlen) {
drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
/* shorten text if necessary */
for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
if (len) {
memcpy(buf, utf8str, len);
buf[len] = '\0';
if (len < utf8strlen)
for (i = len; i && i > len - 3; buf[--i] = '.')
; /* NOP */
if (render) {
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
usedfont->xfont, x, ty, (XftChar8 *)buf, len);
}
x += ew;
w -= ew;
}
}
if (!*text) {
break;
} else if (nextfont) {
charexists = 0;
usedfont = nextfont;
} else {
/* Regardless of whether or not a fallback font is found, the
* character must be drawn. */
charexists = 1;
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint);
if (!drw->fonts->pattern) {
/* Refer to the comment in xfont_create for more information. */
die("the first font in the cache must be loaded from a font string.");
}
fcpattern = FcPatternDuplicate(drw->fonts->pattern);
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
FcPatternAddBool(fcpattern, FC_COLOR, FcFalse);
FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
FcDefaultSubstitute(fcpattern);
match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
FcCharSetDestroy(fccharset);
FcPatternDestroy(fcpattern);
if (match) {
usedfont = xfont_create(drw, NULL, match);
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
; /* NOP */
curfont->next = usedfont;
} else {
xfont_free(usedfont);
usedfont = drw->fonts;
}
}
}
}
if (d)
XftDrawDestroy(d);
return x + (render ? w : 0);
}
void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{
if (!drw)
return;
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
}
void
drw_sync(Drw *drw)
{
XSync(drw->dpy, False);
}
unsigned int
drw_fontset_getwidth(Drw *drw, const char *text)
{
if (!drw || !drw->fonts || !text)
return 0;
return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
}
void
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
{
XGlyphInfo ext;
if (!font || !text)
return;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
if (w)
*w = ext.xOff;
if (h)
*h = font->h;
}

50
drw.h Normal file
View File

@ -0,0 +1,50 @@
/* See LICENSE file for copyright and license details. */
typedef struct Fnt {
Display *dpy;
unsigned int h;
XftFont *xfont;
FcPattern *pattern;
struct Fnt *next;
} Fnt;
enum { ColFg, ColBg }; /* Clr scheme index */
typedef XftColor Clr;
typedef struct {
unsigned int w, h;
Display *dpy;
int screen;
Window root;
Drawable drawable;
GC gc;
Clr *scheme;
Fnt *fonts;
} Drw;
/* Drawable abstraction */
Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h);
void drw_resize(Drw *drw, unsigned int w, unsigned int h);
void drw_free(Drw *drw);
/* Fnt abstraction */
Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
void drw_fontset_free(Fnt* set);
unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
/* Colorscheme abstraction */
void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
/* Drawing context manipulation */
void drw_setfontset(Drw *drw, Fnt *set);
void drw_setscheme(Drw *drw, Clr *scm);
/* Drawing functions */
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
/* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
void drw_sync(Drw *drw);

BIN
drw.o Normal file

Binary file not shown.

168
layout.h Normal file
View File

@ -0,0 +1,168 @@
//   
#define KEYS 71
static Key keys_en[KEYS] = {
{ "`~", XK_grave, 1 },
{ "1!", XK_1, 1 },
{ "2@", XK_2, 1 },
{ "3#", XK_3, 1 },
{ "4$", XK_4, 1 },
{ "5%", XK_5, 1 },
{ "6^", XK_6, 1 },
{ "7&", XK_7, 1 },
{ "8*", XK_8, 1 },
{ "9(", XK_9, 1 },
{ "0)", XK_0, 1 },
{ "-_", XK_minus, 1 },
{ "=+", XK_plus, 1 },
{ "", XK_BackSpace, 2 },
{ 0 }, /* New row */
{ "->|", XK_Tab, 1 },
{ 0, XK_q, 1 },
{ 0, XK_w, 1 },
{ 0, XK_e, 1 },
{ 0, XK_r, 1 },
{ 0, XK_t, 1 },
{ 0, XK_y, 1 },
{ 0, XK_u, 1 },
{ 0, XK_i, 1 },
{ 0, XK_o, 1 },
{ 0, XK_p, 1 },
{ "[", XK_bracketleft, 1 },
{ "]", XK_bracketright, 1 },
{ "", XK_Return, 1 },
{ 0 }, /* New row */
{ "Esc", XK_Escape, 1 },
{ 0, XK_a, 1 },
{ 0, XK_s, 1 },
{ 0, XK_d, 1 },
{ 0, XK_f, 1 },
{ 0, XK_g, 1 },
{ 0, XK_h, 1 },
{ 0, XK_j, 1 },
{ 0, XK_k, 1 },
{ 0, XK_l, 1 },
{ ":;", XK_semicolon, 1 },
{ "'\"", XK_exclam, 1 },
{ "\\|", XK_backslash, 1 },
{ 0 }, /* New row */
{ "Shift", XK_Shift_L, 3 },
{ 0, XK_z, 1 },
{ 0, XK_x, 1 },
{ 0, XK_c, 1 },
{ 0, XK_v, 1 },
{ 0, XK_b, 1 },
{ 0, XK_n, 1 },
{ 0, XK_m, 1 },
{ "", XK_Up, 1 },
{ ",", XK_colon, 1 },
{ ".", XK_period, 1 },
{ "/?", XK_slash, 1 },
{ "Shift", XK_Shift_R, 2 },
{ 0 }, /* New row */
{ "", XK_Cancel, 1},
{ "Ctrl", XK_Control_L, 2 },
{ "Alt", XK_Alt_L, 2 },
{ "", XK_Super_L, 2 },
{ "Hyper", XK_Hyper_L, 2 },
{ "", XK_space, 5 },
{ "", XK_Left, 1 },
{ "", XK_Down, 1 },
{ "", XK_Right, 1},
{ "Alt", XK_Alt_R, 2 },
{ "", XK_Super_R, 2 },
{ "Hyper", XK_Hyper_R, 2 },
{ "Ctrl", XK_Control_R, 2 },
};
static Key keys_emoji [KEYS] = {
{ "🙂", 0x101f642 ,1 }, //1
{ "😀", 0x101f600 ,1 },//2
{ "😁", 0x101f601 ,1 },//3
{ "😂", 0x101f602 ,1 },//4
{ "😃", 0x101f603 ,1 },//5
{ "😄", 0x101f604 ,1 },//6
{ "😅", 0x101f605 ,1 },//7
{ "😆", 0x101f606 ,1 },//8
{ "😇", 0x101f607 ,1 },//9
{ "😈", 0x101f608 ,1 },//0
{ "😉", 0x101f609 ,1 },//11
{ "😊", 0x101f60a ,1 },//12
{ "😋", 0x101f60b ,1 },//13
{ "😌", 0x101f60c ,1 },//14
{ "😍", 0x101f60d ,1 },//15
{ "😎", 0x101f60e ,1 },//16
{ "😏", 0x101f60f ,1 },//17
{ "😐", 0x101f610 ,1 },//18
{ "😒", 0x101f612 ,1 },//19
{ "😓", 0x101f613 ,1 },//20
{ "😛", 0x101f61b ,1 },//21
{ "😮", 0x101f62e ,1 },//22
{ "😟", 0x101f61f ,1 },//23
{ "😟", 0x101f620 ,1 },//24
{ "😢", 0x101f622 ,1 },//25
{ "😭", 0x101f62d ,1 },//26
{ "😳", 0x101f633 ,1 },//27
{ "😴", 0x101f634 ,1 },//28
{ "Del", XK_Delete, 1 },//29
{0},
{ "", XF86XK_AudioPlay, 1 },//30
{ "", XF86XK_AudioRecord, 1 },//31
{ "", XF86XK_AudioStop, 1 },//32
{ "◂◂", XF86XK_AudioPrev, 1 },//33
{ "▸▸", XF86XK_AudioNext, 1 },//34
{ "♫M", XF86XK_AudioMute, 1 },//35
{ "♫-", XF86XK_AudioLowerVolume, 1 },//36
{ "♫+", XF86XK_AudioRaiseVolume, 1 },//37
{ "☀-", XF86XK_MonBrightnessDown, 1 },//38
{ "☀+", XF86XK_MonBrightnessUp, 1 },//39
{ "F1", XK_F1, 1 },//40
{ "F2", XK_F2, 1 },//41
{ "F3", XK_F3, 1 },//42
{ "F4", XK_F4, 1 },//43
{ "F5", XK_F5, 1 },//44
{ "F6", XK_F6, 1 },//45
{ "F7", XK_F7, 1 },//46
{ "F8", XK_F8, 1 },//47
{ "F9", XK_F9, 1 },//48
{ "F10", XK_F10, 1 },//49
{ "F11", XK_F11, 1 },//50
{ "F12", XK_F12, 1 },//51
{ "", XK_Cancel, 1},//52
{ "", XK_space, 5 },//53
{0},
{ "", XK_space, 10 },//54
{0},
{ "", XK_space, 10 },//55
{0},
{ "", XK_space, 10 },//56
{0},
{ "", XK_space, 10 },//57
{0},
{ "", XK_space, 10 },//58
{0},
{ "", XK_space, 10 },//59
{0},
{0},
};
Buttonmod buttonmods[] = {
{ XK_Shift_L, Button2 },
{ XK_Alt_L, Button3 },
};
#define OVERLAYS 1
static Key overlay[OVERLAYS] = {
{ 0, XK_Cancel },
};
#define LAYERS 2
static char* layer_names[LAYERS] = {
"en",
"emoji",
};
static Key* available_layers[LAYERS] = {
keys_en,
keys_emoji,
};

BIN
svkbd Executable file

Binary file not shown.

BIN
svkbd-arrows Executable file

Binary file not shown.

72
svkbd.1 Normal file
View File

@ -0,0 +1,72 @@
.Dd August 4, 2020
.Dt SVKBD 1
.Os
.Sh NAME
.Nm svkbd
.Nd simple virtual keyboard
.Sh SYNOPSIS
.Nm
.Op Fl D
.Op Fl d
.Op Fl g Ar geometry
.Op Fl fn Ar font
.Op Fl O
.Op Fl h
.Op Fl H Ar heightfactor
.Op Fl l Ar layers
.Op Fl s Ar layer
.Op Fl v
.Sh DESCRIPTION
.Nm
is a simple virtual keyboard for X.org, intended to be used in environments, where no
keyboard is available.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl D
Debug mode; output verbosely to stderr.
.It Fl d
Set the _NET_WM_WINDOW_TYPE_DOCK property to hint windowmanagers it is
dockable, by default off.
.It Fl fn Ar font
Defines the font or font set used. e.g. "Monospace:bold:size=20" (an Xft font).
.It Fl g Ar geometry
Adjust the initial window position or size as specified by the standard X11
geometry format.
.It Fl h
Show the usage information.
.It Fl H Ar heightfactor
Affects the vertical space taken by the keyboard.
One row of keys takes up 1/heighfactor of the screen's total height.
.It Fl l Ar layers
Comma separated list of layers to enable (by name). If not set, all layers
in the layout will be available.
The layer names are defined by the layout you compiled.
.It Fl s Ar layer
The layer to show on program start-up (by name). If not set, the first
layer of the layout will be shown.
.It Fl O
Disable overlay functionality.
.It Fl v
Show the version information.
.El
.Sh ENVIRONMENT
The following environment variables can be defined, providing
an alternative to some of the command line parameters.
The command line parameters, however, always take precedence:
.Bl -tag -width Ds
.It Ev SVKBD_LAYERS
Comma separated list of layers to enable (by name). The layer names are defined by the layout
you compiled.
.It Ev SVKBD_ENABLEOVERLAYS
Set this to 0 if you want to disable overlay functionality.
.It Ev SVKBD_HEIGHTFACTOR
Affects the vertical space taken by the keyboard.
One row of keys takes up 1/heighfactor of the screen's total height.
.El
.Sh SEE ALSO
.Xr XParseGeometry 3
.Sh AUTHORS
.An Christoph Lohmann Aq Mt 20h@r-36.net
.An Enno Boland Aq Mt gottox@s01.de
.An Miles Alan Aq Mt m@milesalan.com
.An Maarten van Gompel Aq Mt proycon@anaproy.nl

999
svkbd.c Normal file
View File

@ -0,0 +1,999 @@
/* See LICENSE file for copyright and license details. */
#include <sys/select.h>
#include <sys/time.h>
#include <locale.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/XF86keysym.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xproto.h>
#include <X11/extensions/XTest.h>
#include <X11/Xft/Xft.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include "drw.h"
#include "util.h"
/* macros */
#define LENGTH(x) (sizeof x / sizeof x[0])
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)))
#define STRINGTOKEYSYM(X) (XStringToKeySym(X))
/* enums */
enum { SchemeNorm, SchemePress, SchemeHighlight, SchemeLast };
enum { NetWMWindowType, NetLast };
/* typedefs */
typedef struct {
char *label;
KeySym keysym;
unsigned int width;
int x, y, w, h;
Bool pressed;
Bool highlighted;
} Key;
typedef struct {
KeySym mod;
unsigned int button;
} Buttonmod;
/* function declarations */
static void printdbg(const char *fmt, ...);
static void motionnotify(XEvent *e);
static void buttonpress(XEvent *e);
static void buttonrelease(XEvent *e);
static void cleanup(void);
static void configurenotify(XEvent *e);
static void countrows();
static int countkeys(Key *layer);
static void drawkeyboard(void);
static void drawkey(Key *k);
static void expose(XEvent *e);
static Key *findkey(int x, int y);
static void leavenotify(XEvent *e);
static void press(Key *k, KeySym mod);
static double get_press_duration();
static void run(void);
static void setup(void);
static void simulate_keypress(KeySym keysym);
static void simulate_keyrelease(KeySym keysym);
static void showoverlay(int idx);
static void hideoverlay();
static void cyclelayer();
static void setlayer();
static void togglelayer();
static void unpress(Key *k, KeySym mod);
static void updatekeys();
/* variables */
static int screen;
static void (*handler[LASTEvent]) (XEvent *) = {
[ButtonPress] = buttonpress,
[ButtonRelease] = buttonrelease,
[ConfigureNotify] = configurenotify,
[Expose] = expose,
[LeaveNotify] = leavenotify,
[MotionNotify] = motionnotify
};
static Atom netatom[NetLast];
static Display *dpy;
static Drw *drw;
static Window root, win;
static Clr* scheme[SchemeLast];
static Bool running = True, isdock = False;
static KeySym pressedmod = 0;
static struct timeval pressbegin;
static int currentlayer = 0;
static int enableoverlays = 1;
static int currentoverlay = -1; /* -1 = no overlay */
static KeySym overlaykeysym = 0; /* keysym for which the overlay is presented */
static int releaseprotect = 0; /* set to 1 after overlay is shown, protecting against immediate release */
static int tmp_keycode = 1;
static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0;
static char *name = "svkbd";
static int debug = 0;
static int numlayers = 0;
static int numkeys = 0;
static KeySym ispressingkeysym;
Bool ispressing = False;
Bool sigtermd = False;
/* configuration, allows nested code to access above variables */
#include "config.h"
#ifndef LAYOUT
#error "make sure to define LAYOUT"
#endif
#include LAYOUT
static Key keys[KEYS] = { NULL };
static Key* layers[LAYERS];
void
motionnotify(XEvent *e)
{
XPointerMovedEvent *ev = &e->xmotion;
int i;
for (i = 0; i < numkeys; i++) {
if (keys[i].keysym && ev->x > keys[i].x
&& ev->x < keys[i].x + keys[i].w
&& ev->y > keys[i].y
&& ev->y < keys[i].y + keys[i].h) {
if (keys[i].highlighted != True) {
if (ispressing) {
keys[i].pressed = True;
} else {
keys[i].highlighted = True;
}
drawkey(&keys[i]);
}
continue;
}
if (!IsModifierKey(keys[i].keysym) && keys[i].pressed == True) {
unpress(&keys[i], 0);
drawkey(&keys[i]);
}
if (keys[i].highlighted == True) {
keys[i].highlighted = False;
drawkey(&keys[i]);
}
}
}
void
buttonpress(XEvent *e)
{
XButtonPressedEvent *ev = &e->xbutton;
Key *k;
KeySym mod = 0;
int i;
ispressing = True;
for (i = 0; i < LENGTH(buttonmods); i++) {
if (ev->button == buttonmods[i].button) {
mod = buttonmods[i].mod;
break;
}
}
if ((k = findkey(ev->x, ev->y)))
press(k, mod);
}
void
buttonrelease(XEvent *e)
{
XButtonPressedEvent *ev = &e->xbutton;
Key *k;
KeySym mod = 0;
int i;
ispressing = False;
for (i = 0; i < LENGTH(buttonmods); i++) {
if (ev->button == buttonmods[i].button) {
mod = buttonmods[i].mod;
break;
}
}
if (ev->x < 0 || ev->y < 0) {
unpress(NULL, mod);
} else {
if ((k = findkey(ev->x, ev->y)))
unpress(k, mod);
}
}
void
cleanup(void)
{
int i;
for (i = 0; i < SchemeLast; i++)
free(scheme[i]);
drw_sync(drw);
drw_free(drw);
XSync(dpy, False);
drw_free(drw);
XDestroyWindow(dpy, win);
XSync(dpy, False);
XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
}
void
configurenotify(XEvent *e)
{
XConfigureEvent *ev = &e->xconfigure;
if (ev->window == win && (ev->width != ww || ev->height != wh)) {
ww = ev->width;
wh = ev->height;
drw_resize(drw, ww, wh);
updatekeys();
}
}
void
countrows(void)
{
int i;
for (i = 0, rows = 1; i < numkeys; i++) {
if (keys[i].keysym == 0)
rows++;
}
}
int
countkeys(Key *layer)
{
int i, nkeys = 0;
for (i = 0; i < KEYS; i++) {
if (i > 0 && layer[i].keysym == 0 && layer[i - 1].keysym == 0) {
nkeys--;
break;
}
nkeys++;
}
return nkeys;
}
void
drawkeyboard(void)
{
int i;
for (i = 0; i < numkeys; i++) {
if (keys[i].keysym != 0)
drawkey(&keys[i]);
}
}
void
drawkey(Key *k)
{
int x, y, w, h;
const char *l;
if (k->pressed)
drw_setscheme(drw, scheme[SchemePress]);
else if (k->highlighted)
drw_setscheme(drw, scheme[SchemeHighlight]);
else
drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, k->x, k->y, k->w, k->h, 1, 1);
drw_rect(drw, k->x, k->y, k->w, k->h, 0, 0);
if (k->keysym == XK_KP_Insert) {
if (enableoverlays) {
l = "";
} else {
l = "";
}
} else if (k->label) {
l = k->label;
} else {
l = XKeysymToString(k->keysym);
}
h = fontsize * 2;
y = k->y + (k->h / 2) - (h / 2);
w = TEXTW(l);
x = k->x + (k->w / 2) - (w / 2);
drw_text(drw, x, y, w, h, 0, l, 0);
drw_map(drw, win, k->x, k->y, k->w, k->h);
}
void
expose(XEvent *e)
{
XExposeEvent *ev = &e->xexpose;
if (ev->count == 0 && (ev->window == win))
drawkeyboard();
}
Key *
findkey(int x, int y) {
int i;
for (i = 0; i < numkeys; i++) {
if (keys[i].keysym && x > keys[i].x &&
x < keys[i].x + keys[i].w &&
y > keys[i].y && y < keys[i].y + keys[i].h) {
return &keys[i];
}
}
return NULL;
}
int
hasoverlay(KeySym keysym)
{
int begin, i;
begin = 0;
for (i = 0; i < OVERLAYS; i++) {
if (overlay[i].keysym == XK_Cancel) {
begin = i+1;
} else if (overlay[i].keysym == keysym) {
return begin+1;
}
}
return -1;
}
void
leavenotify(XEvent *e)
{
if (currentoverlay != -1)
hideoverlay();
unpress(NULL, 0);
}
void
record_press_begin(KeySym ks)
{
/* record the begin of the press, don't simulate the actual keypress yet */
gettimeofday(&pressbegin, NULL);
ispressingkeysym = ks;
}
void
press(Key *k, KeySym mod)
{
int i;
int overlayidx = -1;
k->pressed = !k->pressed;
if (debug) printdbg("Begin press: %ld\n", k->keysym);
pressbegin.tv_sec = 0;
pressbegin.tv_usec = 0;
ispressingkeysym = 0;
if (!IsModifierKey(k->keysym)) {
if (enableoverlays && currentoverlay == -1)
overlayidx = hasoverlay(k->keysym);
if (enableoverlays && overlayidx != -1) {
if (!pressbegin.tv_sec && !pressbegin.tv_usec) {
/*record the begin of the press, don't simulate the actual keypress yet */
record_press_begin(k->keysym);
}
} else {
if (debug) printdbg("Simulating press: %ld\n", k->keysym);
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
simulate_keypress(keys[i].keysym);
}
}
pressedmod = mod;
if (pressedmod) {
simulate_keypress(mod);
}
simulate_keypress(k->keysym);
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
simulate_keyrelease(keys[i].keysym);
}
}
}
}
drawkey(k);
}
int
tmp_remap(KeySym keysym)
{
XChangeKeyboardMapping(dpy, tmp_keycode, 1, &keysym, 1);
XSync(dpy, False);
return tmp_keycode;
}
void
simulate_keypress(KeySym keysym)
{
KeyCode code = XKeysymToKeycode(dpy, keysym);
if (code == 0)
code = tmp_remap(keysym);
XTestFakeKeyEvent(dpy, code, True, 0);
}
void
simulate_keyrelease(KeySym keysym)
{
KeyCode code = XKeysymToKeycode(dpy, keysym);
if (code == 0)
code = tmp_remap(keysym);
XTestFakeKeyEvent(dpy, code, False, 0);
}
double
get_press_duration(void)
{
struct timeval now;
gettimeofday(&now, NULL);
return (double) ((now.tv_sec * 1000000L + now.tv_usec) -
(pressbegin.tv_sec * 1000000L + pressbegin.tv_usec)) /
(double) 1000000L;
}
void
unpress(Key *k, KeySym mod)
{
int i;
if (k != NULL) {
switch(k->keysym) {
case XK_Cancel:
cyclelayer();
break;
case XK_script_switch:
togglelayer();
break;
case XK_KP_Insert:
enableoverlays = !enableoverlays;
break;
case XK_Break:
running = False;
break;
default:
break;
}
}
if ((pressbegin.tv_sec || pressbegin.tv_usec) && enableoverlays && k && k->keysym == ispressingkeysym) {
if (currentoverlay == -1) {
if (get_press_duration() < overlay_delay) {
if (debug) printdbg("Delayed simulation of press after release: %ld\n", k->keysym);
/* simulate the press event, as we postponed it earlier in press() */
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
simulate_keypress(keys[i].keysym);
}
}
pressedmod = mod;
if (pressedmod) {
simulate_keypress(mod);
}
simulate_keypress(k->keysym);
pressbegin.tv_sec = 0;
pressbegin.tv_usec = 0;
} else {
return;
}
}
}
if (debug) {
if (k) {
printdbg("Simulation of release: %ld\n", k->keysym);
} else {
printdbg("Simulation of release (all keys)\n");
}
}
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
simulate_keyrelease(keys[i].keysym);
keys[i].pressed = 0;
drawkey(&keys[i]);
break;
}
}
if (i != numkeys) {
if (pressedmod) {
simulate_keyrelease(mod);
}
pressedmod = 0;
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed) {
simulate_keyrelease(keys[i].keysym);
keys[i].pressed = 0;
drawkey(&keys[i]);
}
}
}
if (enableoverlays && currentoverlay != -1) {
if (releaseprotect) {
releaseprotect = 0;
} else {
hideoverlay();
}
}
}
void
run(void)
{
XEvent ev;
int xfd;
fd_set fds;
struct timeval tv;
double duration = 0.0;
int i, r;
xfd = ConnectionNumber(dpy);
tv.tv_sec = 1;
tv.tv_usec = 0;
XFlush(dpy);
while (running) {
usleep(100000L); /* 100ms */
FD_ZERO(&fds);
FD_SET(xfd, &fds);
r = select(xfd + 1, &fds, NULL, NULL, &tv);
if (r) {
while (XPending(dpy)) {
XNextEvent(dpy, &ev);
if (handler[ev.type]) {
(handler[ev.type])(&ev); /* call handler */
}
}
} else {
/* time-out expired without anything interesting happening, check for long-presses */
if (ispressing && ispressingkeysym) {
duration = get_press_duration();
if (debug == 2) printdbg("%f\n", duration);
if (get_press_duration() >= overlay_delay) {
if (debug) printdbg("press duration %f\n", duration);
showoverlay(hasoverlay(ispressingkeysym));
pressbegin.tv_sec = 0;
pressbegin.tv_usec = 0;
ispressingkeysym = 0;
}
}
}
if (r == -1 || sigtermd) {
/* an error occurred or we received a signal */
/* E.g. Generally in scripts we want to call SIGTERM on svkbd in which case
if the user is holding for example the enter key (to execute
the kill or script that does the kill), that causes an issue
since then X doesn't know the keyup is never coming.. (since
process will be dead before finger lifts - in that case we
just trigger out fake up presses for all keys */
if (debug) printdbg("signal received, releasing all keys");
for (i = 0; i < numkeys; i++) {
XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
}
running = False;
}
}
}
void
setup(void)
{
XSetWindowAttributes wa;
XTextProperty str;
XSizeHints *sizeh = NULL;
XClassHint *ch;
XWMHints *wmh;
Atom atype = -1;
int i, j, sh, sw;
#ifdef XINERAMA
XineramaScreenInfo *info = NULL;
#endif
/* init screen */
screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen);
#ifdef XINERAMA
if (XineramaIsActive(dpy)) {
info = XineramaQueryScreens(dpy, &i);
sw = info[0].width;
sh = info[0].height;
XFree(info);
} else
#endif
{
sw = DisplayWidth(dpy, screen);
sh = DisplayHeight(dpy, screen);
}
drw = drw_create(dpy, screen, root, sw, sh);
if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
die("no fonts could be loaded");
drw_setscheme(drw, scheme[SchemeNorm]);
/* find an unused keycode to use as a temporary keycode (derived from source:
https://stackoverflow.com/questions/44313966/c-xtest-emitting-key-presses-for-every-unicode-character) */
KeySym *keysyms;
int keysyms_per_keycode = 0;
int keycode_low, keycode_high;
Bool key_is_empty;
int symindex;
XDisplayKeycodes(dpy, &keycode_low, &keycode_high);
keysyms = XGetKeyboardMapping(dpy, keycode_low, keycode_high - keycode_low, &keysyms_per_keycode);
for (i = keycode_low; i <= keycode_high; i++) {
key_is_empty = True;
for (j = 0; j < keysyms_per_keycode; j++) {
symindex = (i - keycode_low) * keysyms_per_keycode + j;
if (keysyms[symindex] != 0) {
key_is_empty = False;
} else {
break;
}
}
if (key_is_empty) {
tmp_keycode = i;
break;
}
}
/* init appearance */
for (j = 0; j < SchemeLast; j++)
scheme[j] = drw_scm_create(drw, colors[j], 2);
/* init atoms */
if (isdock) {
netatom[NetWMWindowType] = XInternAtom(dpy,
"_NET_WM_WINDOW_TYPE", False);
atype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
}
/* init appearance */
countrows();
if (!ww)
ww = sw;
if (!wh)
wh = sh * rows / heightfactor;
if (!wx)
wx = 0;
if (wx < 0)
wx = sw + wx - ww;
if (!wy)
wy = sh - wh;
if (wy < 0)
wy = sh + wy - wh;
for (i = 0; i < numkeys; i++)
keys[i].pressed = 0;
wa.override_redirect = !wmborder;
wa.border_pixel = scheme[SchemeNorm][ColFg].pixel;
wa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBorderPixel |
CWBackingPixel, &wa);
XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
ButtonPressMask|ExposureMask|LeaveWindowMask|
PointerMotionMask);
wmh = XAllocWMHints();
wmh->input = False;
wmh->flags = InputHint;
if (!isdock) {
sizeh = XAllocSizeHints();
sizeh->flags = PMaxSize | PMinSize;
sizeh->min_width = sizeh->max_width = ww;
sizeh->min_height = sizeh->max_height = wh;
}
XStringListToTextProperty(&name, 1, &str);
ch = XAllocClassHint();
ch->res_class = name;
ch->res_name = name;
XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh,
ch);
XFree(keysyms);
XFree(ch);
XFree(wmh);
XFree(str.value);
if (sizeh != NULL)
XFree(sizeh);
if (isdock) {
XChangeProperty(dpy, win, netatom[NetWMWindowType], XA_ATOM,
32, PropModeReplace,
(unsigned char *)&atype, 1);
}
XMapRaised(dpy, win);
drw_resize(drw, ww, wh);
updatekeys();
drawkeyboard();
}
void
updatekeys(void)
{
int i, j;
int x = 0, y = 0, h, base, r = rows;
h = (wh - 1) / rows;
for (i = 0; i < numkeys; i++, r--) {
for (j = i, base = 0; j < numkeys && keys[j].keysym != 0; j++)
base += keys[j].width;
for (x = 0; i < numkeys && keys[i].keysym != 0; i++) {
keys[i].x = x;
keys[i].y = y;
keys[i].w = keys[i].width * (ww - 1) / base;
keys[i].h = r == 1 ? wh - y - 1 : h;
x += keys[i].w;
}
if (base != 0)
keys[i - 1].w = ww - 1 - keys[i - 1].x;
y += h;
}
}
void
usage(char *argv0)
{
fprintf(stderr, "usage: %s [-hdvDO] [-g geometry] [-fn font] [-l layers] [-s initial_layer]\n", argv0);
fprintf(stderr, "Options:\n");
fprintf(stderr, " -d - Set Dock Window Type\n");
fprintf(stderr, " -D - Enable debug\n");
fprintf(stderr, " -O - Disable overlays\n");
fprintf(stderr, " -l - Comma separated list of layers to enable\n");
fprintf(stderr, " -s - Layer to select on program start\n");
fprintf(stderr, " -H [int] - Height fraction, one key row takes 1/x of the screen height");
fprintf(stderr, " -fn [font] - Set font (Xft, e.g: DejaVu Sans:bold:size=20)\n");
exit(1);
}
void
setlayer(void)
{
numkeys = countkeys(layers[currentlayer]);
memcpy(&keys, layers[currentlayer], sizeof(Key) * numkeys);
}
void
cyclelayer(void)
{
currentlayer++;
if (currentlayer >= numlayers)
currentlayer = 0;
if (debug) printdbg("Cycling to layer %d\n", currentlayer);
setlayer();
updatekeys();
drawkeyboard();
}
void
togglelayer(void)
{
if (currentlayer > 0) {
currentlayer = 0;
} else if (numlayers > 1) {
currentlayer = 1;
}
if (debug) printdbg("Toggling layer %d\n", currentlayer);
setlayer();
updatekeys();
drawkeyboard();
}
void
showoverlay(int idx)
{
if (debug) printdbg("Showing overlay %d\n", idx);
int i,j;
/* unpress existing key (visually only) */
for (i = 0; i < numkeys; i++) {
if (keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
keys[i].pressed = 0;
drawkey(&keys[i]);
break;
}
}
for (i = idx, j=0; i < OVERLAYS; i++, j++) {
if (overlay[i].keysym == XK_Cancel)
break;
while (keys[j].keysym == 0)
j++;
keys[j].label = overlay[i].label;
keys[j].keysym = overlay[i].keysym;
}
currentoverlay = idx;
overlaykeysym = ispressingkeysym;
releaseprotect = 1;
updatekeys();
drawkeyboard();
XSync(dpy, False);
}
void
hideoverlay(void)
{
if (debug) printdbg("Hiding overlay, overlay was #%d\n", currentoverlay);
currentoverlay = -1;
overlaykeysym = 0;
currentlayer--;
cyclelayer();
}
void
sigterm(int signo)
{
running = False;
sigtermd = True;
if (debug) printdbg("SIGTERM received\n");
}
void
init_layers(char *layer_names_list, const char *initial_layer_name)
{
char *s;
int j, found;
if (layer_names_list == NULL) {
numlayers = LAYERS;
memcpy(&layers, &available_layers, sizeof(available_layers));
if (initial_layer_name != NULL) {
for (j = 0; j < LAYERS; j++) {
if (strcmp(layer_names[j], initial_layer_name) == 0) {
currentlayer = j;
break;
}
}
}
} else {
s = strtok(layer_names_list, ",");
while (s != NULL) {
if (numlayers+1 > LAYERS)
die("too many layers specified");
found = 0;
for (j = 0; j < LAYERS; j++) {
if (strcmp(layer_names[j], s) == 0) {
fprintf(stderr, "Adding layer %s\n", s);
layers[numlayers] = available_layers[j];
if (initial_layer_name != NULL && strcmp(layer_names[j], initial_layer_name) == 0) {
currentlayer = numlayers;
}
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "Undefined layer: %s\n", s);
exit(3);
}
numlayers++;
s = strtok(NULL,",");
}
}
setlayer();
}
void
printdbg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fflush(stderr);
}
int
main(int argc, char *argv[])
{
char *initial_layer_name = NULL;
char *layer_names_list = NULL;
char *tmp;
int i, xr, yr, bitm;
unsigned int wr, hr;
signal(SIGTERM, sigterm);
if (OVERLAYS <= 1) {
enableoverlays = 0;
} else {
if ((tmp = getenv("SVKBD_ENABLEOVERLAYS")))
enableoverlays = atoi(tmp);
}
if ((tmp = getenv("SVKBD_LAYERS"))) {
if (!(layer_names_list = strdup(tmp)))
die("memory allocation error");
}
if ((tmp = getenv("SVKBD_HEIGHTFACTOR")))
heightfactor = atoi(tmp);
/* parse command line arguments */
for (i = 1; argv[i]; i++) {
if (!strcmp(argv[i], "-v")) {
die("svkbd-"VERSION);
} else if (!strcmp(argv[i], "-d")) {
isdock = True;
continue;
} else if (!strncmp(argv[i], "-g", 2)) {
if (i >= argc - 1)
continue;
bitm = XParseGeometry(argv[i + 1], &xr, &yr, &wr, &hr);
if (bitm & XValue)
wx = xr;
if (bitm & YValue)
wy = yr;
if (bitm & WidthValue)
ww = (int)wr;
if (bitm & HeightValue)
wh = (int)hr;
if (bitm & XNegative && wx == 0)
wx = -1;
if (bitm & YNegative && wy == 0)
wy = -1;
i++;
} else if (!strcmp(argv[i], "-fn")) { /* font or font set */
fonts[0] = argv[++i];
} else if (!strcmp(argv[i], "-D")) {
debug = 1;
} else if (!strcmp(argv[i], "-h")) {
usage(argv[0]);
} else if (!strcmp(argv[i], "-O")) {
enableoverlays = 0;
} else if (!strcmp(argv[i], "-l")) {
if (i >= argc - 1)
continue;
free(layer_names_list);
if (!(layer_names_list = strdup(argv[++i])))
die("memory allocation error");
} else if (!strcmp(argv[i], "-s")) {
if (i >= argc - 1)
continue;
initial_layer_name = argv[++i];
} else if (!strcmp(argv[i], "-H")) {
if (i >= argc - 1)
continue;
heightfactor = atoi(argv[++i]);
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[i]);
exit(2);
}
}
if (heightfactor <= 0)
die("height factor must be a positive integer");
init_layers(layer_names_list, initial_layer_name);
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fprintf(stderr, "warning: no locale support");
if (!(dpy = XOpenDisplay(0)))
die("cannot open display");
setup();
run();
cleanup();
XCloseDisplay(dpy);
free(layer_names_list);
return 0;
}

BIN
svkbd.o Normal file

Binary file not shown.

36
util.c Normal file
View File

@ -0,0 +1,36 @@
/* See LICENSE file for copyright and license details. */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
void *
ecalloc(size_t nmemb, size_t size)
{
void *p;
if (!(p = calloc(nmemb, size)))
die("calloc:");
return p;
}
void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
exit(1);
}

8
util.h Normal file
View File

@ -0,0 +1,8 @@
/* See LICENSE file for copyright and license details. */
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);

BIN
util.o Normal file

Binary file not shown.