svkbd

simple virtual keyboard
git clone git://git.suckless.org/svkbd
Log | Files | Refs | LICENSE

commit 5f9a5d2068f288f4f5067265bd48fe65561248f4
Author: Enno Boland (Gottox) <gottox@s01.de>
Date:   Tue, 15 Jul 2008 18:53:38 +0200

initial checkin.
Diffstat:
Makefile | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 13+++++++++++++
config.h | 14++++++++++++++
config.mk | 27+++++++++++++++++++++++++++
svkbd.c | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 472 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,60 @@ +# svkbd - simple virtual keyboard +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = svkbd.c +OBJ = ${SRC:.c=.o} + +all: options svkbd + +options: + @echo svkbd build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +svkbd: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f svkbd ${OBJ} svkbd-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p svkbd-${VERSION} + @cp -R LICENSE Makefile README config.def.h config.mk \ + ${SRC} svkbd-${VERSION} + @tar -cf svkbd-${VERSION}.tar svkbd-${VERSION} + @gzip svkbd-${VERSION}.tar + @rm -rf svkbd-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f svkbd ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/svkbd + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < svkbd.1 > ${DESTDIR}${MANPREFIX}/man1/svkbd.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/svkbd.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/svkbd + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/svkbd.1 + +.PHONY: all options clean dist install uninstall diff --git a/config.def.h b/config.def.h @@ -0,0 +1,13 @@ +static const char font[] = "-*-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*"; +static const char normbgcolor[] = "#cccccc"; +static const char normfgcolor[] = "#000000"; +static const char selbgcolor[] = "#0066ff"; +static const char selfgcolor[] = "#ffffff"; +static const char hovbgcolor[] = "#0066ff"; +static const char hovfgcolor[] = "#cccccc"; + +static Key keys[] = { + { 1, XK_Return }, + { 0 }, + { 1, XK_space }, +}; diff --git a/config.h b/config.h @@ -0,0 +1,14 @@ +static const char font[] = "-*-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*"; +static const char normbgcolor[] = "#cccccc"; +static const char normfgcolor[] = "#000000"; +static const char selbgcolor[] = "#0066ff"; +static const char selfgcolor[] = "#ffffff"; +static const char hovbgcolor[] = "#0066ff"; +static const char hovfgcolor[] = "#cccccc"; + +static Key keys[] = { + { 2, XK_Return }, + { 1, XK_space }, + { 0 }, + { 1, XK_space }, +}; diff --git a/config.mk b/config.mk @@ -0,0 +1,27 @@ +# svkbd version +VERSION = 0.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCS = -I. -I/usr/include -I${X11INC} +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +CFLAGS = -g -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -g ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/svkbd.c b/svkbd.c @@ -0,0 +1,358 @@ +/* See LICENSE file for copyright and license details. + * + * dynamic window manager is designed like any other X client as well. It is + * driven through handling X events. In contrast to other X clients, a window + * manager selects for SubstructureRedirectMask on the root window, to receive + * events about window (dis-)appearance. Only one X connection at a time is + * allowed to select for this event mask. + * + * Calls to fetch an X event from the event queue are blocking. Due reading + * status text from standard input, a select()-driven main loop has been + * implemented which selects for reads on the X connection and STDIN_FILENO to + * handle all data smoothly. The event handlers of dwm are organized in an + * array which is accessed whenever a new event has been fetched. This allows + * event dispatching in O(1) time. + * + * Each child of the root window is called a client, except windows which have + * set the override_redirect flag. Clients are organized in a global + * doubly-linked client list, the focus history is remembered through a global + * stack list. Each client contains a bit array to indicate the tags of a + * client. + * + * Keys and tagging rules are organized as arrays and defined in config.h. + * + * To understand everything else, start reading main(). + */ +#include <errno.h> +#include <locale.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#ifdef XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +/* macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) +#define LENGTH(x) (sizeof x / sizeof x[0]) +#define MAXMOD 16 +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define TAGMASK ((int)((1LL << LENGTH(tags)) - 1)) +#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) +#define ISVISIBLE(x) (x->tags & tagset[seltags]) + +/* enums */ +enum { ColFG, ColBG, ColLast }; + +/* typedefs */ +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef struct { + ulong norm[ColLast]; + ulong sel[ColLast]; + ulong hover[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; + XFontSet set; + XFontStruct *xfont; + } font; +} DC; /* draw context */ + +typedef struct { + uint width; + KeySym keysym; + int x, y, w, h; + Bool sel; +} Key; + +/* function declarations */ +static void buttonpress(XEvent *e); +static void cleanup(void); +static void configurenotify(XEvent *e); +static void destroynotify(XEvent *e); +static void die(const char *errstr, ...); +static void drawkeyboard(void); +static void drawkey(Key *k, ulong col[ColLast]); +static void expose(XEvent *e); +static ulong getcolor(const char *colstr); +static void initfont(const char *fontstr); +static void leavenotify(XEvent *e); +static void motionnotify(XEvent *e); +static void run(void); +static void setup(void); +static int textnw(const char *text, uint len); + +/* variables */ +static int screen; +static int wx, wy, ww, wh; +static void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [Expose] = expose, + [LeaveNotify] = leavenotify, + [MotionNotify] = motionnotify, +}; +static Display *dpy; +static DC dc; +static Window root, win; +static Bool running = True; +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +buttonpress(XEvent *e) { +} + +void +cleanup(void) { + if(dc.font.set) + XFreeFontSet(dpy, dc.font.set); + else + XFreeFont(dpy, dc.font.xfont); + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + 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; + XFreePixmap(dpy, dc.drawable); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen)); + drawkeyboard(); + } +} + +void +die(const char *errstr, ...) { + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +drawkeyboard(void) { + int rows, i, j; + int x = 0, y = 0, h, base; + + for(i = 0, rows = 1; i < LENGTH(keys); i++) + if(keys[i].keysym == 0) + rows++; + h = wh / rows; + for(i = 0; i < LENGTH(keys); i++) { + for(j = i, base = 0; j < LENGTH(keys) && keys[j].keysym != 0; j++) + base += keys[j].width; + for(x = 0; i < LENGTH(keys) && keys[i].keysym != 0; i++) { + keys[i].x = x; + keys[i].y = y; + keys[i].w = keys[i].width * ww / base; + keys[i].h = h; + x += keys[i].w; + printf("%i %i %i %i\n", x, y, keys[i].w, h); + } + y += h; + } + for(i = 0; i < LENGTH(keys); i++) { + if(keys[i].keysym != 0) + drawkey(&keys[i], dc.norm); + } + XSync(dpy, False); + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, wh, 0, 0); +} + +void +drawkey(Key *k, ulong col[ColLast]) { + int x, y, h, len; + XRectangle r = { k->x, k->y, k->w, k->h}; + const char *text; + + XSetForeground(dpy, dc.gc, col[ColBG]); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + XSetForeground(dpy, dc.gc, col[ColFG]); + XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1); + text = XKeysymToString(k->keysym); + len = strlen(text); + h = dc.font.ascent + dc.font.descent; + y = k->y + (k->h / 2) - (h / 2) + dc.font.ascent; + x = k->x + (k->w / 2) - (textnw(text, len) / 2); + if(dc.font.set) + XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, text, len); + else + XDrawString(dpy, dc.drawable, dc.gc, x, y, text, len); +} + +void +destroynotify(XEvent *e) { + +} + +void +expose(XEvent *e) { + XExposeEvent *ev = &e->xexpose; + + if(ev->count == 0 && (ev->window == win)) + drawkeyboard(); +} + +ulong +getcolor(const char *colstr) { + Colormap cmap = DefaultColormap(dpy, screen); + XColor color; + + if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) + die("error, cannot allocate color '%s'\n", colstr); + return color.pixel; +} + +void +initfont(const char *fontstr) { + char *def, **missing; + int i, n; + + missing = NULL; + if(dc.font.set) + XFreeFontSet(dpy, dc.font.set); + dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); + if(missing) { + while(n--) + fprintf(stderr, "dwm: missing fontset: %s\n", missing[n]); + XFreeStringList(missing); + } + if(dc.font.set) { + XFontSetExtents *font_extents; + XFontStruct **xfonts; + char **font_names; + dc.font.ascent = dc.font.descent = 0; + font_extents = XExtentsOfFontSet(dc.font.set); + n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); + for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { + dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); + dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent); + xfonts++; + } + } + else { + if(dc.font.xfont) + XFreeFont(dpy, dc.font.xfont); + dc.font.xfont = NULL; + if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) + && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) + die("error, cannot load font: '%s'\n", fontstr); + dc.font.ascent = dc.font.xfont->ascent; + dc.font.descent = dc.font.xfont->descent; + } + dc.font.height = dc.font.ascent + dc.font.descent; +} + +void +leavenotify(XEvent *e) { + +} + +void +motionnotify(XEvent *e) { + +} + +void +run(void) { + XEvent ev; + + /* main event loop, also reads status text from stdin */ + XSync(dpy, False); + while(running) { + XNextEvent(dpy, &ev); + if(handler[ev.type]) + (handler[ev.type])(&ev); /* call handler */ + } +} + +void +setup(void) { + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + initfont(font); + + /* init appearance */ + ww = DisplayWidth(dpy, screen); + wh = 200; + wx = 0; + wy = DisplayHeight(dpy, screen) - wh; + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.hover[ColBG] = getcolor(hovbgcolor); + dc.hover[ColFG] = getcolor(hovfgcolor); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen)); + dc.gc = XCreateGC(dpy, root, 0, 0); + if(!dc.font.set) + XSetFont(dpy, dc.gc, dc.font.xfont->fid); + + win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, dc.norm[ColFG], dc.norm[ColBG]); + XSelectInput(dpy, win, StructureNotifyMask | PointerMotionMask | ExposureMask); + XMapRaised(dpy, win); + drawkeyboard(); +} + +int +textnw(const char *text, uint len) { + XRectangle r; + + if(dc.font.set) { + XmbTextExtents(dc.font.set, text, len, NULL, &r); + return r.width; + } + return XTextWidth(dc.font.xfont, text, len); +} + +int +main(int argc, char *argv[]) { + if(argc == 2 && !strcmp("-v", argv[1])) + die("svkc-"VERSION", © 2006-2008 svkbd engineers, see LICENSE for details\n"); + else if(argc != 1) + die("usage: svkbd [-v]\n"); + + if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "warning: no locale support\n"); + + if(!(dpy = XOpenDisplay(0))) + die("svkbd: cannot open display\n"); + + setup(); + run(); + cleanup(); + + XCloseDisplay(dpy); + return 0; +}