My dmenu build
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

570 lines
12 KiB

  1. /* See LICENSE file for copyright and license details. */
  2. #include <ctype.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <X11/Xatom.h>
  8. #include <X11/Xlib.h>
  9. #include <X11/Xutil.h>
  10. #ifdef XINERAMA
  11. #include <X11/extensions/Xinerama.h>
  12. #endif
  13. #include <draw.h>
  14. #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
  15. #define MIN(a,b) ((a) < (b) ? (a) : (b))
  16. #define MAX(a,b) ((a) > (b) ? (a) : (b))
  17. #define UTF8_CODEPOINT(c) (((c) & 0xc0) != 0x80)
  18. typedef struct Item Item;
  19. struct Item {
  20. char *text;
  21. Item *next; /* traverses all items */
  22. Item *left, *right; /* traverses matching items */
  23. };
  24. static void appenditem(Item *item, Item **list, Item **last);
  25. static void calcoffsetsh(void);
  26. static void calcoffsetsv(void);
  27. static char *cistrstr(const char *s, const char *sub);
  28. static void drawmenu(void);
  29. static void drawmenuh(void);
  30. static void drawmenuv(void);
  31. static void grabkeyboard(void);
  32. static void insert(const char *s, ssize_t n);
  33. static void keypress(XKeyEvent *e);
  34. static void match(void);
  35. static void paste(void);
  36. static void readstdin(void);
  37. static void run(void);
  38. static void setup(void);
  39. static void usage(void);
  40. static char text[4096];
  41. static size_t cursor = 0;
  42. static const char *prompt = NULL;
  43. static const char *normbgcolor = "#cccccc";
  44. static const char *normfgcolor = "#000000";
  45. static const char *selbgcolor = "#0066ff";
  46. static const char *selfgcolor = "#ffffff";
  47. static unsigned int inputw = 0;
  48. static unsigned int lines = 0;
  49. static unsigned int mw, mh;
  50. static unsigned int promptw;
  51. static unsigned long normcol[ColLast];
  52. static unsigned long selcol[ColLast];
  53. static Atom utf8;
  54. static Bool topbar = True;
  55. static DC *dc;
  56. static Item *allitems, *matches;
  57. static Item *curr, *prev, *next, *sel;
  58. static Window root, win;
  59. static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
  60. static char *(*fstrstr)(const char *, const char *) = strstr;
  61. static void (*calcoffsets)(void) = calcoffsetsh;
  62. void
  63. appenditem(Item *item, Item **list, Item **last) {
  64. if(!*last)
  65. *list = item;
  66. else
  67. (*last)->right = item;
  68. item->left = *last;
  69. item->right = NULL;
  70. *last = item;
  71. }
  72. void
  73. calcoffsetsh(void) {
  74. unsigned int w, x;
  75. w = promptw + inputw + textw(dc, "<") + textw(dc, ">");
  76. for(x = w, next = curr; next; next = next->right)
  77. if((x += MIN(textw(dc, next->text), mw / 3)) > mw)
  78. break;
  79. for(x = w, prev = curr; prev && prev->left; prev = prev->left)
  80. if((x += MIN(textw(dc, prev->left->text), mw / 3)) > mw)
  81. break;
  82. }
  83. void
  84. calcoffsetsv(void) {
  85. unsigned int i;
  86. next = prev = curr;
  87. for(i = 0; i < lines && next; i++)
  88. next = next->right;
  89. for(i = 0; i < lines && prev && prev->left; i++)
  90. prev = prev->left;
  91. }
  92. char *
  93. cistrstr(const char *s, const char *sub) {
  94. size_t len;
  95. for(len = strlen(sub); *s; s++)
  96. if(!strncasecmp(s, sub, len))
  97. return (char *)s;
  98. return NULL;
  99. }
  100. void
  101. drawmenu(void) {
  102. dc->x = 0;
  103. dc->y = 0;
  104. drawrect(dc, 0, 0, mw, mh, BG(dc, normcol));
  105. dc->h = dc->font.height + 2;
  106. dc->y = topbar ? 0 : mh - dc->h;
  107. /* print prompt? */
  108. if(prompt) {
  109. dc->w = promptw;
  110. drawtext(dc, prompt, selcol);
  111. dc->x = dc->w;
  112. }
  113. dc->w = mw - dc->x;
  114. /* print input area */
  115. if(matches && lines == 0 && textw(dc, text) <= inputw)
  116. dc->w = inputw;
  117. drawtext(dc, text, normcol);
  118. drawrect(dc, textnw(dc, text, cursor) + dc->h/2 - 2, 2, 1, dc->h - 4, FG(dc, normcol));
  119. if(lines > 0)
  120. drawmenuv();
  121. else if(curr && (dc->w == inputw || curr->next))
  122. drawmenuh();
  123. commitdraw(dc, win);
  124. }
  125. void
  126. drawmenuh(void) {
  127. Item *item;
  128. dc->x += inputw;
  129. dc->w = textw(dc, "<");
  130. if(curr->left)
  131. drawtext(dc, "<", normcol);
  132. for(item = curr; item != next; item = item->right) {
  133. dc->x += dc->w;
  134. dc->w = MIN(textw(dc, item->text), mw / 3);
  135. drawtext(dc, item->text, (item == sel) ? selcol : normcol);
  136. }
  137. dc->w = textw(dc, ">");
  138. dc->x = mw - dc->w;
  139. if(next)
  140. drawtext(dc, ">", normcol);
  141. }
  142. void
  143. drawmenuv(void) {
  144. Item *item;
  145. dc->y = topbar ? dc->h : 0;
  146. dc->w = mw - dc->x;
  147. for(item = curr; item != next; item = item->right) {
  148. drawtext(dc, item->text, (item == sel) ? selcol : normcol);
  149. dc->y += dc->h;
  150. }
  151. }
  152. void
  153. grabkeyboard(void) {
  154. int i;
  155. for(i = 0; i < 1000; i++) {
  156. if(!XGrabKeyboard(dc->dpy, root, True, GrabModeAsync, GrabModeAsync, CurrentTime))
  157. return;
  158. usleep(1000);
  159. }
  160. eprintf("cannot grab keyboard\n");
  161. }
  162. void
  163. insert(const char *s, ssize_t n) {
  164. memmove(text + cursor + n, text + cursor, sizeof text - cursor - n);
  165. if(n > 0)
  166. memcpy(text + cursor, s, n);
  167. cursor += n;
  168. match();
  169. }
  170. void
  171. keypress(XKeyEvent *e) {
  172. char buf[sizeof text];
  173. int n;
  174. size_t len;
  175. KeySym ksym;
  176. len = strlen(text);
  177. XLookupString(e, buf, sizeof buf, &ksym, NULL);
  178. if(e->state & ControlMask) {
  179. switch(tolower(ksym)) {
  180. default:
  181. return;
  182. case XK_a:
  183. ksym = XK_Home;
  184. break;
  185. case XK_b:
  186. ksym = XK_Left;
  187. break;
  188. case XK_c:
  189. ksym = XK_Escape;
  190. break;
  191. case XK_e:
  192. ksym = XK_End;
  193. break;
  194. case XK_f:
  195. ksym = XK_Right;
  196. break;
  197. case XK_h:
  198. ksym = XK_BackSpace;
  199. break;
  200. case XK_i:
  201. ksym = XK_Tab;
  202. break;
  203. case XK_j:
  204. case XK_m:
  205. ksym = XK_Return;
  206. break;
  207. case XK_k: /* delete right */
  208. text[cursor] = '\0';
  209. match();
  210. break;
  211. case XK_n:
  212. ksym = XK_Down;
  213. break;
  214. case XK_p:
  215. ksym = XK_Up;
  216. break;
  217. case XK_u: /* delete left */
  218. insert(NULL, -cursor);
  219. break;
  220. case XK_w: /* delete word */
  221. if(cursor == 0)
  222. return;
  223. n = 0;
  224. while(cursor - n++ > 0 && text[cursor - n] == ' ');
  225. while(cursor - n++ > 0 && text[cursor - n] != ' ');
  226. insert(NULL, 1-n);
  227. break;
  228. case XK_y: /* paste selection */
  229. XConvertSelection(dc->dpy, XA_PRIMARY, utf8, None, win, CurrentTime);
  230. /* causes SelectionNotify event */
  231. return;
  232. }
  233. }
  234. switch(ksym) {
  235. default:
  236. if(!iscntrl((int)*buf))
  237. insert(buf, MIN(strlen(buf), sizeof text - cursor));
  238. break;
  239. case XK_BackSpace:
  240. if(cursor == 0)
  241. return;
  242. for(n = 1; cursor - n > 0 && !UTF8_CODEPOINT(text[cursor - n]); n++);
  243. insert(NULL, -n);
  244. break;
  245. case XK_Delete:
  246. if(cursor == len)
  247. return;
  248. for(n = 1; cursor + n < len && !UTF8_CODEPOINT(text[cursor + n]); n++);
  249. cursor += n;
  250. insert(NULL, -n);
  251. break;
  252. case XK_End:
  253. if(cursor < len) {
  254. cursor = len;
  255. break;
  256. }
  257. while(next) {
  258. sel = curr = next;
  259. calcoffsets();
  260. }
  261. while(sel && sel->right)
  262. sel = sel->right;
  263. break;
  264. case XK_Escape:
  265. exit(EXIT_FAILURE);
  266. case XK_Home:
  267. if(sel == matches) {
  268. cursor = 0;
  269. break;
  270. }
  271. sel = curr = matches;
  272. calcoffsets();
  273. break;
  274. case XK_Left:
  275. if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
  276. while(cursor-- > 0 && !UTF8_CODEPOINT(text[cursor]));
  277. break;
  278. }
  279. else if(lines > 0)
  280. return;
  281. case XK_Up:
  282. if(!sel || !sel->left)
  283. return;
  284. sel = sel->left;
  285. if(sel->right == curr) {
  286. curr = prev;
  287. calcoffsets();
  288. }
  289. break;
  290. case XK_Next:
  291. if(!next)
  292. return;
  293. sel = curr = next;
  294. calcoffsets();
  295. break;
  296. case XK_Prior:
  297. if(!prev)
  298. return;
  299. sel = curr = prev;
  300. calcoffsets();
  301. break;
  302. case XK_Return:
  303. case XK_KP_Enter:
  304. fputs((sel && !(e->state & ShiftMask)) ? sel->text : text, stdout);
  305. fflush(stdout);
  306. exit(EXIT_SUCCESS);
  307. case XK_Right:
  308. if(cursor < len) {
  309. while(cursor++ < len && !UTF8_CODEPOINT(text[cursor]));
  310. break;
  311. }
  312. else if(lines > 0)
  313. return;
  314. case XK_Down:
  315. if(!sel || !sel->right)
  316. return;
  317. sel = sel->right;
  318. if(sel == next) {
  319. curr = next;
  320. calcoffsets();
  321. }
  322. break;
  323. case XK_Tab:
  324. if(!sel)
  325. return;
  326. strncpy(text, sel->text, sizeof text);
  327. cursor = strlen(text);
  328. match();
  329. break;
  330. }
  331. drawmenu();
  332. }
  333. void
  334. match(void) {
  335. unsigned int len;
  336. Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
  337. len = strlen(text);
  338. matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
  339. for(item = allitems; item; item = item->next)
  340. if(!fstrncmp(text, item->text, len + 1))
  341. appenditem(item, &lexact, &exactend);
  342. else if(!fstrncmp(text, item->text, len))
  343. appenditem(item, &lprefix, &prefixend);
  344. else if(fstrstr(item->text, text))
  345. appenditem(item, &lsubstr, &substrend);
  346. if(lexact) {
  347. matches = lexact;
  348. itemend = exactend;
  349. }
  350. if(lprefix) {
  351. if(itemend) {
  352. itemend->right = lprefix;
  353. lprefix->left = itemend;
  354. }
  355. else
  356. matches = lprefix;
  357. itemend = prefixend;
  358. }
  359. if(lsubstr) {
  360. if(itemend) {
  361. itemend->right = lsubstr;
  362. lsubstr->left = itemend;
  363. }
  364. else
  365. matches = lsubstr;
  366. }
  367. curr = prev = next = sel = matches;
  368. calcoffsets();
  369. }
  370. void
  371. paste(void) {
  372. char *p, *q;
  373. int di;
  374. unsigned long dl;
  375. Atom da;
  376. XGetWindowProperty(dc->dpy, win, utf8, 0, sizeof text - cursor, True,
  377. utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
  378. insert(p, (q = strchr(p, '\n')) ? q-p : strlen(p));
  379. XFree(p);
  380. drawmenu();
  381. }
  382. void
  383. readstdin(void) {
  384. char buf[sizeof text], *p;
  385. Item *item, *new;
  386. allitems = NULL;
  387. for(item = NULL; fgets(buf, sizeof buf, stdin); item = new) {
  388. if((p = strchr(buf, '\n')))
  389. *p = '\0';
  390. if(!(new = malloc(sizeof *new)))
  391. eprintf("cannot malloc %u bytes\n", sizeof *new);
  392. if(!(new->text = strdup(buf)))
  393. eprintf("cannot strdup %u bytes\n", strlen(buf));
  394. inputw = MAX(inputw, textw(dc, new->text));
  395. new->next = new->left = new->right = NULL;
  396. if(item)
  397. item->next = new;
  398. else
  399. allitems = new;
  400. }
  401. }
  402. void
  403. run(void) {
  404. XEvent ev;
  405. while(!XNextEvent(dc->dpy, &ev))
  406. switch(ev.type) {
  407. case Expose:
  408. if(ev.xexpose.count == 0)
  409. drawmenu();
  410. break;
  411. case KeyPress:
  412. keypress(&ev.xkey);
  413. break;
  414. case SelectionNotify:
  415. if(ev.xselection.property == utf8)
  416. paste();
  417. break;
  418. case VisibilityNotify:
  419. if(ev.xvisibility.state != VisibilityUnobscured)
  420. XRaiseWindow(dc->dpy, win);
  421. break;
  422. }
  423. }
  424. void
  425. setup(void) {
  426. int x, y, screen;
  427. XSetWindowAttributes wa;
  428. #ifdef XINERAMA
  429. int n;
  430. XineramaScreenInfo *info;
  431. #endif
  432. screen = DefaultScreen(dc->dpy);
  433. root = RootWindow(dc->dpy, screen);
  434. utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
  435. normcol[ColBG] = getcolor(dc, normbgcolor);
  436. normcol[ColFG] = getcolor(dc, normfgcolor);
  437. selcol[ColBG] = getcolor(dc, selbgcolor);
  438. selcol[ColFG] = getcolor(dc, selfgcolor);
  439. /* input window geometry */
  440. mh = (dc->font.height + 2) * (lines + 1);
  441. #ifdef XINERAMA
  442. if((info = XineramaQueryScreens(dc->dpy, &n))) {
  443. int i, di;
  444. unsigned int du;
  445. Window dw;
  446. XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
  447. for(i = 0; i < n; i++)
  448. if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
  449. break;
  450. x = info[i].x_org;
  451. y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
  452. mw = info[i].width;
  453. XFree(info);
  454. }
  455. else
  456. #endif
  457. {
  458. x = 0;
  459. y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh;
  460. mw = DisplayWidth(dc->dpy, screen);
  461. }
  462. /* input window */
  463. wa.override_redirect = True;
  464. wa.background_pixmap = ParentRelative;
  465. wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
  466. win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0,
  467. DefaultDepth(dc->dpy, screen), CopyFromParent,
  468. DefaultVisual(dc->dpy, screen),
  469. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  470. grabkeyboard();
  471. setcanvas(dc, win, mw, mh);
  472. inputw = MIN(inputw, mw/3);
  473. promptw = prompt ? MIN(textw(dc, prompt), mw/5) : 0;
  474. XMapRaised(dc->dpy, win);
  475. text[0] = '\0';
  476. match();
  477. }
  478. void
  479. usage(void) {
  480. fputs("usage: dmenu [-b] [-i] [-l lines] [-p prompt] [-fn font] [-nb color]\n"
  481. " [-nf color] [-sb color] [-sf color] [-v]\n", stderr);
  482. exit(EXIT_FAILURE);
  483. }
  484. int
  485. main(int argc, char *argv[]) {
  486. int i;
  487. progname = "dmenu";
  488. dc = initdraw();
  489. for(i = 1; i < argc; i++)
  490. /* single flags */
  491. if(!strcmp(argv[i], "-v")) {
  492. fputs("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n", stdout);
  493. exit(EXIT_SUCCESS);
  494. }
  495. else if(!strcmp(argv[i], "-b"))
  496. topbar = False;
  497. else if(!strcmp(argv[i], "-i")) {
  498. fstrncmp = strncasecmp;
  499. fstrstr = cistrstr;
  500. }
  501. else if(i == argc-1)
  502. usage();
  503. /* double flags */
  504. else if(!strcmp(argv[i], "-l")) {
  505. if((lines = atoi(argv[++i])) > 0)
  506. calcoffsets = calcoffsetsv;
  507. }
  508. else if(!strcmp(argv[i], "-p"))
  509. prompt = argv[++i];
  510. else if(!strcmp(argv[i], "-fn"))
  511. initfont(dc, argv[i++]);
  512. else if(!strcmp(argv[i], "-nb"))
  513. normbgcolor = argv[++i];
  514. else if(!strcmp(argv[i], "-nf"))
  515. normfgcolor = argv[++i];
  516. else if(!strcmp(argv[i], "-sb"))
  517. selbgcolor = argv[++i];
  518. else if(!strcmp(argv[i], "-sf"))
  519. selfgcolor = argv[++i];
  520. else
  521. usage();
  522. readstdin();
  523. setup();
  524. run();
  525. return EXIT_FAILURE; /* should not reach */
  526. }