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