~mil/sxmo-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
5 2

[PATCH v6 1/4] Added Xft support (in the same fashion as done in dmenu by Hiltjo Posthuma), Xinerama support, changed colors and key layout

Details
Message ID
<20200718122732.1192398-1-proycon@anaproy.nl>
DKIM signature
missing
Download raw message
Patch: +611 -173
This is version 6 of my svkbd patch, and I'm nearing completion on all
the stuff I wanted to implement. I decided to split this patch into 4
logical units, making for a nicer commit history when merged,
all four together constitute all the work I did on svkbd
and need to be applied in succession.

See the last patch in the series (4/4) for comments on what I changed since
v5.

---
 LICENSE       |   1 +
 Makefile      |   2 +-
 config.def.h  |  17 +-
 config.mk     |  13 +-
 drw.c         | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++
 drw.h         |  51 ++++++
 layout.sxmo.h |  38 ++---
 svkbd.c       | 202 ++++++++----------------
 util.c        |  35 +++++
 util.h        |   8 +
 10 files changed, 611 insertions(+), 173 deletions(-)
 create mode 100644 drw.c
 create mode 100644 drw.h
 create mode 100644 util.c
 create mode 100644 util.h

diff --git a/LICENSE b/LICENSE
index 51864d7..0ccc34f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,6 +2,7 @@ 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"),
diff --git a/Makefile b/Makefile
index c34b112..ea44b05 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@

include config.mk

SRC = svkbd.c
SRC = drw.c svkbd.c util.c

all: options svkbd-${LAYOUT}

diff --git a/config.def.h b/config.def.h
index 5011f40..f748b93 100644
--- a/config.def.h
+++ b/config.def.h
@@ -1,9 +1,12 @@
static const Bool wmborder = True;
static const char font[] = "10x20";
static const char normbgcolor[] = "#cccccc";
static const char normfgcolor[] = "#000000";
static const char pressbgcolor[] = "#0000cc";
static const char pressfgcolor[] = "#ffffff";
static const char highlightbgcolor[] = "#0000cc";
static const char highlightfgcolor[] = "#ffffff";
static int fontsize = 16;
static const char *fonts[] = {
	"monospace:size=16"
};
static const char *colors[SchemeLast][2] = {
	/*     fg         bg       */
	[SchemeNorm] = { "#58a7c6", "#14313d" },
	[SchemePress] = { "#ffffff", "#005577" },
	[SchemeHighlight] = { "#58a7c6", "#005577" },
};

diff --git a/config.mk b/config.mk
index fbb721e..6ee4520 100644
--- a/config.mk
+++ b/config.mk
@@ -1,7 +1,7 @@
# svkbd version
VERSION = 0.1

LAYOUT ?= en
LAYOUT ?= sxmo

# Customize below to fit your system

@@ -12,13 +12,16 @@ MANPREFIX = ${PREFIX}/share/man
X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib

# Xinerama, comment if you don't want it
XINERAMALIBS = -L${X11LIB} -lXinerama
XINERAMAFLAGS = -DXINERAMA

# includes and libs
INCS = -I. -I./layouts -I/usr/include -I${X11INC}
LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXtst
INCS = -I. -I./layouts -I/usr/include -I${X11INC} -I/usr/include/freetype2
LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXtst -lfontconfig -lXft ${XINERAMALIBS}

# flags
CPPFLAGS = -DVERSION=\"${VERSION}\" \
	   ${XINERAMAFLAGS}
CPPFLAGS = -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS = -g -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
LDFLAGS = -g ${LIBS}

diff --git a/drw.c b/drw.c
new file mode 100644
index 0000000..963cca7
--- /dev/null
+++ b/drw.c
@@ -0,0 +1,417 @@
/* 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;
}
diff --git a/drw.h b/drw.h
new file mode 100644
index 0000000..50d9208
--- /dev/null
+++ b/drw.h
@@ -0,0 +1,51 @@
/* 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);
diff --git a/layout.sxmo.h b/layout.sxmo.h
index 52ce781..1ee74bc 100644
--- a/layout.sxmo.h
+++ b/layout.sxmo.h
@@ -37,20 +37,20 @@ static Key keys_en[40] = {
        { 0, XK_m, 1 },
        /*{ "/?", XK_slash, 1 },*/
        { "Tab", XK_Tab, 1 },
        { "<-", XK_BackSpace, 2 },
        { "⇍ Bksp", XK_BackSpace, 2 },

        { 0 }, /* New row */
        { "Layer 2", XK_Cancel, 1},
        { "Shift", XK_Shift_L, 1 },
        { "↺", XK_Cancel, 1},
        { "Shft", XK_Shift_L, 1 },
        /*{ "L", XK_Left, 1 },*/
        { "D", XK_Down, 1 },
        { "U", XK_Up, 1 },
        { "↓", XK_Down, 1 },
        { "↑", XK_Up, 1 },
        /*{ "R", XK_Right, 1 },*/
        { "", XK_space, 2 },
        { "Esc", XK_Escape, 1 },
        { "Ctrl", XK_Control_L, 1 },
        /*{ "Alt", XK_Alt_L, 1 },*/
        { "Enter", XK_Return, 2 },
        { "↲ Enter", XK_Return, 2 },
};

static Key keys_symbols[40] = {
@@ -80,28 +80,28 @@ static Key keys_symbols[40] = {

  { 0 }, /* New row */

  { " ", XK_Shift_L|XK_bar, 1 },
  { " ", XK_Shift_L|XK_bar, 1 },
  { "L", XK_Left, 1 },
  { "R", XK_Right, 1 },
  { " ", XK_Shift_L|XK_bar, 1 },
  { " ", XK_Shift_L|XK_bar, 1 },
  { " ", XK_Shift_L|XK_bar, 1 },
  { "", XK_Shift_L|XK_bar, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
  { "→", XK_Right, 1 },
  { "⇥", XK_End, 1 },
  { "⇊", XK_Next, 1 },
  { "⇈", XK_Prior, 1 },
  { "Tab", XK_Tab, 1 },
  { "<-", XK_BackSpace, 2 },
  { "⇍ Bksp", XK_BackSpace, 2 },

  { 0 }, /* New row */
  { "Layer 1", XK_Cancel, 1},
  { "Shift", XK_Shift_L, 1 },
  { "↺", XK_Cancel, 1},
  { "Shft", XK_Shift_L, 1 },
  /*{ "L", XK_Left, 1 },*/
  { "D", XK_Down, 1 },
  { "U", XK_Up, 1 },
  { "↓", XK_Down, 1 },
  { "↑", XK_Up, 1 },
  /*{ "R", XK_Right, 1 },*/
  { "", XK_space, 2 },
  { "Esc", XK_Escape, 1 },
  { "Ctrl", XK_Control_L, 1 },
  /*{ "Alt", XK_Alt_L, 1 },*/
  { "Enter", XK_Return, 2 },
  { "↲ Enter", XK_Return, 2 },
};

Buttonmod buttonmods[] = {
diff --git a/svkbd.c b/svkbd.c
index 2076fb7..de9e92a 100644
--- a/svkbd.c
+++ b/svkbd.c
@@ -13,38 +13,30 @@
#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 <signal.h>
#include <sys/select.h>

#include "drw.h"
#include "util.h"



/* macros */
#define MAX(a, b)       ((a) > (b) ? (a) : (b))
#define LENGTH(x)       (sizeof x / sizeof x[0])
#define TEXTW(X)        (drw_fontset_getwidth(drw, (X)))

/* enums */
enum { ColFG, ColBG, ColLast };
enum { SchemeNorm, SchemePress, SchemeHighlight, SchemeLast };
enum { NetWMWindowType, NetLast };

/* typedefs */
typedef unsigned int uint;
typedef unsigned long ulong;

typedef struct {
	ulong norm[ColLast];
	ulong press[ColLast];
	ulong high[ColLast];

	Drawable drawable;
	GC gc;
	struct {
		int ascent;
		int descent;
		int height;
		XFontSet set;
		XFontStruct *xfont;
	} font;
} DC; /* draw context */

typedef struct {
	char *label;
	KeySym keysym;
@@ -66,18 +58,15 @@ static void buttonrelease(XEvent *e);
static void cleanup(void);
static void configurenotify(XEvent *e);
static void countrows();
static void die(const char *errstr, ...);
static void drawkeyboard(void);
static void drawkey(Key *k);
static void expose(XEvent *e);
static Key *findkey(int x, int y);
static ulong getcolor(const char *colstr);
static void initfont(const char *fontstr);
static void leavenotify(XEvent *e);
static void press(Key *k, KeySym mod);
static void run(void);
static void setup(void);
static int textnw(const char *text, uint len);
static void togglelayer();
static void unpress(Key *k, KeySym mod);
static void updatekeys();

@@ -93,8 +82,9 @@ static void (*handler[LASTEvent]) (XEvent *) = {
};
static Atom netatom[NetLast];
static Display *dpy;
static DC dc;
static Drw *drw;
static Window root, win;
static Clr* scheme[SchemeLast];
static Bool running = True, isdock = False;
static KeySym pressedmod = 0;
static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0;
@@ -200,14 +190,12 @@ cleanup(void) {
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
		}
	}
	XSync(dpy, False);

	if(dc.font.set)
		XFreeFontSet(dpy, dc.font.set);
	else
		XFreeFont(dpy, dc.font.xfont);
	XFreePixmap(dpy, dc.drawable);
	XFreeGC(dpy, dc.gc);
	for (i = 0; i < SchemeLast; i++)
		free(scheme[i]);
	drw_free(drw);
	drw_sync(drw);
	XSync(dpy, False);
	XDestroyWindow(dpy, win);
	XSync(dpy, False);
	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
@@ -220,9 +208,7 @@ configurenotify(XEvent *e) {
	if(ev->window == win && (ev->width != ww || ev->height != wh)) {
		ww = ev->width;
		wh = ev->height;
		XFreePixmap(dpy, dc.drawable);
		dc.drawable = XCreatePixmap(dpy, root, ww, wh,
				DefaultDepth(dpy, screen));
		drw_resize(drw, ww, wh);
		updatekeys();
	}
}
@@ -237,15 +223,6 @@ countrows() {
	}
}

void
die(const char *errstr, ...) {
	va_list ap;

	va_start(ap, errstr);
	vfprintf(stderr, errstr, ap);
	va_end(ap);
	exit(EXIT_FAILURE);
}

void
drawkeyboard(void) {
@@ -259,42 +236,29 @@ drawkeyboard(void) {

void
drawkey(Key *k) {
	int x, y, h, len;
	XRectangle r = { k->x, k->y, k->w, k->h};
	int x, y, w, h;
	const char *l;
	ulong *col;

	if(k->pressed)
		col = dc.press;
		drw_setscheme(drw, scheme[SchemePress]);
	else if(k->highlighted)
		col = dc.high;
		drw_setscheme(drw, scheme[SchemeHighlight]);
	else
		col = dc.norm;

	XSetForeground(dpy, dc.gc, col[ColBG]);
	XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
	XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
	r.height -= 1;
	r.width -= 1;
	XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
	XSetForeground(dpy, dc.gc, col[ColFG]);
		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->label) {
		l = k->label;
	} else {
		l = XKeysymToString(k->keysym);
	}
	len = strlen(l);
	h = dc.font.ascent + dc.font.descent;
	y = k->y + (k->h / 2) - (h / 2) + dc.font.ascent;
	x = k->x + (k->w / 2) - (textnw(l, len) / 2);
	if(dc.font.set) {
		XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l,
				len);
	} else {
		XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len);
	}
	XCopyArea(dpy, dc.drawable, win, dc.gc, k->x, k->y, k->w, k->h,
			k->x, k->y);
	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
@@ -319,52 +283,7 @@ findkey(int x, int y) {
	return NULL;
}

ulong
getcolor(const char *colstr) {
	Colormap cmap = DefaultColormap(dpy, screen);
	XColor color;

	if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
		die("error, cannot allocate color '%s'\n", colstr);
	return color.pixel;
}

void
initfont(const char *fontstr) {
	char *def, **missing;
	int i, n;

	missing = NULL;
	if(dc.font.set)
		XFreeFontSet(dpy, dc.font.set);
	dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
	if(missing) {
		while(n--)
			fprintf(stderr, "svkbd: missing fontset: %s\n", missing[n]);
		XFreeStringList(missing);
	}
	if(dc.font.set) {
		XFontStruct **xfonts;
		char **font_names;
		dc.font.ascent = dc.font.descent = 0;
		n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
		for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
			dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
			dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
			xfonts++;
		}
	} else {
		if(dc.font.xfont)
			XFreeFont(dpy, dc.font.xfont);
		dc.font.xfont = NULL;
		if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
		&& !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
			die("error, cannot load font: '%s'\n", fontstr);
		dc.font.ascent = dc.font.xfont->ascent;
		dc.font.descent = dc.font.xfont->descent;
	}
	dc.font.height = dc.font.ascent + dc.font.descent;
}

void
leavenotify(XEvent *e) {
@@ -484,15 +403,36 @@ setup(void) {
	XSizeHints *sizeh = NULL;
	XClassHint *ch;
	Atom atype = -1;
	int i, sh, sw;
	int i, j, sh, sw;
	XWMHints *wmh;

	#if XINERAMA
	XineramaScreenInfo *info = NULL;
	#endif

	/* init screen */
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);
	sw = DisplayWidth(dpy, screen);
	sh = DisplayHeight(dpy, screen);
	initfont(font);
	#if 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]);

	/* init appearance */
	for (j = 0; j < SchemeLast; j++)
		scheme[j] = drw_scm_create(drw, colors[j], 2);

	/* init atoms */
	if(isdock) {
@@ -517,23 +457,12 @@ setup(void) {
	if(wy < 0)
		wy = sh + wy - wh;

	dc.norm[ColBG] = getcolor(normbgcolor);
	dc.norm[ColFG] = getcolor(normfgcolor);
	dc.press[ColBG] = getcolor(pressbgcolor);
	dc.press[ColFG] = getcolor(pressfgcolor);
	dc.high[ColBG] = getcolor(highlightbgcolor);
	dc.high[ColFG] = getcolor(highlightfgcolor);
	dc.drawable = XCreatePixmap(dpy, root, ww, wh,
			DefaultDepth(dpy, screen));
	dc.gc = XCreateGC(dpy, root, 0, 0);
	if(!dc.font.set)
		XSetFont(dpy, dc.gc, dc.font.xfont->fid);
	for(i = 0; i < LENGTH(keys); i++)
		keys[i].pressed = 0;

	wa.override_redirect = !wmborder;
	wa.border_pixel = dc.norm[ColFG];
	wa.background_pixel = dc.norm[ColBG];
	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 |
@@ -572,20 +501,11 @@ setup(void) {
	}

	XMapRaised(dpy, win);
	drw_resize(drw, ww, wh);
	updatekeys();
	drawkeyboard();
}

int
textnw(const char *text, uint len) {
	XRectangle r;

	if(dc.font.set) {
		XmbTextExtents(dc.font.set, text, len, NULL, &r);
		return r.width;
	}
	return XTextWidth(dc.font.xfont, text, len);
}

void
updatekeys() {
@@ -639,7 +559,7 @@ main(int argc, char *argv[]) {
	signal(SIGTERM, sigterm);
	for (i = 1; argv[i]; i++) {
		if(!strcmp(argv[i], "-v")) {
			die("svkbd-"VERSION", © 2006-2016 svkbd engineers,"
			die("svkbd-"VERSION", © 2006-2020 svkbd engineers,"
				       " see LICENSE for details\n");
		} else if(!strcmp(argv[i], "-d")) {
			isdock = True;
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..fe044fc
--- /dev/null
+++ b/util.c
@@ -0,0 +1,35 @@
/* 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);
}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..f633b51
--- /dev/null
+++ b/util.h
@@ -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);
--
2.27.0

[PATCH v6 2/4] Added overlays (appearing on long press), multiple layer support (rather than just a toggle) with new layers, style changes

Details
Message ID
<20200718122732.1192398-2-proycon@anaproy.nl>
In-Reply-To
<20200718122732.1192398-1-proycon@anaproy.nl> (view parent)
DKIM signature
missing
Download raw message
Patch: +582 -56
---
 config.def.h  |   9 +-
 layout.sxmo.h | 291 +++++++++++++++++++++++++++++++++++++++++--
 svkbd.c       | 338 +++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 582 insertions(+), 56 deletions(-)

diff --git a/config.def.h b/config.def.h
index f748b93..fe404b4 100644
--- a/config.def.h
+++ b/config.def.h
@@ -1,12 +1,13 @@
static const Bool wmborder = True;
static int fontsize = 16;
static int fontsize = 20;
static double overlay_delay = 1.0;
static const char *fonts[] = {
	"monospace:size=16"
	"DejaVu Sans:bold:size=20"
};
static const char *colors[SchemeLast][2] = {
	/*     fg         bg       */
	[SchemeNorm] = { "#58a7c6", "#14313d" },
	[SchemePress] = { "#ffffff", "#005577" },
	[SchemeNorm] = { "#ffffff", "#14313d" },
	[SchemePress] = { "#ffffff", "#000000" },
	[SchemeHighlight] = { "#58a7c6", "#005577" },
};

diff --git a/layout.sxmo.h b/layout.sxmo.h
index 1ee74bc..2ca0727 100644
--- a/layout.sxmo.h
+++ b/layout.sxmo.h
@@ -1,6 +1,7 @@
static Key keys[40] = { NULL };
#define KEYS 40
static Key keys[KEYS] = { NULL };

static Key keys_en[40] = {
static Key keys_en[KEYS] = {
        { 0, XK_q, 1 },
        { 0, XK_w, 1 },
        { 0, XK_e, 1 },
@@ -23,7 +24,7 @@ static Key keys_en[40] = {
        { 0, XK_j, 1 },
        { 0, XK_k, 1 },
        { 0, XK_l, 1 },
        { ";:", XK_colon, 1 },
        { "/?", XK_slash, 1 },
        /*{ "'", XK_apostrophe, 2 },*/

        { 0 }, /* New row */
@@ -37,7 +38,7 @@ static Key keys_en[40] = {
        { 0, XK_m, 1 },
        /*{ "/?", XK_slash, 1 },*/
        { "Tab", XK_Tab, 1 },
        { "⇍ Bksp", XK_BackSpace, 2 },
        { "⌫Bksp", XK_BackSpace, 2 },

        { 0 }, /* New row */
        { "↺", XK_Cancel, 1},
@@ -53,7 +54,214 @@ static Key keys_en[40] = {
        { "↲ Enter", XK_Return, 2 },
};

static Key keys_symbols[40] = {
#define OVERLAYS 165
static Key overlay[OVERLAYS] = {
        { 0, XK_a }, //Overlay for a
        //---
        { "à", XK_agrave },
        { "á", XK_aacute },
        { "â", XK_acircumflex },
        { "ä", XK_adiaeresis },
        { "ą", XK_aogonek },
        { "ã", XK_atilde },
        { "ā", XK_amacron },
        { "ă", XK_abreve },
        { "å", XK_aring },
        { "æ", XK_ae },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_e }, //Overlay for e
        //---
        { "è", XK_egrave },
        { "é", XK_eacute },
        { "ê", XK_ecircumflex },
        { "ë", XK_ediaeresis },
        { "ę", XK_eogonek },
        { "ē", XK_emacron },
        { "ė", XK_eabovedot },
        { 0, XK_Cancel },
        //--
        { 0, XK_y }, //New overlay
        //---
        { "ỳ", XK_ygrave },
        { "ý", XK_yacute },
        { "ŷ", XK_ycircumflex },
        { "ÿ", XK_ydiaeresis },
        { 0, XK_Cancel },
        //--
        { 0, XK_u }, //New overlay
        //---
        { "ù", XK_ugrave },
        { "ú", XK_uacute },
        { "û", XK_ucircumflex },
        { "ü", XK_udiaeresis },
        { "ų", XK_uogonek },
        { "ū", XK_umacron },
        { "ů", XK_uring},
        { "ŭ", XK_ubreve},
        { "ű", XK_udoubleacute },
        { 0, XK_Cancel },
        //--
        { 0, XK_i }, //New overlay
        //---
        { "ì", XK_igrave },
        { "í", XK_iacute },
        { "î", XK_icircumflex },
        { "ï", XK_idiaeresis },
        { "į", XK_iogonek },
        { "ī", XK_imacron },
        { "ı", XK_idotless },
        { 0, XK_Cancel },
        //--
        { 0, XK_o }, //New overlay
        //---
        { "ò", XK_ograve },
        { "ó", XK_oacute },
        { "ô", XK_ocircumflex },
        { "ö", XK_odiaeresis },
        { "ǫ", XK_ogonek },
        { "õ", XK_otilde },
        { "ō", XK_omacron },
        { "ø", XK_oslash },
        { "ő", XK_odoubleacute },
        { "œ", XK_oe },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_d }, //New overlay
        //---
        { "ď", XK_dcaron },
        { "ð", XK_eth },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_c }, //New overlay
        //---
        { "ç", XK_ccedilla },
        { "ĉ", XK_ccircumflex },
        { "č", XK_ccaron },
        { "ć", XK_cacute },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_s }, //New overlay
        //---
        { "ş", XK_scedilla },
        { "ŝ", XK_scircumflex },
        { "š", XK_scaron },
        { "ś", XK_sacute },
        { "ß", XK_ssharp },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_z }, //New overlay
        //---
        { "ž", XK_zcaron },
        { "ż", XK_zabovedot },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_n }, //New overlay
        //---
        { "ñ", XK_ntilde },
        { "ń", XK_nacute },
        { "ň", XK_ncaron },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //
        { 0, XK_t }, //New overlay
        //---
        { "ț", XK_tcedilla },
        { "ť", XK_tcaron },
        { "þ", XK_thorn },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //----
        { 0, XK_g }, //New overlay
        //---
        { "ĝ", XK_gcircumflex },
        { "ğ", XK_gbreve },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //
        { 0, XK_h }, //New overlay
        //---
        { "ĥ", XK_hcircumflex },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //
        { 0, XK_j }, //New overlay
        //---
        { "ĵ", XK_jcircumflex },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_l }, //New overlay
        //---
        { "ł", XK_lstroke },
        { "ľ", XK_lcaron },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_r }, //New overlay
        //---
        { "ř", XK_rcaron },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { "🙂", 0x101f642 }, //emoji overlay
        //---
        { "😀", 0x101f600 },
        { "😁", 0x101f601 },
        { "😂", 0x101f602 },
        { "😃", 0x101f603 },
        { "😄", 0x101f604 },
        { "😅", 0x101f605 },
        { "😆", 0x101f606 },
        { "😇", 0x101f607 },
        { "😈", 0x101f608 },
        { "😉", 0x101f609 },
        { "😊", 0x101f60a },
        { "😋", 0x101f60b },
        { "😌", 0x101f60c },
        { "😍", 0x101f60d },
        { "😎", 0x101f60e },
        { "😏", 0x101f60f },
        { "😐", 0x101f610 },
        { "😒", 0x101f612 },
        { "😓", 0x101f613 },
        { "😛", 0x101f61b },
        { "😮", 0x101f62e },
        { "😟", 0x101f61f },
        { "😟", 0x101f620 },
        { "😢", 0x101f622 },
        { "😭", 0x101f62d },
        { "😳", 0x101f633 },
        { "😴", 0x101f634 },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
		{ "/?", XK_slash }, //punctuation overlay
		//--
		{ "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_apostrophe, 1 },
		{ "`~", XK_grave, 1 },
		{ "-_", XK_minus, 1 },
		{ "=+", XK_plus, 1 },
		{ "[{", XK_bracketleft, 1 },
		{ "]}", XK_bracketright, 1 },
		{ ",<", XK_comma, 1 },
		{ ".>", XK_period, 1 },
		{ "/?", XK_slash, 1 },
		{ "\\|", XK_backslash, 1 },
		{ "¡", XK_exclamdown, 1 },
		{ "?", XK_questiondown, 1 },
		{ "°", XK_degree, 1 },
		{ "£", XK_sterling, 1 },
		{ "€", XK_EuroSign, 1 },
		{ "¥", XK_yen, 1 },
		{ ";:", XK_colon, 1 },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
};


static Key keys_symbols[KEYS] = {
  { "1!", XK_1, 1 },
  { "2@", XK_2, 1 },
  { "3#", XK_3, 1 },
@@ -80,7 +288,55 @@ static Key keys_symbols[40] = {

  { 0 }, /* New row */

  { "", XK_Shift_L|XK_bar, 1 },
  { "☺", 0x101f642, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
  { "→", XK_Right, 1 },
  { "⇥", XK_End, 1 },
  { "⇊", XK_Next, 1 },
  { ";:", XK_colon, 1 },
  { "Tab", XK_Tab, 1 },
  { "⌫Bksp", XK_BackSpace, 2 },

  { 0 }, /* New row */
  { "↺", XK_Cancel, 1},
  { "Shft", XK_Shift_L, 1 },
  { "↓", XK_Down, 1 },
  { "↑", XK_Up, 1 },
  { "", XK_space, 2 },
  { "Esc", XK_Escape, 1 },
  { "Ctrl", XK_Control_L, 1 },
  { "↲ Enter", XK_Return, 2 },
};

static Key keys_functions[KEYS] = {
  { "F1", XK_F1, 1 },
  { "F2", XK_F2, 1 },
  { "F3", XK_F3, 1 },
  { "F4", XK_F4, 1 },
  { "F5", XK_F5, 1 },
  { "F6", XK_F6, 1 },
  { "F7", XK_F7, 1 },
  { "F8", XK_F8, 1 },
  { "F9", XK_F9, 1 },
  { "F10", XK_F10, 1 },

  { 0 }, /* New row */

  { "▶", XF86XK_AudioPlay, 1 },
  { "●", XF86XK_AudioRecord, 1 },
  { "■", XF86XK_AudioStop, 1 },
  { "◂◂", XF86XK_AudioPrev, 1 },
  { "▸▸", XF86XK_AudioNext, 1 },
  { "♫M", XF86XK_AudioMute, 1 },
  { "♫-", XF86XK_AudioLowerVolume, 1 },
  { "♫+", XF86XK_AudioRaiseVolume, 1 },
  { "☀-", XF86XK_MonBrightnessDown, 1 },
  { "☀+", XF86XK_MonBrightnessUp, 1 },

  { 0 }, /* New row */

  { "Del", XK_Delete, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
  { "→", XK_Right, 1 },
@@ -88,22 +344,37 @@ static Key keys_symbols[40] = {
  { "⇊", XK_Next, 1 },
  { "⇈", XK_Prior, 1 },
  { "Tab", XK_Tab, 1 },
  { "⇍ Bksp", XK_BackSpace, 2 },
  { "⌫Bksp", XK_BackSpace, 2 },

  { 0 }, /* New row */
  { "↺", XK_Cancel, 1},
  { "Shft", XK_Shift_L, 1 },
  /*{ "L", XK_Left, 1 },*/
  { "↓", XK_Down, 1 },
  { "↑", XK_Up, 1 },
  /*{ "R", XK_Right, 1 },*/
  { "", XK_space, 2 },
  { "Esc", XK_Escape, 1 },
  { "Ctrl", XK_Control_L, 1 },
  /*{ "Alt", XK_Alt_L, 1 },*/
  { "↲ Enter", XK_Return, 2 },
};


#define LAYERS 3
static Key* layers[LAYERS] = {
    keys_en,
    keys_symbols,
    keys_functions,
};


#define CYCLEMODKEY (KEYS - 3) //third last key (Escape)
#define CYCLEMODS 3
static Key cyclemods[CYCLEMODS] = {
  { "Esc", XK_Escape, 1 },
  { "Alt", XK_Alt_L, 1 },
  { "AGr", XK_ISO_Level3_Shift, 1 },
};


Buttonmod buttonmods[] = {
        { XK_Shift_L, Button2 },
        { XK_Alt_L, Button3 },
diff --git a/svkbd.c b/svkbd.c
index de9e92a..aaee87a 100644
--- a/svkbd.c
+++ b/svkbd.c
@@ -8,6 +8,8 @@
#include <string.h>
#include <stdlib.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>
@@ -18,16 +20,19 @@
#include <X11/extensions/Xinerama.h>
#endif
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

#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 };
@@ -62,11 +67,18 @@ static void drawkeyboard(void);
static void drawkey(Key *k);
static void expose(XEvent *e);
static Key *findkey(int x, int y);
static int iscyclemod(KeySym keysym);
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 togglelayer();
static void simulate_keypress(KeySym keysym);
static void simulate_keyrelease(KeySym keysym);
static void showoverlay(int idx);
static void cyclemod();
static void hideoverlay();
static void cyclelayer();
static void unpress(Key *k, KeySym mod);
static void updatekeys();

@@ -87,11 +99,20 @@ 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 currentoverlay = -1; // -1 = no overlay
static int currentcyclemod = 0;
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 KeySym ispressingkeysym;

Bool ispressing = False;
Bool baselayer = True;
Bool sigtermd = False;

/* configuration, allows nested code to access above variables */
@@ -284,43 +305,126 @@ findkey(int x, int y) {
}


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

int
iscyclemod(KeySym keysym) {
	int i;
	for(i = 0; i < CYCLEMODS; i++) {
		if(cyclemods[i].keysym == keysym) {
			return i;
		}
	}
	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(!IsModifierKey(k->keysym)) {
		for(i = 0; i < LENGTH(keys); i++) {
			if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
				XTestFakeKeyEvent(dpy,
					XKeysymToKeycode(dpy, keys[i].keysym),
					True, 0);
			}
		}
		pressedmod = mod;
		if(pressedmod) {
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, mod),
					True, 0);
	if (debug) { printf("Begin press: %ld\n", k->keysym); fflush(stdout); }
	pressbegin.tv_sec = 0;
	pressbegin.tv_usec = 0;
	ispressingkeysym = 0;

	int cm = iscyclemod(k->keysym);
	if (cm != -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);
		}
		XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, k->keysym), True, 0);
	} else if(!IsModifierKey(k->keysym)) {
		if (currentoverlay == -1)
			overlayidx = hasoverlay(k->keysym);
		if (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) { printf("Simulating press: %ld\n", k->keysym); fflush(stdout); }
			for(i = 0; i < LENGTH(keys); 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 < LENGTH(keys); i++) {
			if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
				XTestFakeKeyEvent(dpy,
					XKeysymToKeycode(dpy, keys[i].keysym),
					False, 0);
			for(i = 0; i < LENGTH(keys); 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() {
	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;
@@ -328,7 +432,7 @@ unpress(Key *k, KeySym mod) {
	if(k != NULL) {
		switch(k->keysym) {
		case XK_Cancel:
			togglelayer();
			cyclelayer();
			break;
		case XK_Break:
		  running = False;
@@ -337,11 +441,42 @@ unpress(Key *k, KeySym mod) {
		}
	}


	if ((pressbegin.tv_sec || pressbegin.tv_usec) && k && k->keysym == ispressingkeysym) {
		if (currentoverlay == -1) {
			if (get_press_duration() < overlay_delay) {
				if (debug) { printf("Delayed simulation of press after release: %ld\n", k->keysym); fflush(stdout); }
				//simulate the press event, as we postponed it earlier in press()
				for(i = 0; i < LENGTH(keys); 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) {
			printf("Simulation of release: %ld\n", k->keysym); fflush(stdout);
		} else {
			printf("Simulation of release (all keys)"); fflush(stdout);
		}
	}


	for(i = 0; i < LENGTH(keys); i++) {
		if(keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
			XTestFakeKeyEvent(dpy,
				XKeysymToKeycode(dpy, keys[i].keysym),
				False, 0);
			simulate_keyrelease(keys[i].keysym);
			keys[i].pressed = 0;
			drawkey(&keys[i]);
			break;
@@ -349,22 +484,26 @@ unpress(Key *k, KeySym mod) {
	}
	if(i != LENGTH(keys)) {
		if(pressedmod) {
			XTestFakeKeyEvent(dpy,
				XKeysymToKeycode(dpy, pressedmod),
				False, 0);
			simulate_keyrelease(mod);
		}
		pressedmod = 0;

		for(i = 0; i < LENGTH(keys); i++) {
			if(keys[i].pressed) {
				XTestFakeKeyEvent(dpy,
					XKeysymToKeycode(dpy,
						keys[i].keysym), False, 0);
				simulate_keyrelease(keys[i].keysym);
				keys[i].pressed = 0;
				drawkey(&keys[i]);
			}
		}
	}

	if (currentoverlay != -1) {
		if (releaseprotect) {
			releaseprotect = 0;
		} else {
			hideoverlay();
		}
	}
}

void
@@ -373,11 +512,14 @@ run(void) {
	int xfd;
	fd_set fds;
	struct timeval tv;
	double duration = 0.0;
	int cyclemodidx;


	xfd = ConnectionNumber(dpy);
	tv.tv_usec = 0;
	tv.tv_sec = 2;
	tv.tv_sec = 1;


	//XSync(dpy, False);
	XFlush(dpy);
@@ -392,7 +534,25 @@ run(void) {
					(handler[ev.type])(&ev); /* call handler */
				}
			}
		} else {
			if (ispressing && ispressingkeysym) {
				duration = get_press_duration();
				if (debug == 2) { printf("%f\n", duration); fflush(stdout); }
				if (get_press_duration() >= overlay_delay) {
					if (debug) { printf("press duration %f\n", duration); fflush(stdout); }
					cyclemodidx = iscyclemod(ispressingkeysym);
					if (cyclemodidx != -1) {
						cyclemod();
					} else {
						showoverlay(hasoverlay(ispressingkeysym));
					}
					pressbegin.tv_sec = 0;
					pressbegin.tv_usec = 0;
					ispressingkeysym = 0;
				}
			}
		}
		usleep(100000L);
	}
}

@@ -425,10 +585,34 @@ setup(void) {
		sw = DisplayWidth(dpy, screen);
		sh = DisplayHeight(dpy, screen);
	}
    drw = drw_create(dpy, screen, root, sw, sh);
	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]);
	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 = NULL;
	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++)
@@ -464,9 +648,9 @@ setup(void) {
	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);
			CopyFromParent, CopyFromParent, CopyFromParent,
			CWOverrideRedirect | CWBorderPixel |
			CWBackingPixel, &wa);
	XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
			ButtonPressMask|ExposureMask|LeaveWindowMask|
			PointerMotionMask);
@@ -488,6 +672,7 @@ setup(void) {
	XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh,
			ch);

	XFree(keysyms);
	XFree(ch);
	XFree(wmh);
	XFree(str.value);
@@ -531,18 +716,84 @@ updatekeys() {

void
usage(char *argv0) {
	fprintf(stderr, "usage: %s [-hdv] [-g geometry]\n", argv0);
	fprintf(stderr, "usage: %s [-hdvD] [-g geometry] [-fn font]\n", argv0);
	exit(1);
}

void
togglelayer() {
	memcpy(&keys, baselayer ? &keys_symbols : &keys_en, sizeof(keys_en));
cyclelayer() {
	currentlayer++;
	if (currentlayer >= LAYERS)
		currentlayer = 0;
	if (debug) { printf("Cycling to layer %d\n", currentlayer); fflush(stdout); }
	memcpy(&keys, layers[currentlayer], sizeof(keys_en));
	updatekeys();
	drawkeyboard();
}

void
cyclemod() {
	int i;
	//unpress all pressed keys
	for(i = 0; i < LENGTH(keys); i++) {
		if(keys[i].pressed) {
			keys[i].pressed = 0;
			drawkey(&keys[i]);
		}
	}
	pressedmod = 0;
	pressbegin.tv_sec = 0;
	pressbegin.tv_usec = 0;
	ispressingkeysym = 0;
	currentcyclemod++;
	if (currentcyclemod >= CYCLEMODS)
		currentcyclemod = 0;
	if (debug) { printf("Cycling modifier to %d\n", currentcyclemod); fflush(stdout); }
	keys[CYCLEMODKEY].label = cyclemods[currentcyclemod].label;
	keys[CYCLEMODKEY].keysym = cyclemods[currentcyclemod].keysym;
	drawkey(&keys[CYCLEMODKEY]);
	XSync(dpy, False);
}

void
showoverlay(int idx) {
	if (debug) { printf("Showing overlay %d\n", idx); fflush(stdout); }
	int i,j;
	//unpress existing key (visually only)
	for(i = 0; i < LENGTH(keys); 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();
	baselayer = !baselayer;
	XSync(dpy, False);
}

void
hideoverlay() {
	if (debug) { printf("Hiding overlay %d\n", currentoverlay); fflush(stdout); }
	currentoverlay = -1;
	overlaykeysym = 0;
	currentlayer = -1;
	cyclelayer();
}


void
sigterm(int sig)
{
@@ -582,6 +833,10 @@ main(int argc, char *argv[]) {
			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]);
		}
@@ -597,4 +852,3 @@ main(int argc, char *argv[]) {
	XCloseDisplay(dpy);
	return 0;
}

-- 
2.27.0

[PATCH v6 3/4] added an extra key column (alt now on keyboard by default), added cyrillic keymap, added a toggle to enable/disable overlays, added quick toggle buttons to toggle primary two layers. Added some configurability using command line parameters and environment variables.

Details
Message ID
<20200718122732.1192398-3-proycon@anaproy.nl>
In-Reply-To
<20200718122732.1192398-1-proycon@anaproy.nl> (view parent)
DKIM signature
missing
Download raw message
Patch: +272 -95
---
 README.md     |  34 ++++++++--
 layout.sxmo.h | 168 ++++++++++++++++++++++++++++++++++++++++----------
 svkbd.c       | 165 ++++++++++++++++++++++++++++++++-----------------
 3 files changed, 272 insertions(+), 95 deletions(-)

diff --git a/README.md b/README.md
index 71ebd1a..69124fa 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
SVKBD
=====

This is a simple virtual keyboard, intended to be used in environments,
where no keyboard is available.

@@ -9,8 +10,11 @@ Installation
	% make
	% make install

This will create by default `svkbd-en`, which is svkbd using an English
keyboard layout. You can create svkbd for additional layouts by doing:
This will create by default `svkbd-sxmo`, which is svkbd using an versatile
layout with multiple layers and overlays, and optimised for mobile devices.
It was designed for [Simple X Mobile](https://sr.ht/~mil/Sxmo).

You can create svkbd for additional layouts by doing:

	% make svkbd-$layout

@@ -20,25 +24,43 @@ This will take the file `layout.$layout.h` and create `svkbd-$layout`.
Usage
-----

	% svkbd-en
	% svkbd-sxmo

This will open svkbd at the bottom of the screen, showing the default
English layout.

	% svkbd-en -d
	% svkbd-sxmo -d

This tells svkbd-en to announce itself being a dock window, which then
This tells svkbd-sxmo 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-en -g 400x200+1+1
	% svkbd-sxmo -g 400x200+1+1

This will start svkbd-en with a size of 400x200 and at the upper left
window corner.

You can enable layers on the fly 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.

The virtual keyboard comes 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.

Repository
----------

SXMO version:

	git clone https://git.sr.ht/~mil/sxmo-svkbd

Original upstream:

	git clone http://git.suckless.org/svkbd

diff --git a/layout.sxmo.h b/layout.sxmo.h
index 2ca0727..f036fd6 100644
--- a/layout.sxmo.h
+++ b/layout.sxmo.h
@@ -1,7 +1,8 @@
#define KEYS 40
#define KEYS 43
static Key keys[KEYS] = { NULL };

static Key keys_en[KEYS] = {
        { "Esc", XK_Escape, 1 },
        { 0, XK_q, 1 },
        { 0, XK_w, 1 },
        { 0, XK_e, 1 },
@@ -15,6 +16,7 @@ static Key keys_en[KEYS] = {

        { 0 }, /* New row */

        { "'\"", XK_apostrophe, 1 },
        { 0, XK_a, 1 },
        { 0, XK_s, 1 },
        { 0, XK_d, 1 },
@@ -25,10 +27,10 @@ static Key keys_en[KEYS] = {
        { 0, XK_k, 1 },
        { 0, XK_l, 1 },
        { "/?", XK_slash, 1 },
        /*{ "'", XK_apostrophe, 2 },*/

        { 0 }, /* New row */

        { "123", XK_Mode_switch, 1 },
        { 0, XK_z, 1 },
        { 0, XK_x, 1 },
        { 0, XK_c, 1 },
@@ -36,25 +38,21 @@ static Key keys_en[KEYS] = {
        { 0, XK_b, 1 },
        { 0, XK_n, 1 },
        { 0, XK_m, 1 },
        /*{ "/?", XK_slash, 1 },*/
        { "Tab", XK_Tab, 1 },
        { "⌫Bksp", XK_BackSpace, 2 },

        { 0 }, /* New row */
        { "↺", XK_Cancel, 1},
        { "Shft", XK_Shift_L, 1 },
        /*{ "L", XK_Left, 1 },*/
        { "Shift", XK_Shift_L, 2 },
        { "Ctrl", XK_Control_L, 1 },
        { "Alt", XK_Alt_L, 1 },
        { "", XK_space, 2 },
        { "↓", XK_Down, 1 },
        { "↑", XK_Up, 1 },
        /*{ "R", XK_Right, 1 },*/
        { "", XK_space, 2 },
        { "Esc", XK_Escape, 1 },
        { "Ctrl", XK_Control_L, 1 },
        /*{ "Alt", XK_Alt_L, 1 },*/
        { "↲ Enter", XK_Return, 2 },
};

#define OVERLAYS 165
#define OVERLAYS 197
static Key overlay[OVERLAYS] = {
        { 0, XK_a }, //Overlay for a
        //---
@@ -195,6 +193,58 @@ static Key overlay[OVERLAYS] = {
        { 0, XK_r }, //New overlay
        //---
        { "ř", XK_rcaron },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_Cyrillic_softsign }, //New overlay
        //---
        { "ъ", XK_Cyrillic_hardsign },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_Cyrillic_ie }, //New overlay
        //---
        { "ё", XK_Cyrillic_io },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_Cyrillic_e }, //New overlay
        //---
        { "Є", XK_Ukrainian_ie },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_Cyrillic_i }, //New overlay
        //---
        { "і", XK_Ukrainian_i },
        { "ї", XK_Ukrainian_yi },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //---
        { 0, XK_Cyrillic_u }, //New overlay
        //---
        { "ў", XK_Byelorussian_shortu },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { 0, XK_Cyrillic_shorti }, //New overlay
        //---
        { "ј", XK_Cyrillic_je },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { 0, XK_Cyrillic_el }, //New overlay
        //---
        { "љ", XK_Cyrillic_lje },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { 0, XK_Cyrillic_en }, //New overlay
        //---
        { "њ", XK_Cyrillic_nje },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { 0, XK_Cyrillic_tse }, //New overlay
        //---
        { "џ", XK_Cyrillic_dzhe },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { 0, XK_Cyrillic_che }, //New overlay
        //---
        { "ћ", XK_Serbian_tshe },
        { "ђ", XK_Serbian_dje },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
		//---
        { "🙂", 0x101f642 }, //emoji overlay
@@ -262,6 +312,7 @@ static Key overlay[OVERLAYS] = {


static Key keys_symbols[KEYS] = {
  { "Esc", XK_Escape, 1 },
  { "1!", XK_1, 1 },
  { "2@", XK_2, 1 },
  { "3#", XK_3, 1 },
@@ -285,31 +336,34 @@ static Key keys_symbols[KEYS] = {
  { ".>", XK_period, 1 },
  { "/?", XK_slash, 1 },
  { "\\|", XK_backslash, 1 },
  { ";:", XK_colon, 1 },

  { 0 }, /* New row */

  { "abc", XK_Mode_switch, 1 },
  { "☺", 0x101f642, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
  { "→", XK_Right, 1 },
  { "⇥", XK_End, 1 },
  { "⇊", XK_Next, 1 },
  { ";:", XK_colon, 1 },
  { "⇈", XK_Prior, 1 },
  { "Tab", XK_Tab, 1 },
  { "⌫Bksp", XK_BackSpace, 2 },

  { 0 }, /* New row */
  { "↺", XK_Cancel, 1},
  { "Shft", XK_Shift_L, 1 },
  { "Shift", XK_Shift_L, 2 },
  { "Ctrl", XK_Control_L, 1 },
  { "Alt", XK_Alt_L, 1 },
  { "", XK_space, 2 },
  { "↓", XK_Down, 1 },
  { "↑", XK_Up, 1 },
  { "", XK_space, 2 },
  { "Esc", XK_Escape, 1 },
  { "Ctrl", XK_Control_L, 1 },
  { "↲ Enter", XK_Return, 2 },
};

static Key keys_functions[KEYS] = {
  { "Esc", XK_Escape, 1 },
  { "F1", XK_F1, 1 },
  { "F2", XK_F2, 1 },
  { "F3", XK_F3, 1 },
@@ -323,6 +377,7 @@ static Key keys_functions[KEYS] = {

  { 0 }, /* New row */

  { "≅", XK_KP_Insert, 1 },
  { "▶", XF86XK_AudioPlay, 1 },
  { "●", XF86XK_AudioRecord, 1 },
  { "■", XF86XK_AudioStop, 1 },
@@ -336,6 +391,7 @@ static Key keys_functions[KEYS] = {

  { 0 }, /* New row */

  { "abc", XK_Mode_switch, 1 },
  { "Del", XK_Delete, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
@@ -348,30 +404,80 @@ static Key keys_functions[KEYS] = {

  { 0 }, /* New row */
  { "↺", XK_Cancel, 1},
  { "Shft", XK_Shift_L, 1 },
  { "Shift", XK_Shift_L, 2 },
  { "Ctrl", XK_Control_L, 1 },
  { "Alt", XK_Alt_L, 1 },
  { "", XK_space, 2 },
  { "↓", XK_Down, 1 },
  { "↑", XK_Up, 1 },
  { "", XK_space, 2 },
  { "Esc", XK_Escape, 1 },
  { "Ctrl", XK_Control_L, 1 },
  { "↲ Enter", XK_Return, 2 },
};


#define LAYERS 3
static Key* layers[LAYERS] = {
    keys_en,
    keys_symbols,
    keys_functions,
static Key keys_ru[KEYS] = {
        { "и", XK_Cyrillic_shorti, 1 },
        { "ц", XK_Cyrillic_tse, 1 },
        { "у", XK_Cyrillic_u, 1 },
        { "к", XK_Cyrillic_ka, 1 },
        { "е", XK_Cyrillic_ie, 1 },
        { "н", XK_Cyrillic_en, 1 },
        { "г", XK_Cyrillic_ghe, 1 },
        { "ш", XK_Cyrillic_sha, 1 },
        { "щ", XK_Cyrillic_shcha, 1 },
        { "з", XK_Cyrillic_ze, 1 },
        { "х", XK_Cyrillic_ha, 1 },

        { 0 }, /* New row */

        { "ф", XK_Cyrillic_ef, 1 },
        { "ы", XK_Cyrillic_yeru, 1 },
        { "в", XK_Cyrillic_ve, 1 },
        { "а", XK_Cyrillic_a, 1 },
        { "п", XK_Cyrillic_pe, 1 },
        { "о", XK_Cyrillic_o, 1 },
        { "л", XK_Cyrillic_el, 1 },
        { "д", XK_Cyrillic_de, 1 },
        { "ж", XK_Cyrillic_zhe, 1 },
        { "э", XK_Cyrillic_e, 1 },
        { "ю", XK_Cyrillic_yu, 1 },

        { 0 }, /* New row */

        { "123", XK_Mode_switch, 1 },
        { "я", XK_Cyrillic_ya, 1 },
        { "ч", XK_Cyrillic_che, 1 },
        { "с", XK_Cyrillic_es, 1 },
        { "м", XK_Cyrillic_em, 1 },
        { "и", XK_Cyrillic_i, 1 },
        { "т", XK_Cyrillic_te, 1 },
        { "ь", XK_Cyrillic_softsign, 1 },
        { "б", XK_Cyrillic_be, 1 },
        { "⌫Bksp", XK_BackSpace, 2 },

        { 0 }, /* New row */
        { "↺", XK_Cancel, 1},
        { "Shift", XK_Shift_L, 2 },
        { "Ctrl", XK_Control_L, 1 },
        { "Alt", XK_Alt_L, 1 },
        { "", XK_space, 2 },
        { "↓", XK_Down, 1 },
        { "↑", XK_Up, 1 },
        { "↲ Enter", XK_Return, 2 },
};

#define LAYERS 4
static char* layer_names[LAYERS] = {
    "en",
    "symbols",
    "functions",
    "ru",
};

#define CYCLEMODKEY (KEYS - 3) //third last key (Escape)
#define CYCLEMODS 3
static Key cyclemods[CYCLEMODS] = {
  { "Esc", XK_Escape, 1 },
  { "Alt", XK_Alt_L, 1 },
  { "AGr", XK_ISO_Level3_Shift, 1 },
static Key* available_layers[LAYERS] = {
    keys_en,
    keys_symbols,
    keys_functions,
    keys_ru
};


diff --git a/svkbd.c b/svkbd.c
index aaee87a..5157da4 100644
--- a/svkbd.c
+++ b/svkbd.c
@@ -67,7 +67,6 @@ static void drawkeyboard(void);
static void drawkey(Key *k);
static void expose(XEvent *e);
static Key *findkey(int x, int y);
static int iscyclemod(KeySym keysym);
static void leavenotify(XEvent *e);
static void press(Key *k, KeySym mod);
static double get_press_duration();
@@ -76,9 +75,9 @@ static void setup(void);
static void simulate_keypress(KeySym keysym);
static void simulate_keyrelease(KeySym keysym);
static void showoverlay(int idx);
static void cyclemod();
static void hideoverlay();
static void cyclelayer();
static void togglelayer();
static void unpress(Key *k, KeySym mod);
static void updatekeys();

@@ -101,14 +100,15 @@ 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 int currentcyclemod = 0;
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 KeySym ispressingkeysym;

@@ -119,6 +119,8 @@ Bool sigtermd = False;
#include "config.h"
#include "layout.h"

static Key* layers[LAYERS];

void
motionnotify(XEvent *e)
{
@@ -207,6 +209,15 @@ cleanup(void) {
	//      process will be dead before finger lifts - in that case we
	//      just trigger out fake up presses for all keys
	if (sigtermd) {
		//handle last pending events
		XEvent ev;
		while (XPending(dpy)) {
			XNextEvent(dpy, &ev);
			if(handler[ev.type]) {
				(handler[ev.type])(&ev); /* call handler */
			}
		}
		if (debug) { printf("Cleanup: simulating key release\n"); fflush(stdout); }
		for (i = 0; i < LENGTH(keys); i++) {
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
		}
@@ -214,8 +225,8 @@ cleanup(void) {

	for (i = 0; i < SchemeLast; i++)
		free(scheme[i]);
	drw_free(drw);
	drw_sync(drw);
	drw_free(drw);
	XSync(dpy, False);
	XDestroyWindow(dpy, win);
	XSync(dpy, False);
@@ -269,7 +280,13 @@ drawkey(Key *k) {
	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->label) {
	if (k->keysym == XK_KP_Insert) {
		if (enableoverlays) {
			l = "≅";
		} else {
			l = "≇";
		}
	} else if(k->label) {
		l = k->label;
	} else {
		l = XKeysymToString(k->keysym);
@@ -319,17 +336,6 @@ hasoverlay(KeySym keysym) {
	return -1;
}

int
iscyclemod(KeySym keysym) {
	int i;
	for(i = 0; i < CYCLEMODS; i++) {
		if(cyclemods[i].keysym == keysym) {
			return i;
		}
	}
	return -1;
}

void
leavenotify(XEvent *e) {
	if (currentoverlay != -1) {
@@ -355,16 +361,10 @@ press(Key *k, KeySym mod) {
	pressbegin.tv_usec = 0;
	ispressingkeysym = 0;

	int cm = iscyclemod(k->keysym);
	if (cm != -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(!IsModifierKey(k->keysym)) {
		if (currentoverlay == -1)
	if(!IsModifierKey(k->keysym)) {
		if (enableoverlays && currentoverlay == -1)
			overlayidx = hasoverlay(k->keysym);
		if (overlayidx != -1) {
		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);
@@ -434,6 +434,12 @@ unpress(Key *k, KeySym mod) {
		case XK_Cancel:
			cyclelayer();
			break;
		case XK_script_switch:
			togglelayer();
			break;
		case XK_KP_Insert:
			enableoverlays = !enableoverlays;
			break;
		case XK_Break:
		  running = False;
		default:
@@ -442,7 +448,7 @@ unpress(Key *k, KeySym mod) {
	}


	if ((pressbegin.tv_sec || pressbegin.tv_usec) && k && k->keysym == ispressingkeysym) {
	if ((pressbegin.tv_sec || pressbegin.tv_usec) && enableoverlays && k && k->keysym == ispressingkeysym) {
		if (currentoverlay == -1) {
			if (get_press_duration() < overlay_delay) {
				if (debug) { printf("Delayed simulation of press after release: %ld\n", k->keysym); fflush(stdout); }
@@ -469,7 +475,7 @@ unpress(Key *k, KeySym mod) {
		if (k) {
			printf("Simulation of release: %ld\n", k->keysym); fflush(stdout);
		} else {
			printf("Simulation of release (all keys)"); fflush(stdout);
			printf("Simulation of release (all keys)\n"); fflush(stdout);
		}
	}

@@ -497,7 +503,7 @@ unpress(Key *k, KeySym mod) {
		}
	}

	if (currentoverlay != -1) {
	if (enableoverlays && currentoverlay != -1) {
		if (releaseprotect) {
			releaseprotect = 0;
		} else {
@@ -513,7 +519,6 @@ run(void) {
	fd_set fds;
	struct timeval tv;
	double duration = 0.0;
	int cyclemodidx;


	xfd = ConnectionNumber(dpy);
@@ -525,6 +530,7 @@ run(void) {
	XFlush(dpy);

	while (running) {
		usleep(100000L);
		FD_ZERO(&fds);
		FD_SET(xfd, &fds);
		if (select(xfd + 1, &fds, NULL, NULL, &tv)) {
@@ -540,19 +546,13 @@ run(void) {
				if (debug == 2) { printf("%f\n", duration); fflush(stdout); }
				if (get_press_duration() >= overlay_delay) {
					if (debug) { printf("press duration %f\n", duration); fflush(stdout); }
					cyclemodidx = iscyclemod(ispressingkeysym);
					if (cyclemodidx != -1) {
						cyclemod();
					} else {
						showoverlay(hasoverlay(ispressingkeysym));
					}
					showoverlay(hasoverlay(ispressingkeysym));
					pressbegin.tv_sec = 0;
					pressbegin.tv_usec = 0;
					ispressingkeysym = 0;
				}
			}
		}
		usleep(100000L);
	}
}

@@ -716,14 +716,20 @@ updatekeys() {

void
usage(char *argv0) {
	fprintf(stderr, "usage: %s [-hdvD] [-g geometry] [-fn font]\n", argv0);
	fprintf(stderr, "usage: %s [-hdvDOl] [-g geometry] [-fn font]\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, "  -fn [font] - Set font (Xft, e.g: DejaVu Sans:bold:size=20)\n");
	exit(1);
}

void
cyclelayer() {
	currentlayer++;
	if (currentlayer >= LAYERS)
	if (currentlayer >= numlayers)
		currentlayer = 0;
	if (debug) { printf("Cycling to layer %d\n", currentlayer); fflush(stdout); }
	memcpy(&keys, layers[currentlayer], sizeof(keys_en));
@@ -732,29 +738,19 @@ cyclelayer() {
}

void
cyclemod() {
	int i;
	//unpress all pressed keys
	for(i = 0; i < LENGTH(keys); i++) {
		if(keys[i].pressed) {
			keys[i].pressed = 0;
			drawkey(&keys[i]);
		}
togglelayer() {
	if (currentlayer > 0) {
		currentlayer = 0;
	} else if (numlayers > 1) {
		currentlayer = 1;
	}
	pressedmod = 0;
	pressbegin.tv_sec = 0;
	pressbegin.tv_usec = 0;
	ispressingkeysym = 0;
	currentcyclemod++;
	if (currentcyclemod >= CYCLEMODS)
		currentcyclemod = 0;
	if (debug) { printf("Cycling modifier to %d\n", currentcyclemod); fflush(stdout); }
	keys[CYCLEMODKEY].label = cyclemods[currentcyclemod].label;
	keys[CYCLEMODKEY].keysym = cyclemods[currentcyclemod].keysym;
	drawkey(&keys[CYCLEMODKEY]);
	XSync(dpy, False);
	if (debug) { printf("Toggling layer %d\n", currentlayer); fflush(stdout); }
	memcpy(&keys, layers[currentlayer], sizeof(keys_en));
	updatekeys();
	drawkeyboard();
}


void
showoverlay(int idx) {
	if (debug) { printf("Showing overlay %d\n", idx); fflush(stdout); }
@@ -799,15 +795,58 @@ sigterm(int sig)
{
	running = False;
	sigtermd = True;
	if (debug) { printf("Sigterm received\n"); fflush(stdout); }
}


void
init_layers(char * layer_names_list) {
	if (layer_names_list == NULL) {
		numlayers = LAYERS;
		memcpy(&layers, &available_layers, sizeof(available_layers));
	} else {
		char * s;
		int j;
		s = strtok(layer_names_list, ",");
		while (s != NULL) {
			if (numlayers+1 > LAYERS) die("too many layers specified");
			int found = 0;
			for (j = 0; j < LAYERS; j++) {
				if (strcmp(layer_names[j], s) == 0) {
					layers[numlayers] = available_layers[j];
					printf("Adding layer %s\n", s);
					found = 1;
					break;
				}
			}
			if (!found) {
				fprintf(stderr, "Undefined layer: %s\n", s);
				exit(3);
			}
			numlayers++;
			s = strtok(NULL,",");
		}
	}
}

int
main(int argc, char *argv[]) {
	int i, xr, yr, bitm;
	unsigned int wr, hr;
	char * layer_names_list = NULL;

	memcpy(&keys, &keys_en, sizeof(keys_en));
	signal(SIGTERM, sigterm);

	const char* enableoverlays_env = getenv("SVKBD_ENABLEOVERLAYS");
	if (enableoverlays_env != NULL) enableoverlays = atoi(enableoverlays_env);
	const char* layers_env = getenv("SVKBD_LAYERS");
	if (layers_env != NULL) {
		layer_names_list = malloc(128);
		strcpy(layer_names_list, layers_env);
	}


	for (i = 1; argv[i]; i++) {
		if(!strcmp(argv[i], "-v")) {
			die("svkbd-"VERSION", © 2006-2020 svkbd engineers,"
@@ -839,9 +878,18 @@ main(int argc, char *argv[]) {
			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;
			if (layer_names_list == NULL) layer_names_list = malloc(128);
			strcpy(layer_names_list, argv[i+1]);
		}
	}

	init_layers(layer_names_list);

	if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
		fprintf(stderr, "warning: no locale support\n");
	if(!(dpy = XOpenDisplay(0)))
@@ -850,5 +898,6 @@ main(int argc, char *argv[]) {
	run();
	cleanup();
	XCloseDisplay(dpy);
	if (layer_names_list != NULL) free(layer_names_list);
	return 0;
}
-- 
2.27.0

[PATCH v6 4/4] Added a dialer/numpad keyboard, added the ability to handle layouts with less keys/different layouts. Extra configurability: select layout on startup, customisable height fraction.

Details
Message ID
<20200718122732.1192398-4-proycon@anaproy.nl>
In-Reply-To
<20200718122732.1192398-1-proycon@anaproy.nl> (view parent)
DKIM signature
missing
Download raw message
Patch: +125 -29
This is new since v5 and adds a dialer/numpad layout. This layout
contains less keys (= bigger) than the other layouts, so I this patch implements stuff to make
that work reliably.

I introduced an extra command line parameter to select a starting
layout (whilst still retaining access to the others). It makes sense to
invoke ``svkbd-sxmo -s dialer`` when a phone number is to be entered.

This should address this ticket: https://todo.sr.ht/~mil/sxmo-tickets/56

I also made the height fraction configurable, allowing the keyboard to
take more or less vertical space.

---
 README.md     |   7 ++++
 config.def.h  |   1 +
 layout.sxmo.h |  39 +++++++++++++++++-
 svkbd.c       | 107 +++++++++++++++++++++++++++++++++++++-------------
 4 files changed, 125 insertions(+), 29 deletions(-)

diff --git a/README.md b/README.md
index 69124fa..2704601 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,13 @@ overlay functionality with the ``-O`` flag or by setting the environment variabl
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, it simply relies on a standard US QWERTY layout
(setxkbmap us) being activated. If you use another XKB layout you will get unpredictable output that does not match the
labels on the virtual keycaps.

Repository
----------

diff --git a/config.def.h b/config.def.h
index fe404b4..4f8a346 100644
--- a/config.def.h
+++ b/config.def.h
@@ -1,6 +1,7 @@
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"
};
diff --git a/layout.sxmo.h b/layout.sxmo.h
index f036fd6..6aafdb3 100644
--- a/layout.sxmo.h
+++ b/layout.sxmo.h
@@ -68,7 +68,7 @@ static Key overlay[OVERLAYS] = {
        { "æ", XK_ae },
        { 0, XK_Cancel }, /* XK_Cancel signifies  overlay boundary */
        //--
        { 0, XK_e }, //Overlay for e
        { 0, XK_e }, //Overlay for e (first item after boundary defines the trigger)
        //---
        { "è", XK_egrave },
        { "é", XK_eacute },
@@ -465,11 +465,45 @@ static Key keys_ru[KEYS] = {
        { "↲ Enter", XK_Return, 2 },
};

#define LAYERS 4
static Key keys_dialer[KEYS] = {
        { "Esc", XK_Escape, 1 },
        { "1!", XK_1, 1 },
        { "2@", XK_2, 1 },
        { "3#", XK_3, 1 },
        { "⌫Bksp", XK_BackSpace, 2 },
        { 0 }, /* New row */

        { "Shift", XK_Shift_L, 1 },
        { "4$", XK_4, 1 },
        { "5%", XK_5, 1 },
        { "6^", XK_6, 1 },
        { "-_", XK_minus, 1 },
        { ",<", XK_comma, 1 },
        { 0 }, /* New row */

        { "abc", XK_Mode_switch, 1 },
        { "7&", XK_7, 1 },
        { "8*", XK_8, 1 },
        { "9(", XK_9, 1 },
        { "=+", XK_equal, 1 },
        { "/?", XK_slash, 1 },
        { 0 }, /* New row */

        { "↺", XK_Cancel, 1},
        { "", XK_space, 1 },
        { "0)", XK_0, 1 },
        { ".>", XK_period, 1 },
        { "↲ Enter", XK_Return, 2},
        { 0 }, /* New row */
        { 0 }, /* Last item (double 0) */
};

#define LAYERS 5
static char* layer_names[LAYERS] = {
    "en",
    "symbols",
    "functions",
    "dialer",
    "ru",
};

@@ -477,6 +511,7 @@ static Key* available_layers[LAYERS] = {
    keys_en,
    keys_symbols,
    keys_functions,
    keys_dialer,
    keys_ru
};

diff --git a/svkbd.c b/svkbd.c
index 5157da4..aec9d35 100644
--- a/svkbd.c
+++ b/svkbd.c
@@ -63,6 +63,7 @@ static void buttonrelease(XEvent *e);
static void cleanup(void);
static void configurenotify(XEvent *e);
static void countrows();
static int countkeys(Key *k);
static void drawkeyboard(void);
static void drawkey(Key *k);
static void expose(XEvent *e);
@@ -77,6 +78,7 @@ 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();
@@ -109,6 +111,7 @@ 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;

@@ -127,7 +130,7 @@ motionnotify(XEvent *e)
	XPointerMovedEvent *ev = &e->xmotion;
	int i;

	for(i = 0; i < LENGTH(keys); 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
@@ -218,7 +221,7 @@ cleanup(void) {
			}
		}
		if (debug) { printf("Cleanup: simulating key release\n"); fflush(stdout); }
		for (i = 0; i < LENGTH(keys); i++) {
		for (i = 0; i < numkeys; i++) {
			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
		}
	}
@@ -249,18 +252,34 @@ void
countrows() {
	int i = 0;

	for(i = 0, rows = 1; i < LENGTH(keys); i++) {
	for(i = 0, rows = 1; i < numkeys; i++) {
		if(keys[i].keysym == 0)
			rows++;
	}
}

int
countkeys(Key * layer) {
	int keys = 0;
	int i;

	for(i = 0; i < KEYS; i++) {
		if (i > 0 && layer[i].keysym == 0 && layer[i-1].keysym == 0) {
			keys--;
			break;
		}
		keys++;
	}

	return keys;
}


void
drawkeyboard(void) {
	int i;

	for(i = 0; i < LENGTH(keys); i++) {
	for(i = 0; i < numkeys; i++) {
		if(keys[i].keysym != 0)
			drawkey(&keys[i]);
	}
@@ -311,7 +330,7 @@ Key *
findkey(int x, int y) {
	int i;

	for(i = 0; i < LENGTH(keys); 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) {
@@ -371,7 +390,7 @@ press(Key *k, KeySym mod) {
			}
		} else {
			if (debug) { printf("Simulating press: %ld\n", k->keysym); fflush(stdout); }
			for(i = 0; i < LENGTH(keys); i++) {
			for(i = 0; i < numkeys; i++) {
				if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
					simulate_keypress(keys[i].keysym);
				}
@@ -382,7 +401,7 @@ press(Key *k, KeySym mod) {
			}
			simulate_keypress(k->keysym);

			for(i = 0; i < LENGTH(keys); i++) {
			for(i = 0; i < numkeys; i++) {
				if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
					simulate_keyrelease(keys[i].keysym);
				}
@@ -453,7 +472,7 @@ unpress(Key *k, KeySym mod) {
			if (get_press_duration() < overlay_delay) {
				if (debug) { printf("Delayed simulation of press after release: %ld\n", k->keysym); fflush(stdout); }
				//simulate the press event, as we postponed it earlier in press()
				for(i = 0; i < LENGTH(keys); i++) {
				for(i = 0; i < numkeys; i++) {
					if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
						simulate_keypress(keys[i].keysym);
					}
@@ -480,7 +499,7 @@ unpress(Key *k, KeySym mod) {
	}


	for(i = 0; i < LENGTH(keys); i++) {
	for(i = 0; i < numkeys; i++) {
		if(keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
			simulate_keyrelease(keys[i].keysym);
			keys[i].pressed = 0;
@@ -488,13 +507,13 @@ unpress(Key *k, KeySym mod) {
			break;
		}
	}
	if(i != LENGTH(keys)) {
	if(i != numkeys) {
		if(pressedmod) {
			simulate_keyrelease(mod);
		}
		pressedmod = 0;

		for(i = 0; i < LENGTH(keys); i++) {
		for(i = 0; i < numkeys; i++) {
			if(keys[i].pressed) {
				simulate_keyrelease(keys[i].keysym);
				keys[i].pressed = 0;
@@ -630,7 +649,7 @@ setup(void) {
	if(!ww)
		ww = sw;
	if(!wh)
		wh = sh * rows / 16;
		wh = sh * rows / heightfactor;

	if(!wx)
		wx = 0;
@@ -641,7 +660,7 @@ setup(void) {
	if(wy < 0)
		wy = sh + wy - wh;

	for(i = 0; i < LENGTH(keys); i++)
	for(i = 0; i < numkeys; i++)
		keys[i].pressed = 0;

	wa.override_redirect = !wmborder;
@@ -698,10 +717,10 @@ updatekeys() {
	int x = 0, y = 0, h, base, r = rows;

	h = (wh - 1) / rows;
	for(i = 0; i < LENGTH(keys); i++, r--) {
		for(j = i, base = 0; j < LENGTH(keys) && keys[j].keysym != 0; j++)
	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 < LENGTH(keys) && keys[i].keysym != 0; i++) {
		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;
@@ -716,23 +735,30 @@ updatekeys() {

void
usage(char *argv0) {
	fprintf(stderr, "usage: %s [-hdvDOl] [-g geometry] [-fn font]\n", 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() {
	numkeys = countkeys(layers[currentlayer]);
	memcpy(&keys, layers[currentlayer], sizeof(Key) * numkeys);
}

void
cyclelayer() {
	currentlayer++;
	if (currentlayer >= numlayers)
		currentlayer = 0;
	if (debug) { printf("Cycling to layer %d\n", currentlayer); fflush(stdout); }
	memcpy(&keys, layers[currentlayer], sizeof(keys_en));
	setlayer();
	updatekeys();
	drawkeyboard();
}
@@ -745,7 +771,7 @@ togglelayer() {
		currentlayer = 1;
	}
	if (debug) { printf("Toggling layer %d\n", currentlayer); fflush(stdout); }
	memcpy(&keys, layers[currentlayer], sizeof(keys_en));
	setlayer();
	updatekeys();
	drawkeyboard();
}
@@ -756,7 +782,7 @@ showoverlay(int idx) {
	if (debug) { printf("Showing overlay %d\n", idx); fflush(stdout); }
	int i,j;
	//unpress existing key (visually only)
	for(i = 0; i < LENGTH(keys); i++) {
	for(i = 0; i < numkeys; i++) {
		if(keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
			keys[i].pressed = 0;
			drawkey(&keys[i]);
@@ -800,21 +826,32 @@ sigterm(int sig)


void
init_layers(char * layer_names_list) {
init_layers(char * layer_names_list, const char * initial_layer_name) {
	int j;
	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 {
		char * s;
		int j;
		s = strtok(layer_names_list, ",");
		while (s != NULL) {
			if (numlayers+1 > LAYERS) die("too many layers specified");
			int 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];
					printf("Adding layer %s\n", s);
					if (initial_layer_name != NULL && strcmp(layer_names[j], initial_layer_name) == 0) {
						currentlayer = numlayers;
					}
					found = 1;
					break;
				}
@@ -827,17 +864,19 @@ init_layers(char * layer_names_list) {
			s = strtok(NULL,",");
		}
	}
	setlayer();
}

int
main(int argc, char *argv[]) {
	int i, xr, yr, bitm;
	unsigned int wr, hr;
	char * initial_layer_name = NULL;
	char * layer_names_list = NULL;

	memcpy(&keys, &keys_en, sizeof(keys_en));
	signal(SIGTERM, sigterm);

	//parse environment variables
	const char* enableoverlays_env = getenv("SVKBD_ENABLEOVERLAYS");
	if (enableoverlays_env != NULL) enableoverlays = atoi(enableoverlays_env);
	const char* layers_env = getenv("SVKBD_LAYERS");
@@ -845,8 +884,11 @@ main(int argc, char *argv[]) {
		layer_names_list = malloc(128);
		strcpy(layer_names_list, layers_env);
	}
	const char* heightfactor_s = getenv("SVKBD_HEIGHTFACTOR");
	if (heightfactor_s != NULL)
		heightfactor = atoi(heightfactor_s);


	//parse command line arguments
	for (i = 1; argv[i]; i++) {
		if(!strcmp(argv[i], "-v")) {
			die("svkbd-"VERSION", © 2006-2020 svkbd engineers,"
@@ -884,11 +926,22 @@ main(int argc, char *argv[]) {
			if(i >= argc - 1)
				continue;
			if (layer_names_list == NULL) layer_names_list = malloc(128);
			strcpy(layer_names_list, argv[i+1]);
			strcpy(layer_names_list, argv[++i]);
		} 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);
		}
	}

	init_layers(layer_names_list);
	init_layers(layer_names_list, initial_layer_name);

	if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
		fprintf(stderr, "warning: no locale support\n");
--
2.27.0
Details
Message ID
<c3e71bd1-29e0-4c47-845e-d6cbaefaf5bd@www.fastmail.com>
In-Reply-To
<20200718122732.1192398-1-proycon@anaproy.nl> (view parent)
DKIM signature
missing
Download raw message
On Sat, Jul 18, 2020, at 7:27 AM, Maarten van Gompel wrote:
> This is version 6 of my svkbd patch, and I'm nearing completion on all
> the stuff I wanted to implement. I decided to split this patch into 4
> logical units, making for a nicer commit history when merged,
> all four together constitute all the work I did on svkbd
> and need to be applied in succession.
> 
> See the last patch in the series (4/4) for comments on what I changed since
> v5.
> 
> ---
>  LICENSE       |   1 +
>  Makefile      |   2 +-
>  config.def.h  |  17 +-
>  config.mk     |  13 +-
>  drw.c         | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  drw.h         |  51 ++++++
>  layout.sxmo.h |  38 ++---
>  svkbd.c       | 202 ++++++++----------------
>  util.c        |  35 +++++
>  util.h        |   8 +
>  10 files changed, 611 insertions(+), 173 deletions(-)
>  create mode 100644 drw.c
>  create mode 100644 drw.h
>  create mode 100644 util.c
>  create mode 100644 util.h
> 
> diff --git a/LICENSE b/LICENSE
> index 51864d7..0ccc34f 100644
> --- a/LICENSE
> +++ b/LICENSE
> @@ -2,6 +2,7 @@ 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"),
> diff --git a/Makefile b/Makefile
> index c34b112..ea44b05 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -3,7 +3,7 @@
> 
>  include config.mk
> 
> -SRC = svkbd.c
> +SRC = drw.c svkbd.c util.c
> 
>  all: options svkbd-${LAYOUT}
> 
> diff --git a/config.def.h b/config.def.h
> index 5011f40..f748b93 100644
> --- a/config.def.h
> +++ b/config.def.h
> @@ -1,9 +1,12 @@
>  static const Bool wmborder = True;
> -static const char font[] = "10x20";
> -static const char normbgcolor[] = "#cccccc";
> -static const char normfgcolor[] = "#000000";
> -static const char pressbgcolor[] = "#0000cc";
> -static const char pressfgcolor[] = "#ffffff";
> -static const char highlightbgcolor[] = "#0000cc";
> -static const char highlightfgcolor[] = "#ffffff";
> +static int fontsize = 16;
> +static const char *fonts[] = {
> +	"monospace:size=16"
> +};
> +static const char *colors[SchemeLast][2] = {
> +	/*     fg         bg       */
> +	[SchemeNorm] = { "#58a7c6", "#14313d" },
> +	[SchemePress] = { "#ffffff", "#005577" },
> +	[SchemeHighlight] = { "#58a7c6", "#005577" },
> +};
> 
> diff --git a/config.mk b/config.mk
> index fbb721e..6ee4520 100644
> --- a/config.mk
> +++ b/config.mk
> @@ -1,7 +1,7 @@
>  # svkbd version
>  VERSION = 0.1
> 
> -LAYOUT ?= en
> +LAYOUT ?= sxmo
> 
>  # Customize below to fit your system
> 
> @@ -12,13 +12,16 @@ MANPREFIX = ${PREFIX}/share/man
>  X11INC = /usr/X11R6/include
>  X11LIB = /usr/X11R6/lib
> 
> +# Xinerama, comment if you don't want it
> +XINERAMALIBS = -L${X11LIB} -lXinerama
> +XINERAMAFLAGS = -DXINERAMA
> +
>  # includes and libs
> -INCS = -I. -I./layouts -I/usr/include -I${X11INC}
> -LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXtst
> +INCS = -I. -I./layouts -I/usr/include -I${X11INC} 
> -I/usr/include/freetype2
> +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lXtst -lfontconfig -lXft 
> ${XINERAMALIBS}
> 
>  # flags
> -CPPFLAGS = -DVERSION=\"${VERSION}\" \
> -	   ${XINERAMAFLAGS}
> +CPPFLAGS = -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
>  CFLAGS = -g -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
>  LDFLAGS = -g ${LIBS}
> 
> diff --git a/drw.c b/drw.c
> new file mode 100644
> index 0000000..963cca7
> --- /dev/null
> +++ b/drw.c
> @@ -0,0 +1,417 @@
> +/* 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;
> +}
> diff --git a/drw.h b/drw.h
> new file mode 100644
> index 0000000..50d9208
> --- /dev/null
> +++ b/drw.h
> @@ -0,0 +1,51 @@
> +/* 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);
> diff --git a/layout.sxmo.h b/layout.sxmo.h
> index 52ce781..1ee74bc 100644
> --- a/layout.sxmo.h
> +++ b/layout.sxmo.h
> @@ -37,20 +37,20 @@ static Key keys_en[40] = {
>          { 0, XK_m, 1 },
>          /*{ "/?", XK_slash, 1 },*/
>          { "Tab", XK_Tab, 1 },
> -        { "<-", XK_BackSpace, 2 },
> +        { "⇍ Bksp", XK_BackSpace, 2 },
> 
>          { 0 }, /* New row */
> -        { "Layer 2", XK_Cancel, 1},
> -        { "Shift", XK_Shift_L, 1 },
> +        { "↺", XK_Cancel, 1},
> +        { "Shft", XK_Shift_L, 1 },
>          /*{ "L", XK_Left, 1 },*/
> -        { "D", XK_Down, 1 },
> -        { "U", XK_Up, 1 },
> +        { "↓", XK_Down, 1 },
> +        { "↑", XK_Up, 1 },
>          /*{ "R", XK_Right, 1 },*/
>          { "", XK_space, 2 },
>          { "Esc", XK_Escape, 1 },
>          { "Ctrl", XK_Control_L, 1 },
>          /*{ "Alt", XK_Alt_L, 1 },*/
> -        { "Enter", XK_Return, 2 },
> +        { "↲ Enter", XK_Return, 2 },
>  };
> 
>  static Key keys_symbols[40] = {
> @@ -80,28 +80,28 @@ static Key keys_symbols[40] = {
> 
>    { 0 }, /* New row */
> 
> -  { " ", XK_Shift_L|XK_bar, 1 },
> -  { " ", XK_Shift_L|XK_bar, 1 },
> -  { "L", XK_Left, 1 },
> -  { "R", XK_Right, 1 },
> -  { " ", XK_Shift_L|XK_bar, 1 },
> -  { " ", XK_Shift_L|XK_bar, 1 },
> -  { " ", XK_Shift_L|XK_bar, 1 },
> +  { "", XK_Shift_L|XK_bar, 1 },
> +  { "⇤", XK_Home, 1 },
> +  { "←", XK_Left, 1 },
> +  { "→", XK_Right, 1 },
> +  { "⇥", XK_End, 1 },
> +  { "⇊", XK_Next, 1 },
> +  { "⇈", XK_Prior, 1 },
>    { "Tab", XK_Tab, 1 },
> -  { "<-", XK_BackSpace, 2 },
> +  { "⇍ Bksp", XK_BackSpace, 2 },
> 
>    { 0 }, /* New row */
> -  { "Layer 1", XK_Cancel, 1},
> -  { "Shift", XK_Shift_L, 1 },
> +  { "↺", XK_Cancel, 1},
> +  { "Shft", XK_Shift_L, 1 },
>    /*{ "L", XK_Left, 1 },*/
> -  { "D", XK_Down, 1 },
> -  { "U", XK_Up, 1 },
> +  { "↓", XK_Down, 1 },
> +  { "↑", XK_Up, 1 },
>    /*{ "R", XK_Right, 1 },*/
>    { "", XK_space, 2 },
>    { "Esc", XK_Escape, 1 },
>    { "Ctrl", XK_Control_L, 1 },
>    /*{ "Alt", XK_Alt_L, 1 },*/
> -  { "Enter", XK_Return, 2 },
> +  { "↲ Enter", XK_Return, 2 },
>  };
> 
>  Buttonmod buttonmods[] = {
> diff --git a/svkbd.c b/svkbd.c
> index 2076fb7..de9e92a 100644
> --- a/svkbd.c
> +++ b/svkbd.c
> @@ -13,38 +13,30 @@
>  #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 <signal.h>
>  #include <sys/select.h>
> 
> +#include "drw.h"
> +#include "util.h"
> +
> +
> 
>  /* macros */
> -#define MAX(a, b)       ((a) > (b) ? (a) : (b))
>  #define LENGTH(x)       (sizeof x / sizeof x[0])
> +#define TEXTW(X)        (drw_fontset_getwidth(drw, (X)))
> 
>  /* enums */
> -enum { ColFG, ColBG, ColLast };
> +enum { SchemeNorm, SchemePress, SchemeHighlight, SchemeLast };
>  enum { NetWMWindowType, NetLast };
> 
>  /* typedefs */
>  typedef unsigned int uint;
>  typedef unsigned long ulong;
> 
> -typedef struct {
> -	ulong norm[ColLast];
> -	ulong press[ColLast];
> -	ulong high[ColLast];
> -
> -	Drawable drawable;
> -	GC gc;
> -	struct {
> -		int ascent;
> -		int descent;
> -		int height;
> -		XFontSet set;
> -		XFontStruct *xfont;
> -	} font;
> -} DC; /* draw context */
> -
>  typedef struct {
>  	char *label;
>  	KeySym keysym;
> @@ -66,18 +58,15 @@ static void buttonrelease(XEvent *e);
>  static void cleanup(void);
>  static void configurenotify(XEvent *e);
>  static void countrows();
> -static void die(const char *errstr, ...);
>  static void drawkeyboard(void);
>  static void drawkey(Key *k);
>  static void expose(XEvent *e);
>  static Key *findkey(int x, int y);
> -static ulong getcolor(const char *colstr);
> -static void initfont(const char *fontstr);
>  static void leavenotify(XEvent *e);
>  static void press(Key *k, KeySym mod);
>  static void run(void);
>  static void setup(void);
> -static int textnw(const char *text, uint len);
> +static void togglelayer();
>  static void unpress(Key *k, KeySym mod);
>  static void updatekeys();
> 
> @@ -93,8 +82,9 @@ static void (*handler[LASTEvent]) (XEvent *) = {
>  };
>  static Atom netatom[NetLast];
>  static Display *dpy;
> -static DC dc;
> +static Drw *drw;
>  static Window root, win;
> +static Clr* scheme[SchemeLast];
>  static Bool running = True, isdock = False;
>  static KeySym pressedmod = 0;
>  static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0;
> @@ -200,14 +190,12 @@ cleanup(void) {
>  			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
>  		}
>  	}
> -	XSync(dpy, False);
> 
> -	if(dc.font.set)
> -		XFreeFontSet(dpy, dc.font.set);
> -	else
> -		XFreeFont(dpy, dc.font.xfont);
> -	XFreePixmap(dpy, dc.drawable);
> -	XFreeGC(dpy, dc.gc);
> +	for (i = 0; i < SchemeLast; i++)
> +		free(scheme[i]);
> +	drw_free(drw);
> +	drw_sync(drw);
> +	XSync(dpy, False);
>  	XDestroyWindow(dpy, win);
>  	XSync(dpy, False);
>  	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
> @@ -220,9 +208,7 @@ configurenotify(XEvent *e) {
>  	if(ev->window == win && (ev->width != ww || ev->height != wh)) {
>  		ww = ev->width;
>  		wh = ev->height;
> -		XFreePixmap(dpy, dc.drawable);
> -		dc.drawable = XCreatePixmap(dpy, root, ww, wh,
> -				DefaultDepth(dpy, screen));
> +		drw_resize(drw, ww, wh);
>  		updatekeys();
>  	}
>  }
> @@ -237,15 +223,6 @@ countrows() {
>  	}
>  }
> 
> -void
> -die(const char *errstr, ...) {
> -	va_list ap;
> -
> -	va_start(ap, errstr);
> -	vfprintf(stderr, errstr, ap);
> -	va_end(ap);
> -	exit(EXIT_FAILURE);
> -}
> 
>  void
>  drawkeyboard(void) {
> @@ -259,42 +236,29 @@ drawkeyboard(void) {
> 
>  void
>  drawkey(Key *k) {
> -	int x, y, h, len;
> -	XRectangle r = { k->x, k->y, k->w, k->h};
> +	int x, y, w, h;
>  	const char *l;
> -	ulong *col;
> 
>  	if(k->pressed)
> -		col = dc.press;
> +		drw_setscheme(drw, scheme[SchemePress]);
>  	else if(k->highlighted)
> -		col = dc.high;
> +		drw_setscheme(drw, scheme[SchemeHighlight]);
>  	else
> -		col = dc.norm;
> -
> -	XSetForeground(dpy, dc.gc, col[ColBG]);
> -	XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
> -	XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
> -	r.height -= 1;
> -	r.width -= 1;
> -	XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
> -	XSetForeground(dpy, dc.gc, col[ColFG]);
> +		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->label) {
>  		l = k->label;
>  	} else {
>  		l = XKeysymToString(k->keysym);
>  	}
> -	len = strlen(l);
> -	h = dc.font.ascent + dc.font.descent;
> -	y = k->y + (k->h / 2) - (h / 2) + dc.font.ascent;
> -	x = k->x + (k->w / 2) - (textnw(l, len) / 2);
> -	if(dc.font.set) {
> -		XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l,
> -				len);
> -	} else {
> -		XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len);
> -	}
> -	XCopyArea(dpy, dc.drawable, win, dc.gc, k->x, k->y, k->w, k->h,
> -			k->x, k->y);
> +	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
> @@ -319,52 +283,7 @@ findkey(int x, int y) {
>  	return NULL;
>  }
> 
> -ulong
> -getcolor(const char *colstr) {
> -	Colormap cmap = DefaultColormap(dpy, screen);
> -	XColor color;
> -
> -	if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
> -		die("error, cannot allocate color '%s'\n", colstr);
> -	return color.pixel;
> -}
> 
> -void
> -initfont(const char *fontstr) {
> -	char *def, **missing;
> -	int i, n;
> -
> -	missing = NULL;
> -	if(dc.font.set)
> -		XFreeFontSet(dpy, dc.font.set);
> -	dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
> -	if(missing) {
> -		while(n--)
> -			fprintf(stderr, "svkbd: missing fontset: %s\n", missing[n]);
> -		XFreeStringList(missing);
> -	}
> -	if(dc.font.set) {
> -		XFontStruct **xfonts;
> -		char **font_names;
> -		dc.font.ascent = dc.font.descent = 0;
> -		n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
> -		for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
> -			dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
> -			dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
> -			xfonts++;
> -		}
> -	} else {
> -		if(dc.font.xfont)
> -			XFreeFont(dpy, dc.font.xfont);
> -		dc.font.xfont = NULL;
> -		if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
> -		&& !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
> -			die("error, cannot load font: '%s'\n", fontstr);
> -		dc.font.ascent = dc.font.xfont->ascent;
> -		dc.font.descent = dc.font.xfont->descent;
> -	}
> -	dc.font.height = dc.font.ascent + dc.font.descent;
> -}
> 
>  void
>  leavenotify(XEvent *e) {
> @@ -484,15 +403,36 @@ setup(void) {
>  	XSizeHints *sizeh = NULL;
>  	XClassHint *ch;
>  	Atom atype = -1;
> -	int i, sh, sw;
> +	int i, j, sh, sw;
>  	XWMHints *wmh;
> 
> +	#if XINERAMA
> +	XineramaScreenInfo *info = NULL;
> +	#endif
> +
>  	/* init screen */
>  	screen = DefaultScreen(dpy);
>  	root = RootWindow(dpy, screen);
> -	sw = DisplayWidth(dpy, screen);
> -	sh = DisplayHeight(dpy, screen);
> -	initfont(font);
> +	#if 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]);
> +
> +	/* init appearance */
> +	for (j = 0; j < SchemeLast; j++)
> +		scheme[j] = drw_scm_create(drw, colors[j], 2);
> 
>  	/* init atoms */
>  	if(isdock) {
> @@ -517,23 +457,12 @@ setup(void) {
>  	if(wy < 0)
>  		wy = sh + wy - wh;
> 
> -	dc.norm[ColBG] = getcolor(normbgcolor);
> -	dc.norm[ColFG] = getcolor(normfgcolor);
> -	dc.press[ColBG] = getcolor(pressbgcolor);
> -	dc.press[ColFG] = getcolor(pressfgcolor);
> -	dc.high[ColBG] = getcolor(highlightbgcolor);
> -	dc.high[ColFG] = getcolor(highlightfgcolor);
> -	dc.drawable = XCreatePixmap(dpy, root, ww, wh,
> -			DefaultDepth(dpy, screen));
> -	dc.gc = XCreateGC(dpy, root, 0, 0);
> -	if(!dc.font.set)
> -		XSetFont(dpy, dc.gc, dc.font.xfont->fid);
>  	for(i = 0; i < LENGTH(keys); i++)
>  		keys[i].pressed = 0;
> 
>  	wa.override_redirect = !wmborder;
> -	wa.border_pixel = dc.norm[ColFG];
> -	wa.background_pixel = dc.norm[ColBG];
> +	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 |
> @@ -572,20 +501,11 @@ setup(void) {
>  	}
> 
>  	XMapRaised(dpy, win);
> +	drw_resize(drw, ww, wh);
>  	updatekeys();
>  	drawkeyboard();
>  }
> 
> -int
> -textnw(const char *text, uint len) {
> -	XRectangle r;
> -
> -	if(dc.font.set) {
> -		XmbTextExtents(dc.font.set, text, len, NULL, &r);
> -		return r.width;
> -	}
> -	return XTextWidth(dc.font.xfont, text, len);
> -}
> 
>  void
>  updatekeys() {
> @@ -639,7 +559,7 @@ main(int argc, char *argv[]) {
>  	signal(SIGTERM, sigterm);
>  	for (i = 1; argv[i]; i++) {
>  		if(!strcmp(argv[i], "-v")) {
> -			die("svkbd-"VERSION", © 2006-2016 svkbd engineers,"
> +			die("svkbd-"VERSION", © 2006-2020 svkbd engineers,"
>  				       " see LICENSE for details\n");
>  		} else if(!strcmp(argv[i], "-d")) {
>  			isdock = True;
> diff --git a/util.c b/util.c
> new file mode 100644
> index 0000000..fe044fc
> --- /dev/null
> +++ b/util.c
> @@ -0,0 +1,35 @@
> +/* 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);
> +}
> diff --git a/util.h b/util.h
> new file mode 100644
> index 0000000..f633b51
> --- /dev/null
> +++ b/util.h
> @@ -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);
> --
> 2.27.0
>

So I've merged the 1st of your 4 patches to provide Xft support out
of the box along with a larger font for sxmo-svkbd. I also applied
a one line fix to restore SIGTERM handling ontop of that patch; and
restored the original colorscheme as I think its better to have something
high-contrast. Bumped the sxmo-svkbd package to 1.0.5 and should be up
in the Alpine repository now.

I do want to keep the surface-area / functionality of sxmo-svkbd simple,
as is the original svkbd, so that the source is easy to understand and
hack on. So I'm not completly aligned with adding more logic for overlays
/ interefering with the way key handling works. I hope this is ok that I
only partially applied your work. I think the Xft stuff is a huge gain;
thank you for sending. For the dialer layout and switching layouts via
CLI flag that looks good too; and I want to take a closer look at this
and incorporate this soon as well.

I think it might not be a bad idea to reference $KEYBOARD or $SXMO_KEYBOARD 
in sxmo-utils scripts and set it to sxmo-svkbd by default.  This is
probably the prefered way to support users who want to customize their
keyboard further - as ofcourse given different languages etc. users will
have different needs for their keyboard.
Details
Message ID
<20200724130415.guc6ro4x6qhfcvvj@worker.anaproy.lxd>
In-Reply-To
<c3e71bd1-29e0-4c47-845e-d6cbaefaf5bd@www.fastmail.com> (view parent)
DKIM signature
missing
Download raw message
> So I've merged the 1st of your 4 patches to provide Xft support out
> of the box along with a larger font for sxmo-svkbd. I also applied
> a one line fix to restore SIGTERM handling ontop of that patch; and
> restored the original colorscheme as I think its better to have something
> high-contrast. Bumped the sxmo-svkbd package to 1.0.5 and should be up
> in the Alpine repository now.

Great! I did improve the colour scheme in one of the later patches btw, it
should be pretty high contrast, though based on a dark theme rather than
a light one as the original was, but that's a matter of personal
preference.

> I do want to keep the surface-area / functionality of sxmo-svkbd simple,
> as is the original svkbd, so that the source is easy to understand and
> hack on. So I'm not completly aligned with adding more logic for overlays
> / interefering with the way key handling works. I hope this is ok that I
> only partially applied your work.

I'll take the rest of it upstream and see what they think. Although
layers and overlays add some complexity, I do think it's unavoidable if
you don't want to be stuck with only basic ASCII-like input. If they
don't want it in the base either, they could add it as a patch.

> I think the Xft stuff is a huge gain;
> thank you for sending. For the dialer layout and switching layouts via
> CLI flag that looks good too; and I want to take a closer look at this
> and incorporate this soon as well.
>
> I think it might not be a bad idea to reference $KEYBOARD or $SXMO_KEYBOARD
> in sxmo-utils scripts and set it to sxmo-svkbd by default.  This is
> probably the prefered way to support users who want to customize their
> keyboard further - as ofcourse given different languages etc. users will
> have different needs for their keyboard.

Yes, I completely agree. Using a $KEYBOARD environment variable would be
a very good improvement, then people can use whatever virtual keyboard
they please. Still though, this doesn't solve the issue of switching
between multiple keyboard on the fly like the way the layers/overlays
do.

Regards,

--

Maarten van Gompel

proycon@anaproy.nl
https://proycon.anaproy.nl
https://github.com/proycon

GnuPG key:  0x39FE11201A31555C
XMPP:       proycon@anaproy.nl       Matrix: @proycon:matrix.anaproy.nl
Telegram:   proycon                  IRC: proycon (freenode)
Discord:    proycon#8272
Mastodon:   https://social.anaproy.nl/@proycon   (@proycon@social.anaproy.nl)
Twitter:    https://twitter.com/proycon
Bitcoin:    1BRptZsKQtqRGSZ5qKbX2azbfiygHxJPsd
Reply to thread Export thread (mbox)