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