svkbd

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

svkbd.c (29833B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <sys/select.h>
      3 #include <sys/time.h>
      4 
      5 #include <ctype.h>
      6 #include <locale.h>
      7 #include <signal.h>
      8 #include <stdarg.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <time.h>
     13 #include <unistd.h>
     14 
     15 #include <X11/keysym.h>
     16 #include <X11/keysymdef.h>
     17 #include <X11/XF86keysym.h>
     18 #include <X11/Xatom.h>
     19 #include <X11/Xlib.h>
     20 #include <X11/Xutil.h>
     21 #include <X11/Xproto.h>
     22 #include <X11/extensions/XTest.h>
     23 #include <X11/Xft/Xft.h>
     24 #include <X11/Xresource.h>
     25 #ifdef XINERAMA
     26 #include <X11/extensions/Xinerama.h>
     27 #endif
     28 
     29 #include "drw.h"
     30 #include "util.h"
     31 
     32 /* macros */
     33 #define LENGTH(x)         (sizeof x / sizeof x[0])
     34 #define STRINGTOKEYSYM(X) (XStringToKeySym(X))
     35 #define TEXTW(X)          (drw_fontset_getwidth(drw, (X)))
     36 
     37 /* enums */
     38 enum {
     39 	SchemeNorm, SchemeNormABC, SchemeNormABCShift, SchemeNormShift, SchemePress,
     40 	SchemePressShift, SchemeHighlight, SchemeHighlightShift, SchemeOverlay,
     41 	SchemeOverlayShift, SchemeLast
     42 };
     43 enum { NetWMWindowType, NetLast };
     44 
     45 /* typedefs */
     46 typedef struct {
     47 	char *label;
     48 	char *label2;
     49 	KeySym keysym;
     50 	unsigned int width;
     51 	KeySym modifier;
     52 	int x, y, w, h;
     53 	Bool pressed;
     54 	Bool highlighted;
     55 	Bool isoverlay;
     56 } Key;
     57 
     58 typedef struct {
     59 	KeySym mod;
     60 	unsigned int button;
     61 } Buttonmod;
     62 
     63 /* function declarations */
     64 static void printdbg(const char *fmt, ...);
     65 static void motionnotify(XEvent *e);
     66 static void buttonpress(XEvent *e);
     67 static void buttonrelease(XEvent *e);
     68 static void cleanup(void);
     69 static void configurenotify(XEvent *e);
     70 static void countrows();
     71 static int countkeys(Key *layer);
     72 static void drawkeyboard(void);
     73 static void drawkey(Key *k);
     74 static void expose(XEvent *e);
     75 static Key *findkey(int x, int y);
     76 static void leavenotify(XEvent *e);
     77 static void press(Key *k, KeySym mod);
     78 static double get_press_duration();
     79 static void run(void);
     80 static void setup(void);
     81 static void simulate_keypress(KeySym keysym);
     82 static void simulate_keyrelease(KeySym keysym);
     83 static void showoverlay(int idx);
     84 static void hideoverlay();
     85 static void cyclelayer();
     86 static void setlayer();
     87 static void togglelayer();
     88 static void unpress(Key *k, KeySym mod);
     89 static void updatekeys();
     90 static void printkey(Key *k, KeySym mod);
     91 
     92 /* variables */
     93 static int screen;
     94 static void (*handler[LASTEvent]) (XEvent *) = {
     95 	[ButtonPress] = buttonpress,
     96 	[ButtonRelease] = buttonrelease,
     97 	[ConfigureNotify] = configurenotify,
     98 	[Expose] = expose,
     99 	[LeaveNotify] = leavenotify,
    100 	[MotionNotify] = motionnotify
    101 };
    102 static Atom netatom[NetLast];
    103 static Display *dpy;
    104 static Drw *drw;
    105 static Window root, win;
    106 static Clr* scheme[SchemeLast];
    107 static Bool running = True, isdock = False;
    108 static struct timeval pressbegin;
    109 static int currentlayer = 0;
    110 static int enableoverlays = 1;
    111 static int currentoverlay = -1; /* -1 = no overlay */
    112 static int pressonrelease = 1;
    113 static KeySym overlaykeysym = 0; /* keysym for which the overlay is presented */
    114 static int releaseprotect = 0; /* set to 1 after overlay is shown, protecting against immediate release */
    115 static int tmp_keycode = 1;
    116 static int rows = 0, ww = 0, wh = 0, wx = 0, wy = 0;
    117 static int simulateoutput = 1; /* simulate key presses for X */
    118 static int printoutput = 0; /* print key pressed to stdout */
    119 static char *name = "svkbd";
    120 static int debug = 0;
    121 static int numlayers = 0;
    122 static int numkeys = 0;
    123 
    124 static char *colors[10][2]; /* 10 schemes, 2 colors each */
    125 static char *fonts[] = { 0 };
    126 
    127 static KeySym ispressingkeysym;
    128 
    129 Bool ispressing = False;
    130 Bool sigtermd = False;
    131 
    132 /* configuration, allows nested code to access above variables */
    133 #include "config.h"
    134 #ifndef LAYOUT
    135 #error "make sure to define LAYOUT"
    136 #endif
    137 #include LAYOUT
    138 
    139 static Key keys[KEYS] = { NULL };
    140 static Key* layers[LAYERS];
    141 
    142 void
    143 motionnotify(XEvent *e)
    144 {
    145 	XPointerMovedEvent *ev = &e->xmotion;
    146 	int i;
    147 	int lostfocus = -1;
    148 	int gainedfocus = -1;
    149 
    150 	for (i = 0; i < numkeys; i++) {
    151 		if (keys[i].keysym && ev->x > keys[i].x
    152 				&& ev->x < keys[i].x + keys[i].w
    153 				&& ev->y > keys[i].y
    154 				&& ev->y < keys[i].y + keys[i].h) {
    155 			if (keys[i].highlighted != True) {
    156 				if (ispressing) {
    157 					gainedfocus = i;
    158 				} else {
    159 					keys[i].highlighted = True;
    160 				}
    161 				drawkey(&keys[i]);
    162 			}
    163 			continue;
    164 		} else if (keys[i].highlighted == True) {
    165 			keys[i].highlighted = False;
    166 			drawkey(&keys[i]);
    167 		}
    168 	}
    169 
    170 	for (i = 0; i < numkeys; i++) {
    171 		if (!IsModifierKey(keys[i].keysym) && keys[i].pressed == True && lostfocus != gainedfocus) {
    172 			printdbg("Pressed key lost focus: %ld\n", keys[i].keysym);
    173 			lostfocus = i;
    174 			ispressingkeysym = 0;
    175 			keys[i].pressed = 0;
    176 			drawkey(&keys[i]);
    177 		}
    178 	}
    179 
    180 	if ((lostfocus != -1) && (gainedfocus != -1) && (lostfocus != gainedfocus)) {
    181 		printdbg("Clicking new key that gained focus\n");
    182 		press(&keys[gainedfocus], 0);
    183 		keys[gainedfocus].pressed = True;
    184 		keys[gainedfocus].highlighted = True;
    185 	}
    186 }
    187 
    188 void
    189 buttonpress(XEvent *e)
    190 {
    191 	XButtonPressedEvent *ev = &e->xbutton;
    192 	Key *k;
    193 	KeySym mod = 0;
    194 	int i;
    195 
    196 	ispressing = True;
    197 
    198 	if (!(k = findkey(ev->x, ev->y)))
    199 		return;
    200 
    201 	if (k->modifier) {
    202 		mod = k->modifier;
    203 	} else {
    204 		for (i = 0; i < LENGTH(buttonmods); i++) {
    205 			if (ev->button == buttonmods[i].button) {
    206 				mod = buttonmods[i].mod;
    207 				break;
    208 			}
    209 		}
    210 	}
    211 	press(k, mod);
    212 }
    213 
    214 void
    215 buttonrelease(XEvent *e)
    216 {
    217 	XButtonPressedEvent *ev = &e->xbutton;
    218 	Key *k;
    219 	KeySym mod = 0;
    220 	int i;
    221 
    222 	ispressing = False;
    223 
    224 	for (i = 0; i < LENGTH(buttonmods); i++) {
    225 		if (ev->button == buttonmods[i].button) {
    226 			mod = buttonmods[i].mod;
    227 			break;
    228 		}
    229 	}
    230 
    231 	if (ev->x < 0 || ev->y < 0) {
    232 		unpress(NULL, mod);
    233 	} else if ((k = findkey(ev->x, ev->y))) {
    234 		if (k->modifier)
    235 			unpress(k, k->modifier);
    236 		else
    237 			unpress(k, mod);
    238 	}
    239 }
    240 
    241 void
    242 cleanup(void)
    243 {
    244 	int i;
    245 
    246 	for (i = 0; i < SchemeLast; i++)
    247 		free(scheme[i]);
    248 	drw_sync(drw);
    249 	drw_free(drw);
    250 	XSync(dpy, False);
    251 	XDestroyWindow(dpy, win);
    252 	XSync(dpy, False);
    253 	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
    254 }
    255 
    256 void
    257 configurenotify(XEvent *e)
    258 {
    259 	XConfigureEvent *ev = &e->xconfigure;
    260 
    261 	if (ev->window == win && (ev->width != ww || ev->height != wh)) {
    262 		ww = ev->width;
    263 		wh = ev->height;
    264 		drw_resize(drw, ww, wh);
    265 		updatekeys();
    266 	}
    267 }
    268 
    269 void
    270 countrows(void)
    271 {
    272 	int i;
    273 
    274 	for (i = 0, rows = 1; i < numkeys; i++) {
    275 		if (keys[i].keysym == 0) {
    276 			rows++;
    277 			if ((i > 0) && (keys[i-1].keysym == 0)) {
    278 				rows--;
    279 				break;
    280 			}
    281 		}
    282 	}
    283 }
    284 
    285 int
    286 countkeys(Key *layer)
    287 {
    288 	int i, nkeys = 0;
    289 
    290 	for (i = 0; i < KEYS; i++) {
    291 		if (i > 0 && layer[i].keysym == 0 && layer[i - 1].keysym == 0) {
    292 			nkeys--;
    293 			break;
    294 		}
    295 		nkeys++;
    296 	}
    297 
    298 	return nkeys;
    299 }
    300 
    301 void
    302 drawkeyboard(void)
    303 {
    304 	int i;
    305 
    306 	for (i = 0; i < numkeys; i++) {
    307 		if (keys[i].keysym != 0)
    308 			drawkey(&keys[i]);
    309 	}
    310 }
    311 
    312 void
    313 drawkey(Key *k)
    314 {
    315 	int x, y, w, h;
    316 	const char *l;
    317 
    318 	int use_scheme = SchemeNorm;
    319 
    320 	if (k->pressed)
    321 		use_scheme = SchemePress;
    322 	else if (k->highlighted)
    323 		use_scheme = SchemeHighlight;
    324 	else if (k->isoverlay)
    325 		use_scheme = SchemeOverlay;
    326 	else if ((k->keysym == XK_Return) ||
    327 			((k->keysym >= XK_a) && (k->keysym <= XK_z)) ||
    328 			((k->keysym >= XK_Cyrillic_io) && (k->keysym <= XK_Cyrillic_hardsign)))
    329 		use_scheme = SchemeNormABC;
    330 	else
    331 		use_scheme = SchemeNorm;
    332 
    333 	drw_setscheme(drw, scheme[use_scheme]);
    334 	drw_rect(drw, k->x, k->y, k->w, k->h, 1, 1);
    335 
    336 	if (k->keysym == XK_KP_Insert) {
    337 		if (enableoverlays) {
    338 			l = "≅";
    339 		} else {
    340 			l = "≇";
    341 		}
    342 	} else if (k->label) {
    343 		l = k->label;
    344 	} else {
    345 		l = XKeysymToString(k->keysym);
    346 	}
    347 	h = fontsize * 2;
    348 	y = k->y + (k->h / 2) - (h / 2);
    349 	w = TEXTW(l);
    350 	x = k->x + (k->w / 2) - (w / 2);
    351 	drw_text(drw, x, y, w, h, 0, l, 0);
    352 	if (k->label2) {
    353 		if (use_scheme == SchemeNorm)
    354 			use_scheme = SchemeNormShift;
    355 		else if (use_scheme == SchemeNormABC)
    356 			use_scheme = SchemeNormABCShift;
    357 		else if (use_scheme == SchemePress)
    358 			use_scheme = SchemePressShift;
    359 		else if (use_scheme == SchemeHighlight)
    360 			use_scheme = SchemeHighlightShift;
    361 		else if (use_scheme == SchemeOverlay)
    362 			use_scheme = SchemeOverlayShift;
    363 		drw_setscheme(drw, scheme[use_scheme]);
    364 		x += w;
    365 		y -= 15;
    366 		l = k->label2;
    367 		w = TEXTW(l);
    368 		drw_text(drw, x, y, w, h, 0, l, 0);
    369 	}
    370 	drw_map(drw, win, k->x, k->y, k->w, k->h);
    371 }
    372 
    373 void
    374 expose(XEvent *e)
    375 {
    376 	XExposeEvent *ev = &e->xexpose;
    377 
    378 	if (ev->count == 0 && (ev->window == win))
    379 		drawkeyboard();
    380 }
    381 
    382 Key *
    383 findkey(int x, int y) {
    384 	int i;
    385 
    386 	for (i = 0; i < numkeys; i++) {
    387 		if (keys[i].keysym && x > keys[i].x &&
    388 				x < keys[i].x + keys[i].w &&
    389 				y > keys[i].y && y < keys[i].y + keys[i].h) {
    390 			return &keys[i];
    391 		}
    392 	}
    393 	return NULL;
    394 }
    395 
    396 int
    397 hasoverlay(KeySym keysym)
    398 {
    399 	int begin, i;
    400 
    401 	begin = 0;
    402 	for (i = 0; i < OVERLAYS; i++) {
    403 		if (overlay[i].keysym == XK_Cancel) {
    404 			begin = i + 1;
    405 		} else if (overlay[i].keysym == keysym) {
    406 			return begin + 1;
    407 		}
    408 	}
    409 	return -1;
    410 }
    411 
    412 void
    413 leavenotify(XEvent *e)
    414 {
    415 	if (currentoverlay != -1)
    416 		hideoverlay();
    417 	ispressingkeysym = 0;
    418 	unpress(NULL, 0);
    419 }
    420 
    421 void
    422 record_press_begin(KeySym ks)
    423 {
    424 	/* record the begin of the press, don't simulate the actual keypress yet */
    425 	gettimeofday(&pressbegin, NULL);
    426 	ispressingkeysym = ks;
    427 }
    428 
    429 void
    430 press(Key *k, KeySym buttonmod)
    431 {
    432 	int i;
    433 	int overlayidx = -1;
    434 
    435 	k->pressed = !k->pressed;
    436 
    437 	printdbg("Begin click: %ld\n", k->keysym);
    438 	pressbegin.tv_sec = 0;
    439 	pressbegin.tv_usec = 0;
    440 	ispressingkeysym = 0;
    441 
    442 	if (!IsModifierKey(k->keysym)) {
    443 		if (enableoverlays && currentoverlay == -1)
    444 			overlayidx = hasoverlay(k->keysym);
    445 		if ((pressonrelease) || (enableoverlays && overlayidx != -1)) {
    446 			/*record the begin of the press, don't simulate the actual keypress yet */
    447 			record_press_begin(k->keysym);
    448 		} else {
    449 			printdbg("Simulating press: %ld (mod %ld)\n", k->keysym, buttonmod);
    450 			for (i = 0; i < numkeys; i++) {
    451 				if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    452 					simulate_keypress(keys[i].keysym);
    453 				}
    454 			}
    455 			if (buttonmod)
    456 				simulate_keypress(buttonmod);
    457 			simulate_keypress(k->keysym);
    458 			if (printoutput)
    459 				printkey(k, buttonmod);
    460 
    461 			for (i = 0; i < numkeys; i++) {
    462 				if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    463 					simulate_keyrelease(keys[i].keysym);
    464 				}
    465 			}
    466 		}
    467 	}
    468 	drawkey(k);
    469 }
    470 
    471 int
    472 tmp_remap(KeySym keysym)
    473 {
    474 	XChangeKeyboardMapping(dpy, tmp_keycode, 1, &keysym, 1);
    475 	XSync(dpy, False);
    476 
    477 	return tmp_keycode;
    478 }
    479 
    480 void
    481 printkey(Key *k, KeySym mod)
    482 {
    483 	int i, shift;
    484 
    485 	shift = (mod == XK_Shift_L) || (mod == XK_Shift_R) || (mod == XK_Shift_Lock);
    486 	if (!shift) {
    487 		for (i = 0; i < numkeys; i++) {
    488 			if ((keys[i].pressed) && ((keys[i].keysym == XK_Shift_L) ||
    489 			    (keys[i].keysym == XK_Shift_R) || (keys[i].keysym == XK_Shift_Lock))) {
    490 				shift = True;
    491 				break;
    492 			}
    493 		}
    494 	}
    495 	printdbg("Printing key %ld (shift=%d)\n", k->keysym, shift);
    496 	if (k->keysym == XK_Cancel)
    497 		return;
    498 
    499 	KeySym *keysym = &(k->keysym);
    500 	XIM xim = XOpenIM(dpy, 0, 0, 0);
    501 	XIC xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
    502 
    503 	XKeyPressedEvent event;
    504 	event.type = KeyPress;
    505 	event.display = dpy;
    506 	event.state = shift ? ShiftMask : 0;
    507 	event.keycode = XKeysymToKeycode(dpy, *keysym);
    508 	if (event.keycode == 0)
    509 		event.keycode = tmp_remap(*keysym);
    510 
    511 	char buffer[32];
    512 	KeySym ignore;
    513 	Status return_status;
    514 	int l = Xutf8LookupString(xic, &event, buffer, sizeof(buffer), &ignore, &return_status);
    515 	buffer[l] = '\0';
    516 	printdbg("Print buffer: [%s] (length=%d)\n", &buffer, l);
    517 	printf("%s", buffer);
    518 
    519 	XDestroyIC(xic);
    520 	XCloseIM(xim);
    521 }
    522 
    523 void
    524 simulate_keypress(KeySym keysym)
    525 {
    526 	KeyCode code;
    527 
    528 	if (!simulateoutput)
    529 		return;
    530 
    531 	code = XKeysymToKeycode(dpy, keysym);
    532 	if (code == 0)
    533 		code = tmp_remap(keysym);
    534 	XTestFakeKeyEvent(dpy, code, True, 0);
    535 }
    536 
    537 void
    538 simulate_keyrelease(KeySym keysym)
    539 {
    540 	KeyCode code;
    541 
    542 	if (!simulateoutput)
    543 		return;
    544 
    545 	code = XKeysymToKeycode(dpy, keysym);
    546 	if (code == 0)
    547 		code = tmp_remap(keysym);
    548 	XTestFakeKeyEvent(dpy, code, False, 0);
    549 }
    550 
    551 double
    552 get_press_duration(void)
    553 {
    554 	struct timeval now;
    555 
    556 	gettimeofday(&now, NULL);
    557 
    558 	return (double) ((now.tv_sec * 1000000L + now.tv_usec) -
    559 		   (pressbegin.tv_sec * 1000000L + pressbegin.tv_usec)) /
    560 		   (double) 1000000L;
    561 }
    562 
    563 void
    564 unpress(Key *k, KeySym buttonmod)
    565 {
    566 	int i;
    567 
    568 	if (k != NULL) {
    569 		switch(k->keysym) {
    570 		case XK_Cancel:
    571 			cyclelayer();
    572 			break;
    573 		case XK_script_switch:
    574 			togglelayer();
    575 			break;
    576 		case XK_KP_Insert:
    577 			enableoverlays = !enableoverlays;
    578 			break;
    579 		case XK_Break:
    580 			running = False;
    581 			break;
    582 		default:
    583 			break;
    584 		}
    585 	}
    586 
    587 	if ((pressbegin.tv_sec || pressbegin.tv_usec) && (enableoverlays || pressonrelease) && k && k->keysym == ispressingkeysym) {
    588 		printdbg("Delayed simulation of press after release: %ld\n", k->keysym);
    589 		/* simulate the press event, as we postponed it earlier in press() */
    590 		for (i = 0; i < numkeys; i++) {
    591 			if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    592 				simulate_keypress(keys[i].keysym);
    593 			}
    594 		}
    595 		if (buttonmod) {
    596 			simulate_keypress(buttonmod);
    597 		}
    598 		simulate_keypress(k->keysym);
    599 		pressbegin.tv_sec = 0;
    600 		pressbegin.tv_usec = 0;
    601 	}
    602 
    603 	if (k)
    604 		printdbg("Simulation of release: %ld\n", k->keysym);
    605 	else
    606 		printdbg("Simulation of release (all keys)\n");
    607 
    608 	for (i = 0; i < numkeys; i++) {
    609 		if (keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
    610 			simulate_keyrelease(keys[i].keysym);
    611 			if (printoutput)
    612 				printkey(&keys[i], buttonmod);
    613 			keys[i].pressed = 0;
    614 			drawkey(&keys[i]);
    615 		}
    616 	}
    617 
    618 	if (buttonmod) {
    619 		simulate_keyrelease(buttonmod);
    620 	}
    621 
    622 	if ((k == NULL) || (!IsModifierKey(k->keysym))) {
    623 		for (i = 0; i < numkeys; i++) {
    624 			if (keys[i].pressed && IsModifierKey(keys[i].keysym)) {
    625 				simulate_keyrelease(keys[i].keysym);
    626 				keys[i].pressed = 0;
    627 				drawkey(&keys[i]);
    628 			}
    629 		}
    630 	}
    631 
    632 	if (enableoverlays && currentoverlay != -1 && !IsModifierKey(k->keysym)) {
    633 		if (releaseprotect) {
    634 			releaseprotect = 0;
    635 		} else {
    636 			hideoverlay();
    637 		}
    638 	}
    639 }
    640 
    641 void
    642 run(void)
    643 {
    644 	XEvent ev;
    645 	int xfd;
    646 	fd_set fds;
    647 	struct timeval tv;
    648 	double duration = 0.0;
    649 	int overlayidx = -1;
    650 	int i, r;
    651 
    652 	xfd = ConnectionNumber(dpy);
    653 	tv.tv_sec = 0;
    654 	tv.tv_usec = scan_rate;
    655 
    656 	XFlush(dpy);
    657 
    658 	while (running) {
    659 		usleep(100000L); /* 100ms */
    660 		FD_ZERO(&fds);
    661 		FD_SET(xfd, &fds);
    662 		r = select(xfd + 1, &fds, NULL, NULL, &tv);
    663 		if (r) {
    664 			while (XPending(dpy)) {
    665 				XNextEvent(dpy, &ev);
    666 				if (handler[ev.type]) {
    667 					(handler[ev.type])(&ev); /* call handler */
    668 				}
    669 			}
    670 		} else {
    671 			/* time-out expired without anything interesting happening, check for long-presses */
    672 			if (ispressing && ispressingkeysym) {
    673 				duration = get_press_duration();
    674 				if (debug >= 2)
    675 					printdbg("%f\n", duration);
    676 				overlayidx = hasoverlay(ispressingkeysym);
    677 				duration = get_press_duration();
    678 				if ((overlayidx != -1) && (duration >= overlay_delay)) {
    679 					printdbg("press duration %f, activating overlay\n", duration);
    680 					showoverlay(overlayidx);
    681 					pressbegin.tv_sec = 0;
    682 					pressbegin.tv_usec = 0;
    683 					ispressingkeysym = 0;
    684 				} else if ((overlayidx == -1) && (duration >= repeat_delay)) {
    685 					printdbg("press duration %f, activating repeat\n", duration);
    686 					simulate_keyrelease(ispressingkeysym);
    687 					simulate_keypress(ispressingkeysym);
    688 					XSync(dpy, False);
    689 				}
    690 			}
    691 		}
    692 		if (r == -1 || sigtermd) {
    693 			/* an error occurred or we received a signal */
    694 			/* E.g. Generally in scripts we want to call SIGTERM on svkbd in which case
    695 					if the user is holding for example the enter key (to execute
    696 					the kill or script that does the kill), that causes an issue
    697 					since then X doesn't know the keyup is never coming.. (since
    698 					process will be dead before finger lifts - in that case we
    699 					just trigger out fake up presses for all keys */
    700 			printdbg("signal received, releasing all keys");
    701 			for (i = 0; i < numkeys; i++) {
    702 				XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, keys[i].keysym), False, 0);
    703 			}
    704 			running = False;
    705 		}
    706 	}
    707 }
    708 
    709 void
    710 readxresources(void)
    711 {
    712 	XrmDatabase xdb;
    713 	XrmValue xval;
    714 	char *type, *xrm;
    715 
    716 	XrmInitialize();
    717 
    718 	if ((xrm = XResourceManagerString(drw->dpy))) {
    719 		xdb = XrmGetStringDatabase(xrm);
    720 
    721 		if (XrmGetResource(xdb, "svkbd.font", "*", &type, &xval) && !fonts[0])
    722 			fonts[0] = estrdup(xval.addr);
    723 
    724 		if (XrmGetResource(xdb, "svkbd.background", "*", &type, &xval) && !colors[SchemeNorm][ColBg])
    725 			colors[SchemeNorm][ColBg] = estrdup(xval.addr);
    726 		if (XrmGetResource(xdb, "svkbd.foreground", "*", &type, &xval) && !colors[SchemeNorm][ColFg])
    727 			colors[SchemeNorm][ColFg] = estrdup(xval.addr);
    728 
    729 		if (XrmGetResource(xdb, "svkbd.shiftforeground", "*", &type, &xval) && !colors[SchemeNormShift][ColFg])
    730 			colors[SchemeNormShift][ColFg] = estrdup(xval.addr);
    731 		if (XrmGetResource(xdb, "svkbd.shiftbackground", "*", &type, &xval) && !colors[SchemeNormShift][ColBg])
    732 			colors[SchemeNormShift][ColBg] = estrdup(xval.addr);
    733 
    734 		if (XrmGetResource(xdb, "svkbd.ABCforeground", "*", &type, &xval) && !colors[SchemeNormABC][ColFg])
    735 			colors[SchemeNormABC][ColFg] = estrdup(xval.addr);
    736 		if (XrmGetResource(xdb, "svkbd.ABCbackground", "*", &type, &xval) && !colors[SchemeNormABC][ColBg])
    737 			colors[SchemeNormABC][ColBg] = estrdup(xval.addr);
    738 
    739 		if (XrmGetResource(xdb, "svkbd.ABCshiftforeground", "*", &type, &xval) && !colors[SchemeNormShift][ColFg])
    740 			colors[SchemeNormShift][ColFg] = estrdup(xval.addr);
    741 		if (XrmGetResource(xdb, "svkbd.ABCshiftbackground", "*", &type, &xval) && !colors[SchemeNormShift][ColBg])
    742 			colors[SchemeNormShift][ColBg] = estrdup(xval.addr);
    743 
    744 		if (XrmGetResource(xdb, "svkbd.pressbackground", "*", &type, &xval) && !colors[SchemePress][ColBg])
    745 			colors[SchemePress][ColBg] = estrdup(xval.addr);
    746 		if (XrmGetResource(xdb, "svkbd.pressforeground", "*", &type, &xval) && !colors[SchemePress][ColFg])
    747 			colors[SchemePress][ColFg] = estrdup(xval.addr);
    748 
    749 		if (XrmGetResource(xdb, "svkbd.pressshiftbackground", "*", &type, &xval) && !colors[SchemePressShift][ColBg])
    750 			colors[SchemePressShift][ColBg] = estrdup(xval.addr);
    751 		if (XrmGetResource(xdb, "svkbd.pressshiftforeground", "*", &type, &xval) && !colors[SchemePressShift][ColFg])
    752 			colors[SchemePressShift][ColFg] = estrdup(xval.addr);
    753 
    754 		if (XrmGetResource(xdb, "svkbd.highlightbackground", "*", &type, &xval) && !colors[SchemeHighlight][ColBg])
    755 			colors[SchemeHighlight][ColBg] = estrdup(xval.addr);
    756 		if (XrmGetResource(xdb, "svkbd.highlightforeground", "*", &type, &xval) && !colors[SchemeHighlight][ColFg])
    757 			colors[SchemeHighlight][ColFg] = estrdup(xval.addr);
    758 
    759 		if (XrmGetResource(xdb, "svkbd.highlightshiftbackground", "*", &type, &xval) && !colors[SchemeHighlightShift][ColBg])
    760 			colors[SchemeHighlightShift][ColBg] = estrdup(xval.addr);
    761 		if (XrmGetResource(xdb, "svkbd.highlightshiftforeground", "*", &type, &xval) && !colors[SchemeHighlightShift][ColFg])
    762 			colors[SchemeHighlightShift][ColFg] = estrdup(xval.addr);
    763 
    764 		if (XrmGetResource(xdb, "svkbd.overlaybackground", "*", &type, &xval) && !colors[SchemeOverlay][ColBg])
    765 			colors[SchemeOverlay][ColBg] = estrdup(xval.addr);
    766 		if (XrmGetResource(xdb, "svkbd.overlayforeground", "*", &type, &xval) && !colors[SchemeOverlay][ColFg])
    767 			colors[SchemeOverlay][ColFg] = estrdup(xval.addr);
    768 
    769 		if (XrmGetResource(xdb, "svkbd.overlayshiftbackground", "*", &type, &xval) && !colors[SchemeOverlayShift][ColBg])
    770 			colors[SchemeOverlayShift][ColBg] = estrdup(xval.addr);
    771 		if (XrmGetResource(xdb, "svkbd.overlayshiftforeground", "*", &type, &xval) && !colors[SchemeOverlayShift][ColFg])
    772 			colors[SchemeOverlayShift][ColFg] = estrdup(xval.addr);
    773 
    774 		XrmDestroyDatabase(xdb);
    775 	}
    776 }
    777 
    778 void
    779 setup(void)
    780 {
    781 	XSetWindowAttributes wa;
    782 	XTextProperty str;
    783 	XSizeHints *sizeh = NULL;
    784 	XClassHint *ch;
    785 	XWMHints *wmh;
    786 	Atom atype = -1;
    787 	int i, j, sh, sw;
    788 
    789 #ifdef XINERAMA
    790 	XineramaScreenInfo *info = NULL;
    791 #endif
    792 
    793 	/* init screen */
    794 	screen = DefaultScreen(dpy);
    795 	root = RootWindow(dpy, screen);
    796 #ifdef XINERAMA
    797 	if (XineramaIsActive(dpy)) {
    798 		info = XineramaQueryScreens(dpy, &i);
    799 		sw = info[0].width;
    800 		sh = info[0].height;
    801 		XFree(info);
    802 	} else
    803 #endif
    804 	{
    805 		sw = DisplayWidth(dpy, screen);
    806 		sh = DisplayHeight(dpy, screen);
    807 	}
    808 	drw = drw_create(dpy, screen, root, sw, sh);
    809 
    810 	readxresources();
    811 
    812 	/* Apply defaults to font and colors*/
    813 	if (!fonts[0])
    814 		fonts[0] = estrdup(defaultfonts[0]);
    815 	for (i = 0; i < SchemeLast; ++i) {
    816 		for (j = 0; j < 2; ++j) {
    817 			if (!colors[i][j])
    818 				colors[i][j] = estrdup(defaultcolors[i][j]);
    819 		}
    820 	}
    821 
    822 	if (!drw_fontset_create(drw, (const char **) fonts, LENGTH(fonts)))
    823 		die("no fonts could be loaded");
    824 	free(fonts[0]);
    825 
    826 	drw_setscheme(drw, scheme[SchemeNorm]);
    827 
    828 	/* find an unused keycode to use as a temporary keycode (derived from source:
    829 	   https://stackoverflow.com/questions/44313966/c-xtest-emitting-key-presses-for-every-unicode-character) */
    830 	KeySym *keysyms;
    831 	int keysyms_per_keycode = 0;
    832 	int keycode_low, keycode_high;
    833 	Bool key_is_empty;
    834 	int symindex;
    835 
    836 	XDisplayKeycodes(dpy, &keycode_low, &keycode_high);
    837 	keysyms = XGetKeyboardMapping(dpy, keycode_low, keycode_high - keycode_low, &keysyms_per_keycode);
    838 	for (i = keycode_low; i <= keycode_high; i++) {
    839 		key_is_empty = True;
    840 		for (j = 0; j < keysyms_per_keycode; j++) {
    841 			symindex = (i - keycode_low) * keysyms_per_keycode + j;
    842 			if (keysyms[symindex] != 0) {
    843 				key_is_empty = False;
    844 			} else {
    845 				break;
    846 			}
    847 		}
    848 		if (key_is_empty) {
    849 			tmp_keycode = i;
    850 			break;
    851 		}
    852 	}
    853 
    854 	/* init appearance */
    855 	for (j = 0; j < SchemeLast; j++)
    856 		scheme[j] = drw_scm_create(drw, (const char **) colors[j], 2);
    857 
    858 	for (j = 0; j < SchemeLast; ++j) {
    859 		free(colors[j][ColFg]);
    860 		free(colors[j][ColBg]);
    861 	}
    862 
    863 	/* init atoms */
    864 	if (isdock) {
    865 		netatom[NetWMWindowType] = XInternAtom(dpy,
    866 				"_NET_WM_WINDOW_TYPE", False);
    867 		atype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
    868 	}
    869 
    870 	/* init appearance */
    871 	countrows();
    872 	if (!ww)
    873 		ww = sw;
    874 	if (!wh)
    875 		wh = sh * rows / heightfactor;
    876 
    877 	if (!wx)
    878 		wx = 0;
    879 	if (wx < 0)
    880 		wx = sw + wx - ww;
    881 	if (!wy)
    882 		wy = sh - wh;
    883 	if (wy < 0)
    884 		wy = sh + wy - wh;
    885 
    886 	for (i = 0; i < numkeys; i++)
    887 		keys[i].pressed = 0;
    888 
    889 	wa.override_redirect = !wmborder;
    890 	wa.border_pixel = scheme[SchemeNorm][ColFg].pixel;
    891 	wa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
    892 	win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0,
    893 			CopyFromParent, CopyFromParent, CopyFromParent,
    894 			CWOverrideRedirect | CWBorderPixel |
    895 			CWBackingPixel, &wa);
    896 	XSelectInput(dpy, win, StructureNotifyMask|ButtonReleaseMask|
    897 			ButtonPressMask|ExposureMask|LeaveWindowMask|
    898 			PointerMotionMask);
    899 
    900 	wmh = XAllocWMHints();
    901 	wmh->input = False;
    902 	wmh->flags = InputHint;
    903 	if (!isdock) {
    904 		sizeh = XAllocSizeHints();
    905 		sizeh->flags = PMaxSize | PMinSize;
    906 		sizeh->min_width = sizeh->max_width = ww;
    907 		sizeh->min_height = sizeh->max_height = wh;
    908 	}
    909 	XStringListToTextProperty(&name, 1, &str);
    910 	ch = XAllocClassHint();
    911 	ch->res_class = name;
    912 	ch->res_name = name;
    913 
    914 	XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, wmh, ch);
    915 
    916 	XFree(keysyms);
    917 	XFree(ch);
    918 	XFree(wmh);
    919 	XFree(str.value);
    920 	if (sizeh != NULL)
    921 		XFree(sizeh);
    922 
    923 	if (isdock) {
    924 		XChangeProperty(dpy, win, netatom[NetWMWindowType], XA_ATOM,
    925 				32, PropModeReplace,
    926 				(unsigned char *)&atype, 1);
    927 	}
    928 
    929 	XMapRaised(dpy, win);
    930 	drw_resize(drw, ww, wh);
    931 	updatekeys();
    932 	drawkeyboard();
    933 }
    934 
    935 void
    936 updatekeys(void)
    937 {
    938 	int i, j;
    939 	int x = 0, y = 0, h, base, r = rows;
    940 
    941 	h = (wh - 1) / rows;
    942 	for (i = 0; i < numkeys; i++, r--) {
    943 		for (j = i, base = 0; j < numkeys && keys[j].keysym != 0; j++)
    944 			base += keys[j].width;
    945 		for (x = 0; i < numkeys && keys[i].keysym != 0; i++) {
    946 			keys[i].x = x;
    947 			keys[i].y = y;
    948 			keys[i].w = keys[i].width * (ww - 1) / base;
    949 			keys[i].h = r == 1 ? wh - y - 1 : h;
    950 			x += keys[i].w;
    951 		}
    952 		if (base != 0)
    953 			keys[i - 1].w = ww - 1 - keys[i - 1].x;
    954 		y += h;
    955 	}
    956 }
    957 
    958 void
    959 usage(char *argv0)
    960 {
    961 	fprintf(stderr, "usage: %s [-hdnovDOR] [-g geometry] [-fn font] [-l layers] [-s initial_layer]\n", argv0);
    962 	fprintf(stderr, "Options:\n");
    963 	fprintf(stderr, "  -d         - Set Dock Window Type\n");
    964 	fprintf(stderr, "  -D         - Enable debug\n");
    965 	fprintf(stderr, "  -O         - Disable overlays\n");
    966 	fprintf(stderr, "  -R         - Disable press-on-release\n");
    967 	fprintf(stderr, "  -n         - Do not simulate key presses for X\n");
    968 	fprintf(stderr, "  -o         - Print to standard output\n");
    969 	fprintf(stderr, "  -l         - Comma separated list of layers to enable\n");
    970 	fprintf(stderr, "  -s         - Layer to select on program start\n");
    971 	fprintf(stderr, "  -H [int]   - Height fraction, one key row takes 1/x of the screen height\n");
    972 	fprintf(stderr, "  -fn [font] - Set font (Xft, e.g: DejaVu Sans:bold:size=20)\n");
    973 	fprintf(stderr, "  -g         - Set the window position or size using the X geometry format\n");
    974 	exit(1);
    975 }
    976 
    977 void
    978 setlayer(void)
    979 {
    980 	numkeys = countkeys(layers[currentlayer]);
    981 	memcpy(&keys, layers[currentlayer], sizeof(Key) * numkeys);
    982 	countrows();
    983 }
    984 
    985 void
    986 cyclelayer(void)
    987 {
    988 	currentlayer++;
    989 	if (currentlayer >= numlayers)
    990 		currentlayer = 0;
    991 	printdbg("Cycling to layer %d\n", currentlayer);
    992 	setlayer();
    993 	updatekeys();
    994 	drawkeyboard();
    995 }
    996 
    997 void
    998 togglelayer(void)
    999 {
   1000 	if (currentlayer > 0) {
   1001 		currentlayer = 0;
   1002 	} else if (numlayers > 1) {
   1003 		currentlayer = 1;
   1004 	}
   1005 	printdbg("Toggling layer %d\n", currentlayer);
   1006 	setlayer();
   1007 	updatekeys();
   1008 	drawkeyboard();
   1009 }
   1010 
   1011 void
   1012 showoverlay(int idx)
   1013 {
   1014 	int i, j;
   1015 
   1016 	printdbg("Showing overlay %d\n", idx);
   1017 
   1018 	/* unpress existing key (visually only) */
   1019 	for (i = 0; i < numkeys; i++) {
   1020 		if (keys[i].pressed && !IsModifierKey(keys[i].keysym)) {
   1021 			keys[i].pressed = 0;
   1022 			drawkey(&keys[i]);
   1023 			break;
   1024 		}
   1025 	}
   1026 
   1027 	for (i = idx, j=0; i < OVERLAYS; i++, j++) {
   1028 		if (overlay[i].keysym == XK_Cancel) {
   1029 			break;
   1030 		}
   1031 		while (keys[j].keysym == 0)
   1032 			j++;
   1033 		if (overlay[i].width > 1)
   1034 			j += overlay[i].width - 1;
   1035 		keys[j].label = overlay[i].label;
   1036 		keys[j].label2 = overlay[i].label2;
   1037 		keys[j].keysym = overlay[i].keysym;
   1038 		keys[j].modifier = overlay[i].modifier;
   1039 		keys[j].isoverlay = True;
   1040 	}
   1041 	currentoverlay = idx;
   1042 	overlaykeysym = ispressingkeysym;
   1043 	releaseprotect = 1;
   1044 	updatekeys();
   1045 	drawkeyboard();
   1046 	XSync(dpy, False);
   1047 }
   1048 
   1049 void
   1050 hideoverlay(void)
   1051 {
   1052 	printdbg("Hiding overlay, overlay was #%d\n", currentoverlay);
   1053 	currentoverlay = -1;
   1054 	overlaykeysym = 0;
   1055 	currentlayer--;
   1056 	cyclelayer();
   1057 }
   1058 
   1059 void
   1060 sigterm(int signo)
   1061 {
   1062 	running = False;
   1063 	sigtermd = True;
   1064 	printdbg("SIGTERM received\n");
   1065 }
   1066 
   1067 void
   1068 init_layers(char *layer_names_list, const char *initial_layer_name)
   1069 {
   1070 	char *s;
   1071 	int j, found;
   1072 
   1073 	if (layer_names_list == NULL) {
   1074 		numlayers = LAYERS;
   1075 		memcpy(&layers, &available_layers, sizeof(available_layers));
   1076 		if (initial_layer_name != NULL) {
   1077 			for (j = 0; j < LAYERS; j++) {
   1078 				if (strcmp(layer_names[j], initial_layer_name) == 0) {
   1079 					currentlayer = j;
   1080 					break;
   1081 				}
   1082 			}
   1083 		}
   1084 	} else {
   1085 		s = strtok(layer_names_list, ",");
   1086 		while (s != NULL) {
   1087 			if (numlayers + 1 > LAYERS)
   1088 				die("too many layers specified");
   1089 			found = 0;
   1090 			for (j = 0; j < LAYERS; j++) {
   1091 				if (strcmp(layer_names[j], s) == 0) {
   1092 					fprintf(stderr, "Adding layer %s\n", s);
   1093 					layers[numlayers] = available_layers[j];
   1094 					if (initial_layer_name != NULL && strcmp(layer_names[j], initial_layer_name) == 0) {
   1095 						currentlayer = numlayers;
   1096 					}
   1097 					found = 1;
   1098 					break;
   1099 				}
   1100 			}
   1101 			if (!found) {
   1102 				fprintf(stderr, "Undefined layer: %s\n", s);
   1103 				exit(3);
   1104 			}
   1105 			numlayers++;
   1106 			s = strtok(NULL,",");
   1107 		}
   1108 	}
   1109 	setlayer();
   1110 }
   1111 
   1112 void
   1113 printdbg(const char *fmt, ...)
   1114 {
   1115 	if (!debug)
   1116 		return;
   1117 
   1118 	va_list ap;
   1119 	va_start(ap, fmt);
   1120 	vfprintf(stderr, fmt, ap);
   1121 	va_end(ap);
   1122 	fflush(stderr);
   1123 }
   1124 
   1125 int
   1126 main(int argc, char *argv[])
   1127 {
   1128 	char *initial_layer_name = NULL;
   1129 	char *layer_names_list = NULL;
   1130 	char *tmp;
   1131 	int i, xr, yr, bitm;
   1132 	unsigned int wr, hr;
   1133 
   1134 	signal(SIGTERM, sigterm);
   1135 
   1136 	if (OVERLAYS <= 1) {
   1137 		enableoverlays = 0;
   1138 	} else {
   1139 		if ((tmp = getenv("SVKBD_ENABLEOVERLAYS")))
   1140 			enableoverlays = atoi(tmp);
   1141 	}
   1142 	if ((tmp = getenv("SVKBD_LAYERS")))
   1143 		layer_names_list = estrdup(tmp);
   1144 
   1145 	if ((tmp = getenv("SVKBD_HEIGHTFACTOR")))
   1146 		heightfactor = atoi(tmp);
   1147 
   1148 	if ((tmp = getenv("SVKBD_PRESSONRELEASE"))) /* defaults to 1 */
   1149 		pressonrelease = atoi(tmp);
   1150 
   1151 	/* parse command line arguments */
   1152 	for (i = 1; argv[i]; i++) {
   1153 		if (!strcmp(argv[i], "-v")) {
   1154 			die("svkbd-"VERSION);
   1155 		} else if (!strcmp(argv[i], "-d")) {
   1156 			isdock = True;
   1157 		} else if (!strncmp(argv[i], "-g", 2)) {
   1158 			if (i >= argc - 1)
   1159 				usage(argv[0]);
   1160 
   1161 			bitm = XParseGeometry(argv[++i], &xr, &yr, &wr, &hr);
   1162 			if (bitm & XValue)
   1163 				wx = xr;
   1164 			if (bitm & YValue)
   1165 				wy = yr;
   1166 			if (bitm & WidthValue)
   1167 				ww = (int)wr;
   1168 			if (bitm & HeightValue)
   1169 				wh = (int)hr;
   1170 			if (bitm & XNegative && wx == 0)
   1171 				wx = -1;
   1172 			if (bitm & YNegative && wy == 0)
   1173 				wy = -1;
   1174 		} else if (!strcmp(argv[i], "-fn")) { /* font or font set */
   1175 			if (i >= argc - 1)
   1176 				usage(argv[0]);
   1177 			fonts[0] = estrdup(argv[++i]);
   1178 		} else if (!strcmp(argv[i], "-D")) {
   1179 			debug = 1;
   1180 		} else if (!strcmp(argv[i], "-h")) {
   1181 			usage(argv[0]);
   1182 		} else if (!strcmp(argv[i], "-O")) {
   1183 			enableoverlays = 0;
   1184 		} else if (!strcmp(argv[i], "-o")) {
   1185 			printoutput = 1;
   1186 		} else if (!strcmp(argv[i], "-n")) {
   1187 			simulateoutput = 0;
   1188 		} else if (!strcmp(argv[i], "-R")) {
   1189 			pressonrelease = 0;
   1190 		} else if (!strcmp(argv[i], "-l")) {
   1191 			if (i >= argc - 1)
   1192 				usage(argv[0]);
   1193 			free(layer_names_list);
   1194 			layer_names_list = estrdup(argv[++i]);
   1195 		} else if (!strcmp(argv[i], "-s")) {
   1196 			if (i >= argc - 1)
   1197 				usage(argv[0]);
   1198 			initial_layer_name = argv[++i];
   1199 		} else if (!strcmp(argv[i], "-H")) {
   1200 			if (i >= argc - 1)
   1201 				usage(argv[0]);
   1202 			heightfactor = atoi(argv[++i]);
   1203 		} else {
   1204 			fprintf(stderr, "Invalid argument: %s\n", argv[i]);
   1205 			usage(argv[0]);
   1206 		}
   1207 	}
   1208 
   1209 	if (printoutput)
   1210 		setbuf(stdout, NULL); /* unbuffered output */
   1211 
   1212 	if (heightfactor <= 0)
   1213 		die("height factor must be a positive integer");
   1214 
   1215 	init_layers(layer_names_list, initial_layer_name);
   1216 
   1217 	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
   1218 		fprintf(stderr, "warning: no locale support");
   1219 	if (!(dpy = XOpenDisplay(0)))
   1220 		die("cannot open display");
   1221 	setup();
   1222 	run();
   1223 	cleanup();
   1224 	XCloseDisplay(dpy);
   1225 	free(layer_names_list);
   1226 
   1227 	return 0;
   1228 }