~mil/sxmo-devel

v3 sxmo-svkbd: Xft+Xinerama support, added+adapted layouts, multiple layers and overlay functionality v1 SUPERSEDED

Maarten van Gompel: 1
 Xft+Xinerama support, added+adapted layouts, multiple layers and overlay functionality

 10 files changed, 1177 insertions(+), 213 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~mil/sxmo-devel/patches/11444/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH v3 sxmo-svkbd] Xft+Xinerama support, added+adapted layouts, multiple layers and overlay functionality Export this patch

This is another major update over the previous v2 patch (and including
it). In addition to the functionality already in v2, this one adds:

 * Overlay support, you can now long-press (>1s) certain keys and
   an overlay will be put on the keyboard allowing you to enter variants
   of the pressed letter. This is used e.g. for input of diacritics or
   emojis. The overlay dissappears again as soon as a key is pressed.
   (note: this functionality inevitably affects the ability to press
   and without release hold certain keys and have them outputted on repeat)
 * Multiple layers, rather than having two layers (which was a mere
   toggle), svkbd now support as many layers as you want and cycles
   between them rather than toggling. (note: All layers must have the same
   number of keys). In addition the the symbols layer, I also added a
   functions layers, which holds function keys (Fx..Fn, more arrows,
   brightness controls, etc..)
 * One modifier button (Esc) is now cyclable using long press, i.e. the
   key switches function after long press: you can
   cycle between Esc, Alt and AltGr currently (configurable of course).
   (note: This requires a patched dwm
   that does not rely on alt+mouse to move/resize windows! will submit
   patch to sxmo-dwm for this)
 * Bigger font size and contrast again

Note: svkbd requires a default US XKB keymap to be loaded
(setxkbmap us), otherwise behaviour may differ from what is on the
virtual keycaps.

(There may still be some minor issues unresolved in this build, feel
free to report them on the tracker)

---
 LICENSE       |   1 +
 Makefile      |   2 +-
 config.def.h  |  18 +-
 config.mk     |  13 +-
 drw.c         | 417 ++++++++++++++++++++++++++++++++++++++++
 drw.h         |  51 +++++
 layout.sxmo.h | 323 ++++++++++++++++++++++++++++---
 svkbd.c       | 522 +++++++++++++++++++++++++++++++++-----------------
 util.c        |  35 ++++
 util.h        |   8 +
 10 files changed, 1177 insertions(+), 213 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..fe404b4 100644
--- a/config.def.h
+++ b/config.def.h
@@ -1,9 +1,13 @@
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 = 20;
static double overlay_delay = 1.0;
static const char *fonts[] = {
	"DejaVu Sans:bold:size=20"
};
static const char *colors[SchemeLast][2] = {
	/*     fg         bg       */
	[SchemeNorm] = { "#ffffff", "#14313d" },
	[SchemePress] = { "#ffffff", "#000000" },
	[SchemeHighlight] = { "#58a7c6", "#005577" },
};

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..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,23 +38,230 @@ 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] = {
#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,30 +288,93 @@ 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 },
  { "☺", 0x101f642, 1 },
  { "⇤", XK_Home, 1 },
  { "←", XK_Left, 1 },
  { "→", XK_Right, 1 },
  { "⇥", XK_End, 1 },
  { "⇊", XK_Next, 1 },
  { ";:", XK_colon, 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 },
  /*{ "L", XK_Left, 1 },*/
  { "D", XK_Down, 1 },
  { "U", XK_Up, 1 },
  /*{ "R", XK_Right, 1 },*/
  { "↺", 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 },
  /*{ "Alt", XK_Alt_L, 1 },*/
  { "Enter", XK_Return, 2 },
  { "↲ 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 },
  { "⇥", XK_End, 1 },
  { "⇊", XK_Next, 1 },
  { "⇈", XK_Prior, 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 },
};


#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 2076fb7..aaee87a 100644
--- a/svkbd.c
+++ b/svkbd.c
@@ -8,43 +8,40 @@
#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>
#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 <time.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.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)))
#define STRINGTOKEYSYM(X)			(XStringToKeySym(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 +63,22 @@ 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 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 int textnw(const char *text, uint len);
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();

@@ -93,15 +94,25 @@ 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 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 */
@@ -200,14 +211,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 +229,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 +244,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 +257,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,89 +304,127 @@ 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;
int
hasoverlay(KeySym keysym) {
	int begin, i;
	begin = 0;
	for(i = 0; i < OVERLAYS; i++) {
		if(overlay[i].keysym == XK_Cancel) {
			begin = i+1;
		} else if(overlay[i].keysym == keysym) {
			return begin+1;
		}
	}
	return -1;
}

void
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++;
int
iscyclemod(KeySym keysym) {
	int i;
	for(i = 0; i < CYCLEMODS; i++) {
		if(cyclemods[i].keysym == keysym) {
			return i;
		}
	} 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;
	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;
@@ -409,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;
@@ -418,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;
@@ -430,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
@@ -454,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);
@@ -473,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);
	}
}

@@ -484,15 +563,60 @@ 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]);

	//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++)
		scheme[j] = drw_scm_create(drw, colors[j], 2);

	/* init atoms */
	if(isdock) {
@@ -517,27 +641,16 @@ 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 |
			    CWBackingPixel, &wa);
			CopyFromParent, CopyFromParent, CopyFromParent,
			CWOverrideRedirect | CWBorderPixel |
			CWBackingPixel, &wa);
	XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
			ButtonPressMask|ExposureMask|LeaveWindowMask|
			PointerMotionMask);
@@ -559,6 +672,7 @@ setup(void) {
	XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh,
			ch);

	XFree(keysyms);
	XFree(ch);
	XFree(wmh);
	XFree(str.value);
@@ -572,20 +686,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() {
@@ -611,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();
	baselayer = !baselayer;
}

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();
	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)
{
@@ -639,7 +810,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;
@@ -662,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]);
		}
@@ -677,4 +852,3 @@ main(int argc, char *argv[]) {
	XCloseDisplay(dpy);
	return 0;
}

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
Fantastic!


On Fri, 3 Jul 2020 at 20:42, Maarten van Gompel <proycon@anaproy.nl> wrote: