svkbd.c - svkbd - Simple X11 onscreen keyboard.
(HTM) git clone git://r-36.net/svkbd
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) 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