--- 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 @@ -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 @@ -1,6 +1,7 @@ static const Bool wmborder = True; static int fontsize = 20; static double overlay_delay = 1.0; +static int heightfactor = 16; //one row of keys takes up 1/x of the screen height static const char *fonts[] = { "DejaVu Sans:bold:size=20" }; diff --git a/layout.sxmo.h b/layout.sxmo.h index f036fd6..6aafdb3 100644 --- a/layout.sxmo.h +++ b/layout.sxmo.h @@ -68,7 +68,7 @@ static Key overlay[OVERLAYS] = { { "æ", XK_ae }, { 0, XK_Cancel }, /* XK_Cancel signifies overlay boundary */ //-- - { 0, XK_e }, //Overlay for e + { 0, XK_e }, //Overlay for e (first item after boundary defines the trigger) //--- { "è", XK_egrave }, { "é", XK_eacute }, @@ -465,11 +465,45 @@ static Key keys_ru[KEYS] = { { "↲ Enter", XK_Return, 2 }, }; -#define LAYERS 4 +static Key keys_dialer[KEYS] = { + { "Esc", XK_Escape, 1 }, + { "1!", XK_1, 1 }, + { "2@", XK_2, 1 }, + { "3#", XK_3, 1 }, + { "⌫Bksp", XK_BackSpace, 2 }, + { 0 }, /* New row */ + + { "Shift", XK_Shift_L, 1 }, + { "4$", XK_4, 1 }, + { "5%", XK_5, 1 }, + { "6^", XK_6, 1 }, + { "-_", XK_minus, 1 }, + { ",<", XK_comma, 1 }, + { 0 }, /* New row */ + + { "abc", XK_Mode_switch, 1 }, + { "7&", XK_7, 1 }, + { "8*", XK_8, 1 }, + { "9(", XK_9, 1 }, + { "=+", XK_equal, 1 }, + { "/?", XK_slash, 1 }, + { 0 }, /* New row */ + + { "↺", XK_Cancel, 1}, + { "", XK_space, 1 }, + { "0)", XK_0, 1 }, + { ".>", XK_period, 1 }, + { "↲ Enter", XK_Return, 2}, + { 0 }, /* New row */ + { 0 }, /* Last item (double 0) */ +}; + +#define LAYERS 5 static char* layer_names[LAYERS] = { "en", "symbols", "functions", + "dialer", "ru", }; @@ -477,6 +511,7 @@ static Key* available_layers[LAYERS] = { keys_en, keys_symbols, keys_functions, + keys_dialer, keys_ru }; diff --git a/svkbd.c b/svkbd.c index 746af77..ae0267e 100644 --- a/svkbd.c +++ b/svkbd.c @@ -63,6 +63,7 @@ static void buttonrelease(XEvent *e); static void cleanup(void); static void configurenotify(XEvent *e); static void countrows(); +static int countkeys(Key *k); static void drawkeyboard(void); static void drawkey(Key *k); static void expose(XEvent *e); @@ -77,6 +78,7 @@ static void simulate_keyrelease(KeySym keysym); static void showoverlay(int idx); static void hideoverlay(); static void cyclelayer(); +static void setlayer(); static void togglelayer(); static void unpress(Key *k, KeySym mod); static void updatekeys(); @@ -109,6 +111,7 @@ static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0; static char *name = "svkbd"; static int debug = 0; static int numlayers = 0; +static int numkeys = 0; static KeySym ispressingkeysym; @@ -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 @@ -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); } } @@ -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]); } @@ -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) { @@ -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); } @@ -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); } @@ -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); } @@ -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; @@ -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; @@ -633,7 +652,7 @@ setup(void) { if(!ww) ww = sw; if(!wh) - wh = sh * rows / 32; + wh = sh * rows / heightfactor; if(!wx) wx = 0; @@ -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; @@ -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; @@ -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(); } @@ -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(); } @@ -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]); @@ -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; } @@ -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"); @@ -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," @@ -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); } } - 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
