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