thingmenu.c (15423B)
1 /* 2 * Copy me if you can. 3 * by 20h 4 */ 5 #include <unistd.h> 6 #include <locale.h> 7 #include <signal.h> 8 #include <stdarg.h> 9 #include <stdio.h> 10 #include <string.h> 11 #include <stdlib.h> 12 #include <libgen.h> 13 #include <sys/wait.h> 14 #include <X11/keysym.h> 15 #include <X11/Xatom.h> 16 #include <X11/Xlib.h> 17 #include <X11/Xutil.h> 18 #include <X11/Xproto.h> 19 #include <X11/extensions/XTest.h> 20 21 /* macros */ 22 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 23 #define LENGTH(x) (sizeof x / sizeof x[0]) 24 25 /* enums */ 26 enum { ColFG, ColBG, ColLast }; 27 enum { NetWMWindowType, NetLast }; 28 29 /* typedefs */ 30 typedef unsigned int uint; 31 typedef unsigned long ulong; 32 33 typedef struct { 34 ulong norm[ColLast]; 35 ulong press[ColLast]; 36 ulong high[ColLast]; 37 38 Drawable drawable; 39 GC gc; 40 struct { 41 int ascent; 42 int descent; 43 int height; 44 XFontSet set; 45 XFontStruct *xfont; 46 } font; 47 } DC; /* draw context */ 48 49 typedef struct { 50 char *label; 51 char *cmd; 52 uint width; 53 int x, y, w, h; 54 Bool highlighted; 55 Bool pressed; 56 Bool forceexit; 57 } Entry; 58 59 /* function declarations */ 60 static void motionnotify(XEvent *e); 61 static void keyrelease(XEvent *e); 62 static void buttonpress(XEvent *e); 63 static void buttonrelease(XEvent *e); 64 static void cleanup(void); 65 static void configurenotify(XEvent *e); 66 static void unmapnotify(XEvent *e); 67 static void die(const char *errstr, ...); 68 static void drawmenu(void); 69 static void drawentry(Entry *e); 70 static void expose(XEvent *e); 71 static Entry *findentry(int x, int y); 72 static ulong getcolor(const char *colstr); 73 static void initfont(const char *fontstr); 74 static void leavenotify(XEvent *e); 75 static void press(Entry *e); 76 static void run(void); 77 static void setup(void); 78 static void sigchld(int unused); 79 static int textnw(const char *text, uint len); 80 static void unpress(Entry *e); 81 static void updateentries(void); 82 83 /* variables */ 84 static int screen; 85 static void (*handler[LASTEvent]) (XEvent *) = { 86 [KeyRelease] = keyrelease, 87 [ButtonPress] = buttonpress, 88 [ButtonRelease] = buttonrelease, 89 [ConfigureNotify] = configurenotify, 90 [UnmapNotify] = unmapnotify, 91 [Expose] = expose, 92 [LeaveNotify] = leavenotify, 93 [MotionNotify] = motionnotify 94 }; 95 96 static Display *dpy; 97 static DC dc; 98 static Window root, win; 99 static Bool running = True, horizontal = False; 100 /* 101 * ww = window width; www = wanted window width; wh = window height; 102 * wx = window x position; wy = window y position; 103 */ 104 static int ww = 0, www = 0, wh = 0, wx = 0, wy = 0; 105 static char *name = "thingmenu"; 106 107 Entry **entries = NULL; 108 int nentries = 0; 109 int exitentry = -1; 110 int oneshot = 1; 111 Bool ispressing = 0; 112 113 char *argv0; 114 115 #include "arg.h" 116 117 /* configuration, allows nested code to access above variables */ 118 #include "config.h" 119 120 void 121 motionnotify(XEvent *e) 122 { 123 XPointerMovedEvent *ev = &e->xmotion; 124 int i; 125 126 for(i = 0; i < nentries; i++) { 127 if(ev->x > entries[i]->x 128 && ev->x < entries[i]->x + entries[i]->w 129 && ev->y > entries[i]->y 130 && ev->y < entries[i]->y + entries[i]->h) { 131 if (entries[i]->highlighted != True) { 132 if (ispressing) { 133 entries[i]->pressed = True; 134 } else { 135 entries[i]->highlighted = True; 136 } 137 drawentry(entries[i]); 138 } 139 continue; 140 } 141 if (entries[i]->pressed == True) { 142 entries[i]->pressed = False; 143 drawentry(entries[i]); 144 } 145 if (entries[i]->highlighted == True) { 146 entries[i]->highlighted = False; 147 drawentry(entries[i]); 148 } 149 } 150 } 151 152 void 153 keyrelease(XEvent *e) 154 { 155 int i; 156 XKeyEvent *xkey = &e->xkey; 157 KeySym key = XLookupKeysym(xkey, 0); 158 159 for (i = 0; i < nentries && !entries[i]->highlighted; i++); 160 161 if (key >= XK_0 && key <= XK_9) { 162 i = key - XK_0; 163 key = XK_Return; 164 } else if (key >= XK_KP_0 && key <= XK_KP_9) { 165 i = key - XK_KP_0; 166 key = XK_Return; 167 } 168 169 switch (key) { 170 case XK_KP_Insert: 171 i = 0; 172 key = XK_Return; 173 break; 174 case XK_KP_End: 175 i = 1; 176 key = XK_Return; 177 break; 178 case XK_KP_Down: 179 i = 2; 180 key = XK_Return; 181 break; 182 case XK_KP_Page_Down: 183 i = 3; 184 key = XK_Return; 185 break; 186 case XK_KP_Left: 187 i = 4; 188 key = XK_Return; 189 break; 190 case XK_KP_Begin: 191 i = 5; 192 key = XK_Return; 193 break; 194 case XK_KP_Right: 195 i = 6; 196 key = XK_Return; 197 break; 198 case XK_KP_Home: 199 i = 7; 200 key = XK_Return; 201 break; 202 case XK_KP_Up: 203 i = 8; 204 key = XK_Return; 205 break; 206 case XK_KP_Page_Up: 207 i = 9; 208 key = XK_Return; 209 break; 210 } 211 212 switch (key) { 213 case XK_k: 214 key = XK_Up; 215 case XK_j: 216 if(key == XK_j) 217 key = XK_Down; 218 case XK_Up: 219 case XK_Down: 220 if (i < nentries) { 221 entries[i]->highlighted = False; 222 drawentry(entries[i]); 223 } 224 225 if (key == XK_Up) { 226 i = ((i - 1) + nentries) % nentries; 227 } else if(key == XK_Down) { 228 if (i < nentries) { 229 i = (i + 1) % nentries; 230 } else { 231 i = 0; 232 } 233 } 234 235 entries[i]->highlighted = True; 236 drawentry(entries[i]); 237 break; 238 case XK_period: 239 case XK_KP_Decimal: 240 case XK_KP_Delete: 241 i = exitentry; 242 case XK_Return: 243 case XK_space: 244 if (i < nentries) { 245 press(entries[i]); 246 unpress(entries[i]); 247 } 248 break; 249 case XK_Escape: 250 running = False; 251 break; 252 } 253 } 254 255 void 256 buttonpress(XEvent *e) 257 { 258 XButtonPressedEvent *ev = &e->xbutton; 259 Entry *en; 260 261 if(ev->button != Button1) 262 return; 263 264 ispressing = True; 265 266 if((en = findentry(ev->x, ev->y))) 267 press(en); 268 } 269 270 void 271 buttonrelease(XEvent *e) 272 { 273 XButtonPressedEvent *ev = &e->xbutton; 274 Entry *en; 275 276 if(ev->button != Button1) 277 return; 278 279 ispressing = False; 280 281 if((en = findentry(ev->x, ev->y))) 282 unpress(en); 283 } 284 285 void 286 cleanup(void) 287 { 288 if(dc.font.set) 289 XFreeFontSet(dpy, dc.font.set); 290 else 291 XFreeFont(dpy, dc.font.xfont); 292 XFreePixmap(dpy, dc.drawable); 293 XFreeGC(dpy, dc.gc); 294 XDestroyWindow(dpy, win); 295 XSync(dpy, False); 296 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); 297 } 298 299 void 300 configurenotify(XEvent *e) 301 { 302 XConfigureEvent *ev = &e->xconfigure; 303 304 if(ev->window == win && (ev->width != ww || ev->height != wh)) { 305 ww = ev->width; 306 wh = ev->height; 307 XFreePixmap(dpy, dc.drawable); 308 dc.drawable = XCreatePixmap(dpy, root, ww, wh, 309 DefaultDepth(dpy, screen)); 310 updateentries(); 311 } 312 } 313 314 void 315 die(const char *errstr, ...) 316 { 317 va_list ap; 318 319 va_start(ap, errstr); 320 vfprintf(stderr, errstr, ap); 321 va_end(ap); 322 exit(EXIT_FAILURE); 323 } 324 325 void 326 drawmenu(void) 327 { 328 int i; 329 330 for(i = 0; i < nentries; i++) 331 drawentry(entries[i]); 332 XSync(dpy, False); 333 } 334 335 void 336 drawentry(Entry *e) 337 { 338 int x, y, h, len; 339 XRectangle r = { e->x, e->y, e->w, e->h }; 340 const char *l; 341 ulong *col; 342 343 if(e->pressed) 344 col = dc.press; 345 else if(e->highlighted) 346 col = dc.high; 347 else 348 col = dc.norm; 349 350 XSetForeground(dpy, dc.gc, col[ColBG]); 351 XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); 352 XSetForeground(dpy, dc.gc, dc.norm[ColFG]); 353 r.height -= 1; 354 r.width -= 1; 355 XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1); 356 XSetForeground(dpy, dc.gc, col[ColFG]); 357 358 l = e->label; 359 len = strlen(l); 360 h = dc.font.height; 361 y = e->y + (e->h / 2) - (h / 2) + dc.font.ascent; 362 x = e->x + (e->w / 2) - (textnw(l, len) / 2); 363 if(dc.font.set) { 364 XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, l, 365 len); 366 } else 367 XDrawString(dpy, dc.drawable, dc.gc, x, y, l, len); 368 XCopyArea(dpy, dc.drawable, win, dc.gc, e->x, e->y, e->w, e->h, 369 e->x, e->y); 370 } 371 372 void 373 unmapnotify(XEvent *e) 374 { 375 running = False; 376 } 377 378 void 379 expose(XEvent *e) 380 { 381 XExposeEvent *ev = &e->xexpose; 382 383 if(ev->count == 0 && (ev->window == win)) 384 drawmenu(); 385 } 386 387 Entry * 388 findentry(int x, int y) 389 { 390 int i; 391 392 for(i = 0; i < nentries; i++) { 393 if(x > entries[i]->x && x < entries[i]->x + entries[i]->w 394 && y > entries[i]->y 395 && y < entries[i]->y + entries[i]->h) { 396 return entries[i]; 397 } 398 } 399 return NULL; 400 } 401 402 ulong 403 getcolor(const char *colstr) 404 { 405 Colormap cmap = DefaultColormap(dpy, screen); 406 XColor color; 407 408 if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) 409 die("error, cannot allocate color '%s'\n", colstr); 410 return color.pixel; 411 } 412 413 void 414 initfont(const char *fontstr) 415 { 416 char *def, **missing; 417 int i, n; 418 419 missing = NULL; 420 if(dc.font.set) 421 XFreeFontSet(dpy, dc.font.set); 422 dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); 423 if(missing) { 424 while(n--) { 425 fprintf(stderr, "thingmenu: missing fontset: %s\n", 426 missing[n]); 427 } 428 XFreeStringList(missing); 429 } 430 if(dc.font.set) { 431 XFontStruct **xfonts; 432 char **font_names; 433 dc.font.ascent = dc.font.descent = 0; 434 n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); 435 for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { 436 dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); 437 dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent); 438 xfonts++; 439 } 440 } 441 else { 442 if(dc.font.xfont) 443 XFreeFont(dpy, dc.font.xfont); 444 dc.font.xfont = NULL; 445 if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) 446 && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) 447 die("error, cannot load font: '%s'\n", fontstr); 448 dc.font.ascent = dc.font.xfont->ascent; 449 dc.font.descent = dc.font.xfont->descent; 450 } 451 dc.font.height = dc.font.ascent + dc.font.descent; 452 } 453 454 void 455 leavenotify(XEvent *e) 456 { 457 unpress(NULL); 458 } 459 460 void 461 run(void) 462 { 463 XEvent ev; 464 465 /* main event loop */ 466 XSync(dpy, False); 467 while(running) { 468 XNextEvent(dpy, &ev); 469 if(handler[ev.type]) 470 (handler[ev.type])(&ev); /* call handler */ 471 } 472 } 473 474 void 475 setup(void) 476 { 477 XSetWindowAttributes wa; 478 XTextProperty str; 479 XSizeHints *sizeh; 480 XClassHint *ch; 481 int i, sh, sw, ls; 482 483 /* clean up any zombies immediately */ 484 sigchld(0); 485 486 /* init screen */ 487 screen = DefaultScreen(dpy); 488 root = RootWindow(dpy, screen); 489 sw = DisplayWidth(dpy, screen) - 1; 490 sh = DisplayHeight(dpy, screen) - 1; 491 initfont(font); 492 493 /* init atoms */ 494 495 /* init appearance */ 496 497 for (i = 0, www = 0; i < nentries; i++) { 498 ls = textnw(entries[i]->label, 499 strlen(entries[i]->label)); 500 if (ls > www) 501 www = ls; 502 } 503 www *= widthscaling; 504 505 if (!ww) { 506 if (horizontal) { 507 ww = www * nentries; 508 } else { 509 ww = www; 510 } 511 } 512 if (!wh) { 513 if (horizontal) { 514 wh = dc.font.height * heightscaling; 515 } else { 516 wh = nentries * dc.font.height * heightscaling; 517 } 518 } 519 if (!wy) 520 wy = (sh - wh) / 2; 521 if (wy < 0) 522 wy = sh + wy - wh; 523 if (!wx) 524 wx = (sw - ww) / 2; 525 if (wx < 0) 526 wx = sw + wx - ww; 527 528 dc.norm[ColBG] = getcolor(normbgcolor); 529 dc.norm[ColFG] = getcolor(normfgcolor); 530 dc.press[ColBG] = getcolor(pressbgcolor); 531 dc.press[ColFG] = getcolor(pressfgcolor); 532 dc.high[ColBG] = getcolor(highlightbgcolor); 533 dc.high[ColFG] = getcolor(highlightfgcolor); 534 535 dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen)); 536 dc.gc = XCreateGC(dpy, root, 0, 0); 537 if(!dc.font.set) 538 XSetFont(dpy, dc.gc, dc.font.xfont->fid); 539 for(i = 0; i < nentries; i++) 540 entries[i]->pressed = 0; 541 542 wa.override_redirect = !wmborder; 543 wa.border_pixel = dc.norm[ColFG]; 544 wa.background_pixel = dc.norm[ColBG]; 545 win = XCreateWindow(dpy, root, wx, wy, ww, wh, 0, 546 CopyFromParent, CopyFromParent, CopyFromParent, 547 CWOverrideRedirect | CWBorderPixel | CWBackingPixel, &wa); 548 XSelectInput(dpy, win, StructureNotifyMask|KeyReleaseMask| 549 ButtonReleaseMask|ButtonPressMask| 550 ExposureMask|LeaveWindowMask|PointerMotionMask); 551 552 sizeh = XAllocSizeHints(); 553 sizeh->flags = PMaxSize | PMinSize; 554 sizeh->min_width = sizeh->max_width = ww; 555 sizeh->min_height = sizeh->max_height = wh; 556 XStringListToTextProperty(&name, 1, &str); 557 ch = XAllocClassHint(); 558 ch->res_class = name; 559 ch->res_name = name; 560 561 XSetWMProperties(dpy, win, &str, &str, NULL, 0, sizeh, NULL, 562 ch); 563 564 XFree(ch); 565 XFree(str.value); 566 XFree(sizeh); 567 568 XMapRaised(dpy, win); 569 updateentries(); 570 drawmenu(); 571 } 572 573 void 574 sigchld(int unused) 575 { 576 if (signal(SIGCHLD, sigchld) == SIG_ERR) 577 die("can't install SIGCHLD handler:"); 578 while (0 < waitpid(-1, NULL, WNOHANG)); 579 } 580 581 int 582 textnw(const char *text, uint len) 583 { 584 XRectangle r; 585 586 if(dc.font.set) { 587 XmbTextExtents(dc.font.set, text, len, NULL, &r); 588 return r.width; 589 } 590 return XTextWidth(dc.font.xfont, text, len); 591 } 592 593 void 594 runentry(Entry *e) 595 { 596 char *shell; 597 598 if (oneshot || e->forceexit) 599 running = False; 600 601 switch (fork()) { 602 case -1: 603 break; 604 case 0: 605 shell = getenv("SHELL"); 606 if (!shell) 607 shell = "/bin/sh"; 608 609 execlp(shell, basename(shell), "-c", e->cmd, (char *)NULL); 610 break; 611 } 612 } 613 614 void 615 press(Entry *e) 616 { 617 e->pressed = !e->pressed; 618 619 drawentry(e); 620 } 621 622 void 623 unpress(Entry *e) 624 { 625 int i; 626 627 if (e != NULL) { 628 e->pressed = !e->pressed; 629 630 runentry(e); 631 drawentry(e); 632 } else { 633 for(i = 0; i < nentries; i++) { 634 if(entries[i]->pressed) { 635 entries[i]->pressed = 0; 636 drawentry(entries[i]); 637 } 638 } 639 } 640 } 641 642 void 643 updateentries(void) 644 { 645 int i, x, y, h, w; 646 647 x = 0; 648 y = 0; 649 650 if (horizontal) { 651 h = wh; 652 w = www; 653 } else { 654 h = wh / nentries; 655 w = ww; 656 } 657 for(i = 0; i < nentries; i++) { 658 entries[i]->x = x; 659 entries[i]->y = y; 660 entries[i]->w = w; 661 entries[i]->h = h; 662 if (horizontal) { 663 x += w; 664 } else { 665 y += h; 666 } 667 } 668 } 669 670 void 671 usage(void) 672 { 673 fprintf(stderr, "usage: %s [-hnosx] [-g geometry] [-w widthscaling] " 674 "[-e heightscaling] [--] " 675 "label0 cmd0 [label1 cmd1 ...]\n", argv0); 676 exit(1); 677 } 678 679 int 680 main(int argc, char *argv[]) 681 { 682 Bool addexit, usenumpad; 683 char *label, *cmd; 684 int i, xr, yr, bitm; 685 unsigned int wr, hr; 686 687 argv0 = argv[0]; 688 689 addexit = True; 690 usenumpad = False; 691 692 if (argc < 2) 693 usage(); 694 695 ARGBEGIN { 696 case 'g': 697 bitm = XParseGeometry(EARGF(usage()), &xr, &yr, &wr, &hr); 698 if (bitm & XValue) 699 wx = xr; 700 if (bitm & YValue) 701 wy = yr; 702 if (bitm & WidthValue) 703 ww = (int)wr; 704 if (bitm & HeightValue) 705 wh = (int)hr; 706 if (bitm & XNegative && wx == 0) 707 wx = -1; 708 if (bitm & YNegative && wy == 0) 709 wy = -1; 710 break; 711 case 'e': 712 heightscaling = atof(EARGF(usage())); 713 break; 714 case 'n': 715 usenumpad = True; 716 break; 717 case 'o': 718 horizontal = True; 719 break; 720 case 's': 721 oneshot = 0; 722 break; 723 case 'w': 724 widthscaling = atof(EARGF(usage())); 725 break; 726 case 'x': 727 addexit = False; 728 break; 729 default: 730 usage(); 731 } ARGEND; 732 733 for (i = 0; argv[i]; i++) { 734 label = argv[i]; 735 if (!argv[i+1]) 736 break; 737 i++; 738 cmd = argv[i]; 739 740 if (!(entries = realloc(entries, sizeof(entries[0])*(++nentries)))) 741 die("realloc returned NULL"); 742 if (!(entries[nentries-1] = calloc(1, sizeof(*entries[0])))) 743 die("calloc returned NULL"); 744 if (usenumpad == True && nentries < 11) { 745 if (!(asprintf(&entries[nentries-1]->label, 746 "%d:%s", nentries-1, strdup(label)))) { 747 die("asprintf returned NULL\n"); 748 } 749 } else { 750 if (!(entries[nentries-1]->label = strdup(label))) 751 die("strdup returned NULL\n"); 752 } 753 if (!(entries[nentries-1]->cmd = strdup(cmd))) 754 die("strdup returned NULL\n"); 755 entries[nentries-1]->forceexit = False; 756 } 757 if (nentries < 1) 758 usage(); 759 760 if (addexit) { 761 if (!(entries = realloc(entries, sizeof(entries[0])*(++nentries)))) 762 die("realloc returned NULL"); 763 if (!(entries[nentries-1] = calloc(1, sizeof(*entries[0])))) 764 die("calloc returned NULL"); 765 if (usenumpad == True) { 766 if (!(entries[nentries-1]->label = strdup(".:cancel"))) 767 die("strdup returned NULL\n"); 768 } else { 769 if (!(entries[nentries-1]->label = strdup("cancel"))) 770 die("strdup returned NULL\n"); 771 } 772 if (!(entries[nentries-1]->cmd = strdup("exit"))) 773 die("strdup returned NULL\n"); 774 entries[nentries-1]->forceexit = True; 775 exitentry = nentries - 1; 776 } 777 778 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 779 fprintf(stderr, "warning: no locale support\n"); 780 if(!(dpy = XOpenDisplay(0))) 781 die("thingmenu: cannot open display\n"); 782 783 setup(); 784 run(); 785 cleanup(); 786 XCloseDisplay(dpy); 787 788 for (i = 0; i < nentries; i++) { 789 free(entries[i]->label); 790 free(entries[i]->cmd); 791 free(entries[i]); 792 } 793 free(entries); 794 795 return 0; 796 } 797