From f3f4efd63fa35cabf5ffb819b32e62656f0fec6b Mon Sep 17 00:00:00 2001 From: zcake Date: Tue, 22 Dec 2020 13:51:39 +0800 Subject: [PATCH] uploading ... --- LICENSE | 23 ++ Makefile | 63 ++++ README.md | 87 +++++ config.def.h | 13 + config.h | 17 + config.mk | 25 ++ drw.c | 418 +++++++++++++++++++++ drw.h | 50 +++ drw.o | Bin 0 -> 11512 bytes layout.h | 168 +++++++++ svkbd | Bin 0 -> 52480 bytes svkbd-arrows | Bin 0 -> 41800 bytes svkbd.1 | 72 ++++ svkbd.c | 999 +++++++++++++++++++++++++++++++++++++++++++++++++++ svkbd.o | Bin 0 -> 43768 bytes util.c | 36 ++ util.h | 8 + util.o | Bin 0 -> 2512 bytes 18 files changed, 1979 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 config.def.h create mode 100644 config.h create mode 100644 config.mk create mode 100644 drw.c create mode 100644 drw.h create mode 100644 drw.o create mode 100644 layout.h create mode 100755 svkbd create mode 100755 svkbd-arrows create mode 100644 svkbd.1 create mode 100644 svkbd.c create mode 100644 svkbd.o create mode 100644 util.c create mode 100644 util.h create mode 100644 util.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ccc34f --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2011 Christoph Lohmann <20h@r-36.net> +© 2008-2011 Enno Boland +© 2020 Maarten van Gompel + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3a0dcd2 --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +# svkbd - simple virtual keyboard +# See LICENSE file for copyright and license details. +.POSIX: + +NAME = svkbd +VERSION = 0.2.2 + +include config.mk + +BIN = ${NAME} +SRC = drw.c ${NAME}.c util.c +OBJ = ${SRC:.c=.o} +MAN1 = ${NAME}.1 + +all: ${BIN} + +options: + @echo svkbd build options: + @echo "CFLAGS = ${SVKBD_CFLAGS}" + @echo "CPPLAGS = ${SVKBD_CPPFLAGS}" + @echo "LDFLAGS = ${SVKBD_LDFLAGS}" + @echo "CC = ${CC}" + +config.h: + cp config.def.h $@ + +.c.o: + ${CC} ${SVKBD_CFLAGS} ${SVKBD_CPPFLAGS} -c $< + +${OBJ}: config.h config.mk + +${BIN}: ${OBJ} + ${CC} -o ${BIN} ${OBJ} ${SVKBD_LDFLAGS} + +clean: + rm -f ${NAME}-?? ${NAME}-??.o ${OBJ} ${BIN} + +dist: + rm -rf "${NAME}-${VERSION}" + mkdir -p "${NAME}-${VERSION}" + cp LICENSE Makefile README.md config.def.h config.mk ${MAN1} \ + drw.h util.h ${SRC} ${NAME}-${VERSION} + for i in layout.*.h; \ + do \ + cp $$i ${NAME}-${VERSION}; \ + done + tar -cf - "${NAME}-${VERSION}" | \ + gzip -c > "${NAME}-${VERSION}.tar.gz" + rm -rf "${NAME}-${VERSION}" + +install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp ${NAME} ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME} + mkdir -p "${DESTDIR}${MANPREFIX}/man1" + sed "s/VERSION/${VERSION}/g" < ${MAN1} > ${DESTDIR}${MANPREFIX}/man1/${MAN1} + chmod 644 ${DESTDIR}${MANPREFIX}/man1/${MAN1} + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/${NAME} + rm -f ${DESTDIR}${MANPREFIX}/man1/${MAN1} + +.PHONY: all clean dist options install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd54e65 --- /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:** + * ``mobile-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 (ЙЦУКЕН based); the э key is moved to an overlay on е + * a dialer/numeric layer + * ``mobile-plain`` - This is a plain layout with only a qwerty layer and numeric/punctuation layer. It was + originally made for [sxmo](https://sr.ht/~mil/Sxmo/). +* **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-mobile-intl + +This will open svkbd at the bottom of the screen, showing the default +international layout. + + $ svkbd-mobile-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-mobile-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 ``mobile-intl``, ``mobile-plain`` 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 new file mode 100644 index 0000000..df37ff9 --- /dev/null +++ b/config.def.h @@ -0,0 +1,13 @@ +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" +}; +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#ffffff", "#14313d" }, + [SchemePress] = { "#ffffff", "#000000" }, + [SchemeHighlight] = { "#58a7c6", "#005577" }, +}; diff --git a/config.h b/config.h new file mode 100644 index 0000000..11bb5a4 --- /dev/null +++ b/config.h @@ -0,0 +1,17 @@ +static const Bool wmborder = True; +static const char *fonts[] = { +"FiraCode Nerd Font:style=Light:size=10:antialias=true:autohint=true", +"Chiron Sans HK Light:style=Light:size=10", +"OpenMoji:style=Color", +}; + +static int fontsize = 10; +static double overlay_delay = 1.0; +static int heightfactor = 30; //one row of keys takes up 1/x of the screen height +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#ff5555", "#282a36" }, + [SchemePress] = { "#50fa7b","#282a36" }, + [SchemeHighlight] = { "#ff79c6", "#282a36" }, + +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..d50217d --- /dev/null +++ b/config.mk @@ -0,0 +1,25 @@ +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +PKG_CONFIG = pkg-config + +# Xinerama, comment if you don't want it +XINERAMALIBS = -L${X11LIB} -lXinerama +XINERAMAFLAGS = -DXINERAMA + +# includes and libs +INCS = -I. -I./layouts -I${X11INC} \ + `$(PKG_CONFIG) --cflags fontconfig` \ + `$(PKG_CONFIG) --cflags freetype2` +LIBS = -L${X11LIB} -lX11 -lXtst -lXft ${XINERAMALIBS} \ + `$(PKG_CONFIG) --libs fontconfig` \ + `$(PKG_CONFIG) --libs freetype2` + +# use system flags +SVKBD_CFLAGS = ${CFLAGS} ${INCS} +SVKBD_LDFLAGS = ${LDFLAGS} ${LIBS} +SVKBD_CPPFLAGS = ${CPPFLAGS} -D_DEFAULT_SOURCE -DVERSION=\"VERSION\" ${XINERAMAFLAGS} -DLAYOUT=\"layout.h\" diff --git a/drw.c b/drw.c new file mode 100644 index 0000000..868465c --- /dev/null +++ b/drw.c @@ -0,0 +1,418 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +#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..f397611 --- /dev/null +++ b/drw.h @@ -0,0 +1,50 @@ +/* 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/drw.o b/drw.o new file mode 100644 index 0000000000000000000000000000000000000000..87d440c202098c7827effcca0123d3cccb419cc6 GIT binary patch literal 11512 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 literal 0 HcmV?d00001 diff --git a/layout.h b/layout.h new file mode 100644 index 0000000..0bc7831 --- /dev/null +++ b/layout.h @@ -0,0 +1,168 @@ +//    +#define KEYS 71 +static Key keys_en[KEYS] = { + { "`~", XK_grave, 1 }, + { "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_minus, 1 }, + { "=+", XK_plus, 1 }, + { "⌫", XK_BackSpace, 2 }, + { 0 }, /* New row */ + { "->|", XK_Tab, 1 }, + { 0, XK_q, 1 }, + { 0, XK_w, 1 }, + { 0, XK_e, 1 }, + { 0, XK_r, 1 }, + { 0, XK_t, 1 }, + { 0, XK_y, 1 }, + { 0, XK_u, 1 }, + { 0, XK_i, 1 }, + { 0, XK_o, 1 }, + { 0, XK_p, 1 }, + { "[", XK_bracketleft, 1 }, + { "]", XK_bracketright, 1 }, + { "↵", XK_Return, 1 }, + { 0 }, /* New row */ + { "Esc", XK_Escape, 1 }, + { 0, XK_a, 1 }, + { 0, XK_s, 1 }, + { 0, XK_d, 1 }, + { 0, XK_f, 1 }, + { 0, XK_g, 1 }, + { 0, XK_h, 1 }, + { 0, XK_j, 1 }, + { 0, XK_k, 1 }, + { 0, XK_l, 1 }, + { ":;", XK_semicolon, 1 }, + { "'\"", XK_exclam, 1 }, + { "\\|", XK_backslash, 1 }, + { 0 }, /* New row */ + { "Shift", XK_Shift_L, 3 }, + { 0, XK_z, 1 }, + { 0, XK_x, 1 }, + { 0, XK_c, 1 }, + { 0, XK_v, 1 }, + { 0, XK_b, 1 }, + { 0, XK_n, 1 }, + { 0, XK_m, 1 }, + { "↑", XK_Up, 1 }, + { ",", XK_colon, 1 }, + { ".", XK_period, 1 }, + { "/?", XK_slash, 1 }, + { "Shift", XK_Shift_R, 2 }, + { 0 }, /* New row */ + { "", XK_Cancel, 1}, + { "Ctrl", XK_Control_L, 2 }, + { "Alt", XK_Alt_L, 2 }, + { "", XK_Super_L, 2 }, + { "Hyper", XK_Hyper_L, 2 }, + { "", XK_space, 5 }, + { "←", XK_Left, 1 }, + { "↓", XK_Down, 1 }, + { "→", XK_Right, 1}, + { "Alt", XK_Alt_R, 2 }, + { "", XK_Super_R, 2 }, + { "Hyper", XK_Hyper_R, 2 }, + { "Ctrl", XK_Control_R, 2 }, +}; +static Key keys_emoji [KEYS] = { + { "🙂", 0x101f642 ,1 }, //1 + { "😀", 0x101f600 ,1 },//2 + { "😁", 0x101f601 ,1 },//3 + { "😂", 0x101f602 ,1 },//4 + { "😃", 0x101f603 ,1 },//5 + { "😄", 0x101f604 ,1 },//6 + { "😅", 0x101f605 ,1 },//7 + { "😆", 0x101f606 ,1 },//8 + { "😇", 0x101f607 ,1 },//9 + { "😈", 0x101f608 ,1 },//0 + { "😉", 0x101f609 ,1 },//11 + { "😊", 0x101f60a ,1 },//12 + { "😋", 0x101f60b ,1 },//13 + { "😌", 0x101f60c ,1 },//14 + { "😍", 0x101f60d ,1 },//15 + { "😎", 0x101f60e ,1 },//16 + { "😏", 0x101f60f ,1 },//17 + { "😐", 0x101f610 ,1 },//18 + { "😒", 0x101f612 ,1 },//19 + { "😓", 0x101f613 ,1 },//20 + { "😛", 0x101f61b ,1 },//21 + { "😮", 0x101f62e ,1 },//22 + { "😟", 0x101f61f ,1 },//23 + { "😟", 0x101f620 ,1 },//24 + { "😢", 0x101f622 ,1 },//25 + { "😭", 0x101f62d ,1 },//26 + { "😳", 0x101f633 ,1 },//27 + { "😴", 0x101f634 ,1 },//28 + { "Del", XK_Delete, 1 },//29 + {0}, + { "▶", XF86XK_AudioPlay, 1 },//30 + { "●", XF86XK_AudioRecord, 1 },//31 + { "■", XF86XK_AudioStop, 1 },//32 + { "◂◂", XF86XK_AudioPrev, 1 },//33 + { "▸▸", XF86XK_AudioNext, 1 },//34 + { "♫M", XF86XK_AudioMute, 1 },//35 + { "♫-", XF86XK_AudioLowerVolume, 1 },//36 + { "♫+", XF86XK_AudioRaiseVolume, 1 },//37 + { "☀-", XF86XK_MonBrightnessDown, 1 },//38 + { "☀+", XF86XK_MonBrightnessUp, 1 },//39 +{ "F1", XK_F1, 1 },//40 + { "F2", XK_F2, 1 },//41 + { "F3", XK_F3, 1 },//42 + { "F4", XK_F4, 1 },//43 + { "F5", XK_F5, 1 },//44 + { "F6", XK_F6, 1 },//45 + { "F7", XK_F7, 1 },//46 + { "F8", XK_F8, 1 },//47 + { "F9", XK_F9, 1 },//48 + { "F10", XK_F10, 1 },//49 + { "F11", XK_F11, 1 },//50 + { "F12", XK_F12, 1 },//51 + { "", XK_Cancel, 1},//52 + { "", XK_space, 5 },//53 + {0}, + { "", XK_space, 10 },//54 + {0}, + { "", XK_space, 10 },//55 + {0}, + { "", XK_space, 10 },//56 + {0}, + { "", XK_space, 10 },//57 + {0}, + { "", XK_space, 10 },//58 + {0}, + { "", XK_space, 10 },//59 +{0}, +{0}, + + }; + +Buttonmod buttonmods[] = { + { XK_Shift_L, Button2 }, + { XK_Alt_L, Button3 }, +}; + +#define OVERLAYS 1 +static Key overlay[OVERLAYS] = { + { 0, XK_Cancel }, +}; + +#define LAYERS 2 +static char* layer_names[LAYERS] = { + "en", + "emoji", +}; + +static Key* available_layers[LAYERS] = { + keys_en, + keys_emoji, +}; + diff --git a/svkbd b/svkbd new file mode 100755 index 0000000000000000000000000000000000000000..b397699b3dd8741cc1daf659f22234851c92bac7 GIT binary patch literal 52480 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 ZxoejyS3zTOGgSVjrvyc|DT67>{ueSe$#wt$ literal 0 HcmV?d00001 diff --git a/svkbd-arrows b/svkbd-arrows new file mode 100755 index 0000000000000000000000000000000000000000..44e1531ec1322edaf3cbc70b6c0200ab2012c30b GIT binary patch literal 41800 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 literal 0 HcmV?d00001 diff --git a/svkbd.1 b/svkbd.1 new file mode 100644 index 0000000..45d1adf --- /dev/null +++ b/svkbd.1 @@ -0,0 +1,72 @@ +.Dd August 4, 2020 +.Dt SVKBD 1 +.Os +.Sh NAME +.Nm svkbd +.Nd simple virtual keyboard +.Sh SYNOPSIS +.Nm +.Op Fl D +.Op Fl d +.Op Fl g Ar geometry +.Op Fl fn Ar font +.Op Fl O +.Op Fl h +.Op Fl H Ar heightfactor +.Op Fl l Ar layers +.Op Fl s Ar layer +.Op Fl v +.Sh DESCRIPTION +.Nm +is a simple virtual keyboard for X.org, intended to be used in environments, where no +keyboard is available. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl D +Debug mode; output verbosely to stderr. +.It Fl d +Set the _NET_WM_WINDOW_TYPE_DOCK property to hint windowmanagers it is +dockable, by default off. +.It Fl fn Ar font +Defines the font or font set used. e.g. "Monospace:bold:size=20" (an Xft font). +.It Fl g Ar geometry +Adjust the initial window position or size as specified by the standard X11 +geometry format. +.It Fl h +Show the usage information. +.It Fl H Ar heightfactor +Affects the vertical space taken by the keyboard. +One row of keys takes up 1/heighfactor of the screen's total height. +.It Fl l Ar layers +Comma separated list of layers to enable (by name). If not set, all layers +in the layout will be available. +The layer names are defined by the layout you compiled. +.It Fl s Ar layer +The layer to show on program start-up (by name). If not set, the first +layer of the layout will be shown. +.It Fl O +Disable overlay functionality. +.It Fl v +Show the version information. +.El +.Sh ENVIRONMENT +The following environment variables can be defined, providing +an alternative to some of the command line parameters. +The command line parameters, however, always take precedence: +.Bl -tag -width Ds +.It Ev SVKBD_LAYERS +Comma separated list of layers to enable (by name). The layer names are defined by the layout +you compiled. +.It Ev SVKBD_ENABLEOVERLAYS +Set this to 0 if you want to disable overlay functionality. +.It Ev SVKBD_HEIGHTFACTOR +Affects the vertical space taken by the keyboard. +One row of keys takes up 1/heighfactor of the screen's total height. +.El +.Sh SEE ALSO +.Xr XParseGeometry 3 +.Sh AUTHORS +.An Christoph Lohmann Aq Mt 20h@r-36.net +.An Enno Boland Aq Mt gottox@s01.de +.An Miles Alan Aq Mt m@milesalan.com +.An Maarten van Gompel Aq Mt proycon@anaproy.nl diff --git a/svkbd.c b/svkbd.c new file mode 100644 index 0000000..9b1282a --- /dev/null +++ b/svkbd.c @@ -0,0 +1,999 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif + +#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 }; +enum { NetWMWindowType, NetLast }; + +/* typedefs */ +typedef struct { + char *label; + KeySym keysym; + unsigned int width; + int x, y, w, h; + Bool pressed; + Bool highlighted; +} Key; + +typedef struct { + KeySym mod; + unsigned int button; +} Buttonmod; + +/* function declarations */ +static void printdbg(const char *fmt, ...); +static void motionnotify(XEvent *e); +static void buttonpress(XEvent *e); +static void buttonrelease(XEvent *e); +static void cleanup(void); +static void configurenotify(XEvent *e); +static void countrows(); +static int countkeys(Key *layer); +static void drawkeyboard(void); +static void drawkey(Key *k); +static void expose(XEvent *e); +static Key *findkey(int x, int y); +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 simulate_keypress(KeySym keysym); +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(); + +/* variables */ +static int screen; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ButtonRelease] = buttonrelease, + [ConfigureNotify] = configurenotify, + [Expose] = expose, + [LeaveNotify] = leavenotify, + [MotionNotify] = motionnotify +}; +static Atom netatom[NetLast]; +static Display *dpy; +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 enableoverlays = 1; +static int currentoverlay = -1; /* -1 = no overlay */ +static KeySym overlaykeysym = 0; /* keysym for which the overlay is presented */ +static int releaseprotect = 0; /* set to 1 after overlay is shown, protecting against immediate release */ +static int tmp_keycode = 1; +static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0; +static char *name = "svkbd"; +static int debug = 0; +static int numlayers = 0; +static int numkeys = 0; + +static KeySym ispressingkeysym; + +Bool ispressing = False; +Bool sigtermd = False; + +/* configuration, allows nested code to access above variables */ +#include "config.h" +#ifndef LAYOUT +#error "make sure to define LAYOUT" +#endif +#include LAYOUT + +static Key keys[KEYS] = { NULL }; +static Key* layers[LAYERS]; + +void +motionnotify(XEvent *e) +{ + XPointerMovedEvent *ev = &e->xmotion; + int 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 + && ev->y < keys[i].y + keys[i].h) { + if (keys[i].highlighted != True) { + if (ispressing) { + keys[i].pressed = True; + } else { + keys[i].highlighted = True; + } + drawkey(&keys[i]); + } + continue; + } + + if (!IsModifierKey(keys[i].keysym) && keys[i].pressed == True) { + unpress(&keys[i], 0); + + drawkey(&keys[i]); + } + if (keys[i].highlighted == True) { + keys[i].highlighted = False; + drawkey(&keys[i]); + } + } +} + +void +buttonpress(XEvent *e) +{ + XButtonPressedEvent *ev = &e->xbutton; + Key *k; + KeySym mod = 0; + int i; + + ispressing = True; + + for (i = 0; i < LENGTH(buttonmods); i++) { + if (ev->button == buttonmods[i].button) { + mod = buttonmods[i].mod; + break; + } + } + if ((k = findkey(ev->x, ev->y))) + press(k, mod); +} + +void +buttonrelease(XEvent *e) +{ + XButtonPressedEvent *ev = &e->xbutton; + Key *k; + KeySym mod = 0; + int i; + + ispressing = False; + + for (i = 0; i < LENGTH(buttonmods); i++) { + if (ev->button == buttonmods[i].button) { + mod = buttonmods[i].mod; + break; + } + } + + if (ev->x < 0 || ev->y < 0) { + unpress(NULL, mod); + } else { + if ((k = findkey(ev->x, ev->y))) + unpress(k, mod); + } +} + +void +cleanup(void) +{ + int i; + + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_sync(drw); + drw_free(drw); + XSync(dpy, False); + drw_free(drw); + XDestroyWindow(dpy, win); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); +} + +void +configurenotify(XEvent *e) +{ + XConfigureEvent *ev = &e->xconfigure; + + if (ev->window == win && (ev->width != ww || ev->height != wh)) { + ww = ev->width; + wh = ev->height; + drw_resize(drw, ww, wh); + updatekeys(); + } +} + +void +countrows(void) +{ + int i; + + for (i = 0, rows = 1; i < numkeys; i++) { + if (keys[i].keysym == 0) + rows++; + } +} + +int +countkeys(Key *layer) +{ + int i, nkeys = 0; + + for (i = 0; i < KEYS; i++) { + if (i > 0 && layer[i].keysym == 0 && layer[i - 1].keysym == 0) { + nkeys--; + break; + } + nkeys++; + } + + return nkeys; +} + +void +drawkeyboard(void) +{ + int i; + + for (i = 0; i < numkeys; i++) { + if (keys[i].keysym != 0) + drawkey(&keys[i]); + } +} + +void +drawkey(Key *k) +{ + int x, y, w, h; + const char *l; + + if (k->pressed) + drw_setscheme(drw, scheme[SchemePress]); + else if (k->highlighted) + drw_setscheme(drw, scheme[SchemeHighlight]); + else + 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->keysym == XK_KP_Insert) { + if (enableoverlays) { + l = "≅"; + } else { + l = "≇"; + } + } else if (k->label) { + l = k->label; + } else { + l = XKeysymToString(k->keysym); + } + 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 +expose(XEvent *e) +{ + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (ev->window == win)) + drawkeyboard(); +} + +Key * +findkey(int x, int y) { + int 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) { + return &keys[i]; + } + } + return NULL; +} + +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 +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 (debug) printdbg("Begin press: %ld\n", k->keysym); + pressbegin.tv_sec = 0; + pressbegin.tv_usec = 0; + ispressingkeysym = 0; + + if (!IsModifierKey(k->keysym)) { + if (enableoverlays && currentoverlay == -1) + overlayidx = hasoverlay(k->keysym); + if (enableoverlays && overlayidx != -1) { + if (!pressbegin.tv_sec && !pressbegin.tv_usec) { + /*record the begin of the press, don't simulate the actual keypress yet */ + record_press_begin(k->keysym); + } + } else { + if (debug) printdbg("Simulating press: %ld\n", k->keysym); + for (i = 0; i < numkeys; 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 < numkeys; 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(void) +{ + 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; + + if (k != NULL) { + switch(k->keysym) { + case XK_Cancel: + cyclelayer(); + break; + case XK_script_switch: + togglelayer(); + break; + case XK_KP_Insert: + enableoverlays = !enableoverlays; + break; + case XK_Break: + running = False; + break; + default: + break; + } + } + + if ((pressbegin.tv_sec || pressbegin.tv_usec) && enableoverlays && k && k->keysym == ispressingkeysym) { + if (currentoverlay == -1) { + if (get_press_duration() < overlay_delay) { + if (debug) printdbg("Delayed simulation of press after release: %ld\n", k->keysym); + /* simulate the press event, as we postponed it earlier in press() */ + for (i = 0; i < numkeys; 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) { + printdbg("Simulation of release: %ld\n", k->keysym); + } else { + printdbg("Simulation of release (all keys)\n"); + } + } + + + for (i = 0; i < numkeys; i++) { + if (keys[i].pressed && !IsModifierKey(keys[i].keysym)) { + simulate_keyrelease(keys[i].keysym); + keys[i].pressed = 0; + drawkey(&keys[i]); + break; + } + } + if (i != numkeys) { + if (pressedmod) { + simulate_keyrelease(mod); + } + pressedmod = 0; + + for (i = 0; i < numkeys; i++) { + if (keys[i].pressed) { + simulate_keyrelease(keys[i].keysym); + keys[i].pressed = 0; + drawkey(&keys[i]); + } + } + } + + if (enableoverlays && currentoverlay != -1) { + if (releaseprotect) { + releaseprotect = 0; + } else { + hideoverlay(); + } + } +} + +void +run(void) +{ + XEvent ev; + int xfd; + fd_set fds; + struct timeval tv; + double duration = 0.0; + int i, r; + + xfd = ConnectionNumber(dpy); + tv.tv_sec = 1; + tv.tv_usec = 0; + + XFlush(dpy); + + while (running) { + usleep(100000L); /* 100ms */ + FD_ZERO(&fds); + FD_SET(xfd, &fds); + r = select(xfd + 1, &fds, NULL, NULL, &tv); + if (r) { + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + if (handler[ev.type]) { + (handler[ev.type])(&ev); /* call handler */ + } + } + } else { + /* time-out expired without anything interesting happening, check for long-presses */ + if (ispressing && ispressingkeysym) { + duration = get_press_duration(); + if (debug == 2) printdbg("%f\n", duration); + if (get_press_duration() >= overlay_delay) { + if (debug) printdbg("press duration %f\n", duration); + showoverlay(hasoverlay(ispressingkeysym)); + pressbegin.tv_sec = 0; + pressbegin.tv_usec = 0; + ispressingkeysym = 0; + } + } + } + if (r == -1 || sigtermd) { + /* an error occurred or we received a signal */ + /* E.g. Generally in scripts we want to call SIGTERM on svkbd in which case + if the user is holding for example the enter key (to execute + the kill or script that does the kill), that causes an issue + since then X doesn't know the keyup is never coming.. (since + process will be dead before finger lifts - in that case we + just trigger out fake up presses for all keys */ + if (debug) printdbg("signal received, releasing all keys"); + for (i = 0; i < numkeys; i++) { + XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0); + } + running = False; + } + } +} + +void +setup(void) +{ + XSetWindowAttributes wa; + XTextProperty str; + XSizeHints *sizeh = NULL; + XClassHint *ch; + XWMHints *wmh; + Atom atype = -1; + int i, j, sh, sw; + +#ifdef XINERAMA + XineramaScreenInfo *info = NULL; +#endif + + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); +#ifdef 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; + 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) { + netatom[NetWMWindowType] = XInternAtom(dpy, + "_NET_WM_WINDOW_TYPE", False); + atype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); + } + + /* init appearance */ + countrows(); + if (!ww) + ww = sw; + if (!wh) + wh = sh * rows / heightfactor; + + if (!wx) + wx = 0; + if (wx < 0) + wx = sw + wx - ww; + if (!wy) + wy = sh - wh; + if (wy < 0) + wy = sh + wy - wh; + + for (i = 0; i < numkeys; i++) + keys[i].pressed = 0; + + wa.override_redirect = !wmborder; + 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); + XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask| + ButtonPressMask|ExposureMask|LeaveWindowMask| + PointerMotionMask); + + wmh = XAllocWMHints(); + wmh->input = False; + wmh->flags = InputHint; + if (!isdock) { + sizeh = XAllocSizeHints(); + sizeh->flags = PMaxSize | PMinSize; + sizeh->min_width = sizeh->max_width = ww; + sizeh->min_height = sizeh->max_height = wh; + } + XStringListToTextProperty(&name, 1, &str); + ch = XAllocClassHint(); + ch->res_class = name; + ch->res_name = name; + + XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh, + ch); + + XFree(keysyms); + XFree(ch); + XFree(wmh); + XFree(str.value); + if (sizeh != NULL) + XFree(sizeh); + + if (isdock) { + XChangeProperty(dpy, win, netatom[NetWMWindowType], XA_ATOM, + 32, PropModeReplace, + (unsigned char *)&atype, 1); + } + + XMapRaised(dpy, win); + drw_resize(drw, ww, wh); + updatekeys(); + drawkeyboard(); +} + +void +updatekeys(void) +{ + int i, j; + int x = 0, y = 0, h, base, r = rows; + + h = (wh - 1) / rows; + 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 < numkeys && keys[i].keysym != 0; i++) { + keys[i].x = x; + keys[i].y = y; + keys[i].w = keys[i].width * (ww - 1) / base; + keys[i].h = r == 1 ? wh - y - 1 : h; + x += keys[i].w; + } + if (base != 0) + keys[i - 1].w = ww - 1 - keys[i - 1].x; + y += h; + } +} + +void +usage(char *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(void) +{ + numkeys = countkeys(layers[currentlayer]); + memcpy(&keys, layers[currentlayer], sizeof(Key) * numkeys); +} + +void +cyclelayer(void) +{ + currentlayer++; + if (currentlayer >= numlayers) + currentlayer = 0; + if (debug) printdbg("Cycling to layer %d\n", currentlayer); + setlayer(); + updatekeys(); + drawkeyboard(); +} + +void +togglelayer(void) +{ + if (currentlayer > 0) { + currentlayer = 0; + } else if (numlayers > 1) { + currentlayer = 1; + } + if (debug) printdbg("Toggling layer %d\n", currentlayer); + setlayer(); + updatekeys(); + drawkeyboard(); +} + +void +showoverlay(int idx) +{ + if (debug) printdbg("Showing overlay %d\n", idx); + int i,j; + + /* unpress existing key (visually only) */ + for (i = 0; i < numkeys; 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(void) +{ + if (debug) printdbg("Hiding overlay, overlay was #%d\n", currentoverlay); + currentoverlay = -1; + overlaykeysym = 0; + currentlayer--; + cyclelayer(); +} + +void +sigterm(int signo) +{ + running = False; + sigtermd = True; + if (debug) printdbg("SIGTERM received\n"); +} + +void +init_layers(char *layer_names_list, const char *initial_layer_name) +{ + char *s; + int j, found; + + 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 { + s = strtok(layer_names_list, ","); + while (s != NULL) { + if (numlayers+1 > LAYERS) + die("too many layers specified"); + 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]; + if (initial_layer_name != NULL && strcmp(layer_names[j], initial_layer_name) == 0) { + currentlayer = numlayers; + } + found = 1; + break; + } + } + if (!found) { + fprintf(stderr, "Undefined layer: %s\n", s); + exit(3); + } + numlayers++; + s = strtok(NULL,","); + } + } + setlayer(); +} + +void +printdbg(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + +int +main(int argc, char *argv[]) +{ + char *initial_layer_name = NULL; + char *layer_names_list = NULL; + char *tmp; + int i, xr, yr, bitm; + unsigned int wr, hr; + + signal(SIGTERM, sigterm); + + if (OVERLAYS <= 1) { + enableoverlays = 0; + } else { + if ((tmp = getenv("SVKBD_ENABLEOVERLAYS"))) + enableoverlays = atoi(tmp); + } + if ((tmp = getenv("SVKBD_LAYERS"))) { + if (!(layer_names_list = strdup(tmp))) + die("memory allocation error"); + } + + if ((tmp = getenv("SVKBD_HEIGHTFACTOR"))) + heightfactor = atoi(tmp); + + /* parse command line arguments */ + for (i = 1; argv[i]; i++) { + if (!strcmp(argv[i], "-v")) { + die("svkbd-"VERSION); + } else if (!strcmp(argv[i], "-d")) { + isdock = True; + continue; + } else if (!strncmp(argv[i], "-g", 2)) { + if (i >= argc - 1) + continue; + + bitm = XParseGeometry(argv[i + 1], &xr, &yr, &wr, &hr); + if (bitm & XValue) + wx = xr; + if (bitm & YValue) + wy = yr; + if (bitm & WidthValue) + ww = (int)wr; + if (bitm & HeightValue) + wh = (int)hr; + if (bitm & XNegative && wx == 0) + wx = -1; + 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]); + } else if (!strcmp(argv[i], "-O")) { + enableoverlays = 0; + } else if (!strcmp(argv[i], "-l")) { + if (i >= argc - 1) + continue; + free(layer_names_list); + if (!(layer_names_list = strdup(argv[++i]))) + die("memory allocation error"); + } 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) + die("height factor must be a positive integer"); + + init_layers(layer_names_list, initial_layer_name); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "warning: no locale support"); + if (!(dpy = XOpenDisplay(0))) + die("cannot open display"); + setup(); + run(); + cleanup(); + XCloseDisplay(dpy); + free(layer_names_list); + + return 0; +} diff --git a/svkbd.o b/svkbd.o new file mode 100644 index 0000000000000000000000000000000000000000..550c7d28cd1a6b888ac330aefc1f1e5c0caedac8 GIT binary patch literal 43768 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 +#include +#include +#include + +#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); diff --git a/util.o b/util.o new file mode 100644 index 0000000000000000000000000000000000000000..0dd6399674c40d0fd5f22d5216007a1b9ea7ab01 GIT binary patch literal 2512 zcmbVNJ!}+56#n-100YJt8 z;DVJ>xNs4lK%xOe$sr+8L<&?SqD_HZqyn@C^HZeYr+_Ui^WN@^JPdmc&)RwKeeZ8( z-t*jr!^e)K3k+;+adY7U!Y98Yd^&@0 zaTejTKM+2@i15W_gh98TTlmJ$J=yN(p7#2=#od1H*|&b~`GB8$alp?}+Bv`u4f+-a zK-`}X?x*X)c8cw#@4i_Nb`xLicLYV;4K@-#gS%9(9?TqS1Zz@_VCHbcP8*E{v-76R z$2m#9G@P>c1;E}Qwq2^GFgyGvKiK^e=jhNsOMk9!8~zE7$sp#c4QYw_^X&6(dHIW_ z>3h*Vus#g@xitOHjla$Of27lyP-mPz6bt2YrMMqBdhp=>OwZBdCwFA}`>Z`yA6tR; zn87%I0_obY8zA-Q!1X z7+TLz5^^N!RAja>>pRx zO(v8f7REN>9m;v5`;D-bUZ{Eaj%ANjytfI?y-D&v&;%c9f*)yu|42CRU+z1a0m6@(r;Vtv?BQP{XeXoc*~6lCBGZ{Z;&iz`F?FD4mJl zQE>I1{}s5N|AD~u{125pn}p14QNcSD{H2if{QnDF&;MS@qvn^R$LH!)^0$-D#Q2t2 zjMj|6JBh2)oE7*M4et>+=V?{)@VmjZittn{M(d!EB`)U*R`s`WuW*{U=f-?gO0H8b zSe{e!V3i790oLhi6_#6Jh28tDL0HAg__#AcE)x~cvFP2~6^$MFakMjn~+lt1Hv% z@J^BQbL0C({XMdP;FF?|+^;lCUGBps1IvBVLGYSzP-m1)GEVXz6ikFhY*sp`V1n-{ zfvGdeA#);Lz9)<^$^FL~@Cyk^{d+HdTI9L7e;h00W&gO0UYj5UqJFzD^6wS@Kk4yn f3DbK=5MBxJBr6K(*TQc@QhbjX=ust>MLqrli!