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.
 
 
 
 
 
 

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