Added overlays (appearing on long press), multiple layer support (rather than just a toggle) with new layers, style changes - svkbd - simple virtual keyboard
  HTML git clone git://git.suckless.org/svkbd
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 48994f125e2fdaee15f0724e4f97c24443f9eb96
   DIR parent b488ae6410567d64a48eb676f7038c68f3eb0cc2
  HTML Author: Maarten van Gompel <proycon@anaproy.nl>
       Date:   Sun,  2 Aug 2020 15:46:14 +0200
       
       Added overlays (appearing on long press), multiple layer support (rather than just a toggle) with new layers, style changes
       
       Diffstat:
         M config.def.h                        |       9 +++++----
         M layout.sxmo.h                       |     291 +++++++++++++++++++++++++++++--
         M svkbd.c                             |     338 +++++++++++++++++++++++++++----
       
       3 files changed, 582 insertions(+), 56 deletions(-)
       ---
   DIR diff --git a/config.def.h b/config.def.h
       @@ -1,11 +1,12 @@
        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" },
        };
   DIR diff --git 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 },
   DIR diff --git 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 */
       @@ -287,43 +308,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;
       @@ -331,7 +435,7 @@ unpress(Key *k, KeySym mod) {
                if(k != NULL) {
                        switch(k->keysym) {
                        case XK_Cancel:
       -                        togglelayer();
       +                        cyclelayer();
                                break;
                        case XK_Break:
                          running = False;
       @@ -340,11 +444,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;
       @@ -352,22 +487,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
       @@ -376,11 +515,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);
       @@ -395,7 +537,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);
                }
        }
        
       @@ -428,10 +588,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++)
       @@ -467,9 +651,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);
       @@ -491,6 +675,7 @@ setup(void) {
                XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh,
                                ch);
        
       +        XFree(keysyms);
                XFree(ch);
                XFree(wmh);
                XFree(str.value);
       @@ -534,18 +719,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)
        {
       @@ -585,6 +836,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]);
                        }
       @@ -600,4 +855,3 @@ main(int argc, char *argv[]) {
                XCloseDisplay(dpy);
                return 0;
        }
       -