Re: [hackers] [PATCH 5/6] Added a dialer/numpad keyboard, added the ability to handle layouts with less keys/different layouts. Extra configurability: select layout on startup, customisable height factor.

From: Hiltjo Posthuma <hiltjo_AT_codemadness.org>
Date: Sat, 25 Jul 2020 01:38:33 +0200

On Fri, Jul 24, 2020 at 09:49:59PM +0200, Maarten van Gompel wrote:
> ---
> README.md | 87 ++++++++++++++++++++++++++++++++++++++++
> config.def.h | 1 +
> layout.sxmo.h | 39 +++++++++++++++++-
> svkbd.c | 107 +++++++++++++++++++++++++++++++++++++-------------
> 4 files changed, 205 insertions(+), 29 deletions(-)
> create mode 100644 README.md
>
> diff --git a/README.md b/README.md
> new file mode 100644
> index 0000000..fd0e4d9
> --- /dev/null
> +++ b/README.md
> _AT_@ -0,0 +1,87 @@
> +SVKBD: Simple Virtual Keyboard
> +=================================
> +
> +This is a simple virtual keyboard, intended to be used in environments,
> +where no keyboard is available.
> +
> +Installation
> +------------
> +
> + $ make
> + $ make install
> +
> +This will create by default `svkbd-intl`, which is svkbd using an international
> +layout with multiple layers and overlays, and optimised for mobile devices.
> +
> +You can create svkbd for additional layouts by doing:
> +
> + $ make LAYOUT=$layout
> +
> +This will take the file `layout.$layout.h` and create `svkbd-$layout`.
> +`make install` will then pick up the new file and install it accordingly.
> +
> +Layouts
> +---------
> +
> +The following layouts are available:
> +
> +* **Mobile Layouts:**
> + * ``intl`` - A small international layout optimised for mobile devices. This layout consists of multiple layers which
> + can be switched on the fly, and overlays that appear on long-press of certain keys, adding input ability for
> + diacritics and other variants, as well as some emoji. The layers are:
> + * a basic qwerty layer
> + * a layer for numeric input, arrows, and punctuation
> + * a layer for function keys, media keys, and arrows
> + * a cyrillic layer (ЙЦУКЕН)
> + * a dialer/numeric layer
> + * ``sxmo`` - This is the original English layout for [sxmo](https://sr.ht/~mil/Sxmo) with only a qwerty layer and numeric/punctuation layer.
> +* **Traditional layouts**:
> + * ``en`` - An english layout without layers (QWERTY)
> + * ``de`` - A german layout (QWERTZ)
> + * ``ru`` - A russian layout (ЙЦУКЕН)
> + * ``sh`` - A serbo-croatian layout using latin script (QWERTZ)
> +
> +Usage
> +-----
> +
> + $ svkbd-intl
> +
> +This will open svkbd at the bottom of the screen, showing the default
> +international layout.
> +
> + $ svkbd-intl -d
> +
> +This tells svkbd to announce itself being a dock window, which then
> +is managed differently between different window managers. If using dwm
> +and the dock patch, then this will make svkbd being managed by dwm and
> +some space of the screen being reserved for it.
> +
> + $ svkbd-intl -g 400x200+1+1
> +
> +This will start svkbd-intl with a size of 400x200 and at the upper left
> +window corner.
> +
> +For layouts that consist of multiple layers, you can enable layers on program start through either the ``-l`` flag or
> +through the ``SVKBD_LAYERS`` environment variable. They both take a comma separated list of layer names (as defined in
> +your ``layout.*.h``). Use the ``↺`` button in the bottom-left to cycle through all the layers.
> +
> +Some layouts come with overlays that will show when certain keys are hold pressed for a longer time. For
> +example, a long press on the ``a`` key will enable an overview showing all kinds of diacritic combinations for ``a``.
> +
> +Overlay functionality interferes with the ability to hold a key and have it outputted repeatedly. You can disable
> +overlay functionality with the ``-O`` flag or by setting the environment variable ``SVKBD_ENABLEOVERLAYS=0``. There is
> +also a key on the function layer of the keyboard itself to enable/disable this behaviour on the fly. Its label shows
> +``≅`` when the overlay functionality is enabled and ``≇`` when not.
> +
> +Notes
> +---------
> +
> +This virtual keyboard does not actually modify the X keyboard layout, the ``intl``, ``sxmo`` and ``en`` layouts simply rely on a standard US QWERTY layout (setxkbmap us) being activated, the other layouts (``de``, ``ru``, ``sh``) require their respective XKB keymaps to be active.
> +
> +If you use another XKB layout you will get unpredictable output that does not match the labels on the virtual keycaps!
> +
> +Repository
> +----------
> +
> + git clone https://git.suckless.org/svkbd
> +
> diff --git a/config.def.h b/config.def.h
> index 42d0c38..df37ff9 100644
> --- a/config.def.h
> +++ b/config.def.h
> _AT_@ -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
> _AT_@ -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 },
> _AT_@ -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_AT_", 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",
> };
>
> _AT_@ -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 746af77..ae0267e 100644
> --- a/svkbd.c
> +++ b/svkbd.c
> _AT_@ -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);
> _AT_@ -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();
> _AT_@ -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;
>
> _AT_@ -130,7 +133,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
> _AT_@ -221,7 +224,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);
> }
> }
> _AT_@ -252,18 +255,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]);
> }
> _AT_@ -314,7 +333,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) {
> _AT_@ -374,7 +393,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);
> }
> _AT_@ -385,7 +404,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);
> }
> _AT_@ -456,7 +475,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);
> }
> _AT_@ -483,7 +502,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;
> _AT_@ -491,13 +510,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;
> _AT_@ -633,7 +652,7 @@ setup(void) {
> if(!ww)
> ww = sw;
> if(!wh)
> - wh = sh * rows / 32;
> + wh = sh * rows / heightfactor;
>
> if(!wx)
> wx = 0;
> _AT_@ -644,7 +663,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;
> _AT_@ -701,10 +720,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;
> _AT_@ -719,23 +738,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();
> }
> _AT_@ -748,7 +774,7 @@ togglelayer() {
> currentlayer = 1;
> }
> if (debug) { printf("Toggling layer %d\n", currentlayer); fflush(stdout); }
> - memcpy(&keys, layers[currentlayer], sizeof(keys_en));
> + setlayer();
> updatekeys();
> drawkeyboard();
> }
> _AT_@ -759,7 +785,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]);
> _AT_@ -803,21 +829,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;
> }
> _AT_@ -830,17 +867,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");
> _AT_@ -848,8 +887,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,"
> _AT_@ -887,11 +929,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);
> }
> }
>

If heightfactor <= 0 it should probably error out with some "invalid argument"
message (to prevent divide by zero later etc).


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

-- 
Kind regards,
Hiltjo
Received on Sat Jul 25 2020 - 01:38:33 CEST

This archive was generated by hypermail 2.3.0 : Sat Jul 25 2020 - 01:48:33 CEST