svkbd

Simple X11 onscreen keyboard.
git clone git://r-36.net/svkbd
Log | Files | Refs | README | LICENSE

svkbd.c (13235B)


      1 /* See LICENSE file for copyright and license details.
      2  *
      3  * To understand svkbd, start reading main().
      4  */
      5 #include <locale.h>
      6 #include <stdarg.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 #include <stdlib.h>
     10 #include <X11/keysym.h>
     11 #include <X11/Xatom.h>
     12 #include <X11/Xlib.h>
     13 #include <X11/Xutil.h>
     14 #include <X11/Xproto.h>
     15 #include <X11/extensions/XTest.h>
     16 
     17 /* macros */
     18 #define MAX(a, b)       ((a) > (b) ? (a) : (b))
     19 #define LENGTH(x)       (sizeof x / sizeof x[0])
     20 
     21 /* enums */
     22 enum { ColFG, ColBG, ColLast };
     23 enum { NetWMWindowType, NetLast };
     24 
     25 /* typedefs */
     26 typedef unsigned int uint;
     27 typedef unsigned long ulong;
     28 
     29 typedef struct {
     30 	ulong norm[ColLast];
     31 	ulong press[ColLast];
     32 	ulong high[ColLast];
     33 
     34 	Drawable drawable;
     35 	GC gc;
     36 	struct {
     37 		int ascent;
     38 		int descent;
     39 		int height;
     40 		XFontSet set;
     41 		XFontStruct *xfont;
     42 	} font;
     43 } DC; /* draw context */
     44 
     45 typedef struct {
     46 	char *label;
     47 	KeySym keysym;
     48 	uint width;
     49 	int x, y, w, h;
     50 	Bool pressed;
     51 	Bool highlighted;
     52 } Key;
     53 
     54 typedef struct {
     55 	KeySym mod;
     56 	uint button;
     57 } Buttonmod;
     58 
     59 /* function declarations */
     60 static void motionnotify(XEvent *e);
     61 static void buttonpress(XEvent *e);
     62 static void buttonrelease(XEvent *e);
     63 static void cleanup(void);
     64 static void configurenotify(XEvent *e);
     65 static void countrows();
     66 static void die(const char *errstr, ...);
     67 static void drawkeyboard(void);
     68 static void drawkey(Key *k);
     69 static void expose(XEvent *e);
     70 static Key *findkey(int x, int y);
     71 static ulong getcolor(const char *colstr);
     72 static void initfont(const char *fontstr);
     73 static void leavenotify(XEvent *e);
     74 static void press(Key *k, KeySym mod);
     75 static void run(void);
     76 static void setup(void);
     77 static int textnw(const char *text, uint len);
     78 static void unpress(Key *k, KeySym mod);
     79 static void updatekeys();
     80 
     81 /* variables */
     82 static int screen;
     83 static void (*handler[LASTEvent]) (XEvent *) = {
     84 	[ButtonPress] = buttonpress,
     85 	[ButtonRelease] = buttonrelease,
     86 	[ConfigureNotify] = configurenotify,
     87 	[Expose] = expose,
     88 	[LeaveNotify] = leavenotify,
     89 	[MotionNotify] = motionnotify
     90 };
     91 static Atom netatom[NetLast];
     92 static Display *dpy;
     93 static DC dc;
     94 static Window root, win;
     95 static Bool running = True, isdock = False;
     96 static KeySym pressedmod = 0;
     97 static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0;
     98 static char *name = "svkbd";
     99 
    100 Bool ispressing = False;
    101 
    102 /* configuration, allows nested code to access above variables */
    103 #include "config.h"
    104 #include "layout.h"
    105 
    106 void
    107 motionnotify(XEvent *e)
    108 {
    109 	XPointerMovedEvent *ev = &e->xmotion;
    110 	int i;
    111 
    112 	for(i = 0; i < LENGTH(keys); i++) {
    113 		if(keys[i].keysym && ev->x > keys[i].x
    114 				&& ev->x < keys[i].x + keys[i].w
    115 				&& ev->y > keys[i].y
    116 				&& ev->y < keys[i].y + keys[i].h) {
    117 			if(keys[i].highlighted != True) {
    118 				if(ispressing) {
    119 					keys[i].pressed = True;
    120 				} else {
    121 					keys[i].highlighted = True;
    122 				}
    123 				drawkey(&keys[i]);
    124 			}
    125 			continue;
    126 		}
    127 
    128 		if(!IsModifierKey(keys[i].keysym) && keys[i].pressed == True) {
    129 			unpress(&keys[i], 0);
    130 
    131 			drawkey(&keys[i]);
    132 		}
    133 		if(keys[i].highlighted == True) {
    134 			keys[i].highlighted = False;
    135 			drawkey(&keys[i]);
    136 		}
    137 	}
    138 }
    139 
    140 void
    141 buttonpress(XEvent *e) {
    142 	int i;
    143 	XButtonPressedEvent *ev = &e->xbutton;
    144 	Key *k;
    145 	KeySym mod = 0;
    146 
    147 	ispressing = True;
    148 
    149 	for(i = 0; i < LENGTH(buttonmods); i++) {
    150 		if(ev->button == buttonmods[i].button) {
    151 			mod = buttonmods[i].mod;
    152 			break;
    153 		}
    154 	}
    155 	if((k = findkey(ev->x, ev->y)))
    156 		press(k, mod);
    157 }
    158 
    159 void
    160 buttonrelease(XEvent *e) {
    161 	int i;
    162 	XButtonPressedEvent *ev = &e->xbutton;
    163 	Key *k;
    164 	KeySym mod = 0;
    165 
    166 	ispressing = False;
    167 
    168 	for(i = 0; i < LENGTH(buttonmods); i++) {
    169 		if(ev->button == buttonmods[i].button) {
    170 			mod = buttonmods[i].mod;
    171 			break;
    172 		}
    173 	}
    174 
    175 	if(ev->x < 0 || ev->y < 0) {
    176 		unpress(NULL, mod);
    177 	} else {
    178 		if((k = findkey(ev->x, ev->y)))
    179 			unpress(k, mod);
    180 	}
    181 }
    182 
    183 void
    184 cleanup(void) {
    185 	if(dc.font.set)
    186 		XFreeFontSet(dpy, dc.font.set);
    187 	else
    188 		XFreeFont(dpy, dc.font.xfont);
    189 	XFreePixmap(dpy, dc.drawable);
    190 	XFreeGC(dpy, dc.gc);
    191 	XDestroyWindow(dpy, win);
    192 	XSync(dpy, False);
    193 	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
    194 }
    195 
    196 void
    197 configurenotify(XEvent *e) {
    198 	XConfigureEvent *ev = &e->xconfigure;
    199 
    200 	if(ev->window == win && (ev->width != ww || ev->height != wh)) {
    201 		ww = ev->width;
    202 		wh = ev->height;
    203 		XFreePixmap(dpy, dc.drawable);
    204 		dc.drawable = XCreatePixmap(dpy, root, ww, wh,
    205 				DefaultDepth(dpy, screen));
    206 		updatekeys();
    207 	}
    208 }
    209 
    210 void
    211 countrows() {
    212 	int i = 0;
    213 
    214 	for(i = 0, rows = 1; i < LENGTH(keys); i++) {
    215 		if(keys[i].keysym == 0)
    216 			rows++;
    217 	}
    218 }
    219 
    220 void
    221 die(const char *errstr, ...) {
    222 	va_list ap;
    223 
    224 	va_start(ap, errstr);
    225 	vfprintf(stderr, errstr, ap);
    226 	va_end(ap);
    227 	exit(EXIT_FAILURE);
    228 }
    229 
    230 void
    231 drawkeyboard(void) {
    232 	int i;
    233 
    234 	for(i = 0; i < LENGTH(keys); i++) {
    235 		if(keys[i].keysym != 0)
    236 			drawkey(&keys[i]);
    237 	}
    238 	XSync(dpy, False);
    239 }
    240 
    241 void
    242 drawkey(Key *k) {
    243 	int x, y, h, len;
    244 	XRectangle r = { k->x, k->y, k->w, k->h};
    245 	const char *l;
    246 	ulong *col;
    247 
    248 	if(k->pressed)
    249 		col = dc.press;
    250 	else if(k->highlighted)
    251 		col = dc.high;
    252 	else
    253 		col = dc.norm;
    254 
    255 	XSetForeground(dpy, dc.gc, col[ColBG]);
    256 	XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
    257 	XSetForeground(dpy, dc.gc, dc.norm[ColFG]);
    258 	r.height -= 1;
    259 	r.width -= 1;
    260 	XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
    261 	XSetForeground(dpy, dc.gc, col[ColFG]);
    262 	if(k->label) {
    263 		l = k->label;
    264 	} else {
    265 		l = XKeysymToString(k->keysym);
    266 	}
    267 	len = strlen(l);
    268 	h = dc.font.ascent + dc.font.descent;
    269 	y = k->y + (k->h / 2) - (h / 2) + dc.font.ascent;
    270 	x = k->x + (k->w / 2) - (textnw(l, len) / 2);
    271 	if(dc.font.set) {
    272 		XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l,
    273 				len);
    274 	} else {
    275 		XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len);
    276 	}
    277 	XCopyArea(dpy, dc.drawable, win, dc.gc, k->x, k->y, k->w, k->h,
    278 			k->x, k->y);
    279 }
    280 
    281 void
    282 expose(XEvent *e) {
    283 	XExposeEvent *ev = &e->xexpose;
    284 
    285 	if(ev->count == 0 && (ev->window == win))
    286 		drawkeyboard();
    287 }
    288 
    289 Key *
    290 findkey(int x, int y) {
    291 	int i;
    292 
    293 	for(i = 0; i < LENGTH(keys); i++) {
    294 		if(keys[i].keysym && x > keys[i].x &&
    295 				x < keys[i].x + keys[i].w &&
    296 				y > keys[i].y && y < keys[i].y + keys[i].h) {
    297 			return &keys[i];
    298 		}
    299 	}
    300 	return NULL;
    301 }
    302 
    303 ulong
    304 getcolor(const char *colstr) {
    305 	Colormap cmap = DefaultColormap(dpy, screen);
    306 	XColor color;
    307 
    308 	if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
    309 		die("error, cannot allocate color '%s'\n", colstr);
    310 	return color.pixel;
    311 }
    312 
    313 void
    314 initfont(const char *fontstr) {
    315 	char *def, **missing;
    316 	int i, n;
    317 
    318 	missing = NULL;
    319 	if(dc.font.set)
    320 		XFreeFontSet(dpy, dc.font.set);
    321 	dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
    322 	if(missing) {
    323 		while(n--)
    324 			fprintf(stderr, "svkbd: missing fontset: %s\n", missing[n]);
    325 		XFreeStringList(missing);
    326 	}
    327 	if(dc.font.set) {
    328 		XFontStruct **xfonts;
    329 		char **font_names;
    330 		dc.font.ascent = dc.font.descent = 0;
    331 		n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
    332 		for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
    333 			dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent);
    334 			dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent);
    335 			xfonts++;
    336 		}
    337 	} else {
    338 		if(dc.font.xfont)
    339 			XFreeFont(dpy, dc.font.xfont);
    340 		dc.font.xfont = NULL;
    341 		if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr))
    342 		&& !(dc.font.xfont = XLoadQueryFont(dpy, "fixed")))
    343 			die("error, cannot load font: '%s'\n", fontstr);
    344 		dc.font.ascent = dc.font.xfont->ascent;
    345 		dc.font.descent = dc.font.xfont->descent;
    346 	}
    347 	dc.font.height = dc.font.ascent + dc.font.descent;
    348 }
    349 
    350 void
    351 leavenotify(XEvent *e) {
    352 	unpress(NULL, 0);
    353 }
    354 
    355 void
    356 press(Key *k, KeySym mod) {
    357 	int i;
    358 	k->pressed = !k->pressed;
    359 
    360 	if(!IsModifierKey(k->keysym)) {
    361 		for(i = 0; i < LENGTH(keys); i++) {
    362 			if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    363 				XTestFakeKeyEvent(dpy,
    364 					XKeysymToKeycode(dpy, keys[i].keysym),
    365 					True, 0);
    366 			}
    367 		}
    368 		pressedmod = mod;
    369 		if(pressedmod) {
    370 			XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, mod),
    371 					True, 0);
    372 		}
    373 		XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, k->keysym), True, 0);
    374 
    375 		for(i = 0; i < LENGTH(keys); i++) {
    376 			if(keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    377 				XTestFakeKeyEvent(dpy,
    378 					XKeysymToKeycode(dpy, keys[i].keysym),
    379 					False, 0);
    380 			}
    381 		}
    382 	}
    383 	drawkey(k);
    384 }
    385 
    386 void
    387 unpress(Key *k, KeySym mod) {
    388 	int i;
    389 
    390 	if(k != NULL) {
    391 		switch(k->keysym) {
    392 		case XK_Cancel:
    393 			exit(0);
    394 		default:
    395 			break;
    396 		}
    397 	}
    398 
    399 	for(i = 0; i < LENGTH(keys); i++) {
    400 		if(keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
    401 			XTestFakeKeyEvent(dpy,
    402 				XKeysymToKeycode(dpy, keys[i].keysym),
    403 				False, 0);
    404 			keys[i].pressed = 0;
    405 			drawkey(&keys[i]);
    406 			break;
    407 		}
    408 	}
    409 	if(i != LENGTH(keys)) {
    410 		if(pressedmod) {
    411 			XTestFakeKeyEvent(dpy,
    412 				XKeysymToKeycode(dpy, pressedmod),
    413 				False, 0);
    414 		}
    415 		pressedmod = 0;
    416 
    417 		for(i = 0; i < LENGTH(keys); i++) {
    418 			if(keys[i].pressed) {
    419 				XTestFakeKeyEvent(dpy,
    420 					XKeysymToKeycode(dpy,
    421 						keys[i].keysym), False, 0);
    422 				keys[i].pressed = 0;
    423 				drawkey(&keys[i]);
    424 			}
    425 		}
    426 	}
    427 }
    428 
    429 void
    430 run(void) {
    431 	XEvent ev;
    432 
    433 	/* main event loop */
    434 	XSync(dpy, False);
    435 	while(running) {
    436 		XNextEvent(dpy, &ev);
    437 		if(handler[ev.type])
    438 			(handler[ev.type])(&ev); /* call handler */
    439 	}
    440 }
    441 
    442 void
    443 setup(void) {
    444 	XSetWindowAttributes wa;
    445 	XTextProperty str;
    446 	XSizeHints *sizeh = NULL;
    447 	XClassHint *ch;
    448 	Atom atype = -1;
    449 	int i, sh, sw;
    450 	XWMHints *wmh;
    451 
    452 	/* init screen */
    453 	screen = DefaultScreen(dpy);
    454 	root = RootWindow(dpy, screen);
    455 	sw = DisplayWidth(dpy, screen);
    456 	sh = DisplayHeight(dpy, screen);
    457 	initfont(font);
    458 
    459 	/* init atoms */
    460 	if(isdock) {
    461 		netatom[NetWMWindowType] = XInternAtom(dpy,
    462 				"_NET_WM_WINDOW_TYPE", False);
    463 		atype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
    464 	}
    465 
    466 	/* init appearance */
    467 	countrows();
    468 	if(!ww)
    469 		ww = sw;
    470 	if(!wh)
    471 		wh = sh * rows / 32;
    472 
    473 	if(!wx)
    474 		wx = 0;
    475 	if(wx < 0)
    476 		wx = sw + wx - ww;
    477 	if(!wy)
    478 		wy = sh - wh;
    479 	if(wy < 0)
    480 		wy = sh + wy - wh;
    481 
    482 	dc.norm[ColBG] = getcolor(normbgcolor);
    483 	dc.norm[ColFG] = getcolor(normfgcolor);
    484 	dc.press[ColBG] = getcolor(pressbgcolor);
    485 	dc.press[ColFG] = getcolor(pressfgcolor);
    486 	dc.high[ColBG] = getcolor(highlightbgcolor);
    487 	dc.high[ColFG] = getcolor(highlightfgcolor);
    488 	dc.drawable = XCreatePixmap(dpy, root, ww, wh,
    489 			DefaultDepth(dpy, screen));
    490 	dc.gc = XCreateGC(dpy, root, 0, 0);
    491 	if(!dc.font.set)
    492 		XSetFont(dpy, dc.gc, dc.font.xfont->fid);
    493 	for(i = 0; i < LENGTH(keys); i++)
    494 		keys[i].pressed = 0;
    495 
    496 	wa.override_redirect = !wmborder;
    497 	wa.border_pixel = dc.norm[ColFG];
    498 	wa.background_pixel = dc.norm[ColBG];
    499 	win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0,
    500 			    CopyFromParent, CopyFromParent, CopyFromParent,
    501 			    CWOverrideRedirect | CWBorderPixel |
    502 			    CWBackingPixel, &wa);
    503 	XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
    504 			ButtonPressMask|ExposureMask|LeaveWindowMask|
    505 			PointerMotionMask);
    506 
    507 	wmh = XAllocWMHints();
    508 	wmh->input = False;
    509 	wmh->flags = InputHint;
    510 	if(!isdock) {
    511 		sizeh = XAllocSizeHints();
    512 		sizeh->flags = PMaxSize | PMinSize;
    513 		sizeh->min_width = sizeh->max_width = ww;
    514 		sizeh->min_height = sizeh->max_height = wh;
    515 	}
    516 	XStringListToTextProperty(&name, 1, &str);
    517 	ch = XAllocClassHint();
    518 	ch->res_class = name;
    519 	ch->res_name = name;
    520 
    521 	XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh,
    522 			ch);
    523 
    524 	XFree(ch);
    525 	XFree(wmh);
    526 	XFree(str.value);
    527 	if(sizeh != NULL)
    528 		XFree(sizeh);
    529 
    530 	if(isdock) {
    531 		XChangeProperty(dpy, win, netatom[NetWMWindowType], XA_ATOM,
    532 				32, PropModeReplace,
    533 				(unsigned char *)&atype, 1);
    534 	}
    535 
    536 	XMapRaised(dpy, win);
    537 	updatekeys();
    538 	drawkeyboard();
    539 }
    540 
    541 int
    542 textnw(const char *text, uint len) {
    543 	XRectangle r;
    544 
    545 	if(dc.font.set) {
    546 		XmbTextExtents(dc.font.set, text, len, NULL, &r);
    547 		return r.width;
    548 	}
    549 	return XTextWidth(dc.font.xfont, text, len);
    550 }
    551 
    552 void
    553 updatekeys() {
    554 	int i, j;
    555 	int x = 0, y = 0, h, base, r = rows;
    556 
    557 	h = (wh - 1) / rows;
    558 	for(i = 0; i < LENGTH(keys); i++, r--) {
    559 		for(j = i, base = 0; j < LENGTH(keys) && keys[j].keysym != 0; j++)
    560 			base += keys[j].width;
    561 		for(x = 0; i < LENGTH(keys) && keys[i].keysym != 0; i++) {
    562 			keys[i].x = x;
    563 			keys[i].y = y;
    564 			keys[i].w = keys[i].width * (ww - 1) / base;
    565 			keys[i].h = r == 1 ? wh - y - 1 : h;
    566 			x += keys[i].w;
    567 		}
    568 		if(base != 0)
    569 			keys[i - 1].w = ww - 1 - keys[i - 1].x;
    570 		y += h;
    571 	}
    572 }
    573 
    574 void
    575 usage(char *argv0) {
    576 	fprintf(stderr, "usage: %s [-hdv] [-g geometry]\n", argv0);
    577 	exit(1);
    578 }
    579 
    580 int
    581 main(int argc, char *argv[]) {
    582 	int i, xr, yr, bitm;
    583 	unsigned int wr, hr;
    584 
    585 	for (i = 1; argv[i]; i++) {
    586 		if(!strcmp(argv[i], "-v")) {
    587 			die("svkbd-"VERSION", © 2006-2016 svkbd engineers,"
    588 				       " see LICENSE for details\n");
    589 		} else if(!strcmp(argv[i], "-d")) {
    590 			isdock = True;
    591 			continue;
    592 		} else if(!strncmp(argv[i], "-g", 2)) {
    593 			if(i >= argc - 1)
    594 				continue;
    595 
    596 			bitm = XParseGeometry(argv[i+1], &xr, &yr, &wr, &hr);
    597 			if(bitm & XValue)
    598 				wx = xr;
    599 			if(bitm & YValue)
    600 				wy = yr;
    601 			if(bitm & WidthValue)
    602 				ww = (int)wr;
    603 			if(bitm & HeightValue)
    604 				wh = (int)hr;
    605 			if(bitm & XNegative && wx == 0)
    606 				wx = -1;
    607 			if(bitm & YNegative && wy == 0)
    608 				wy = -1;
    609 			i++;
    610 		} else if(!strcmp(argv[i], "-h")) {
    611 			usage(argv[0]);
    612 		}
    613 	}
    614 
    615 	if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
    616 		fprintf(stderr, "warning: no locale support\n");
    617 	if(!(dpy = XOpenDisplay(0)))
    618 		die("svkbd: cannot open display\n");
    619 	setup();
    620 	run();
    621 	cleanup();
    622 	XCloseDisplay(dpy);
    623 	return 0;
    624 }
    625