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.
 
 
 
 
 
 

553 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 field */
  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_d:
  178. ksym = XK_Delete;
  179. break;
  180. case XK_e:
  181. ksym = XK_End;
  182. break;
  183. case XK_f:
  184. ksym = XK_Right;
  185. break;
  186. case XK_h:
  187. ksym = XK_BackSpace;
  188. break;
  189. case XK_i:
  190. ksym = XK_Tab;
  191. break;
  192. case XK_j:
  193. ksym = XK_Return;
  194. break;
  195. case XK_k: /* delete right */
  196. text[cursor] = '\0';
  197. match();
  198. break;
  199. case XK_n:
  200. ksym = XK_Down;
  201. break;
  202. case XK_p:
  203. ksym = XK_Up;
  204. break;
  205. case XK_u: /* delete left */
  206. insert(NULL, -cursor);
  207. break;
  208. case XK_w: /* delete word */
  209. if(cursor == 0)
  210. return;
  211. n = 0;
  212. while(cursor - n++ > 0 && text[cursor - n] == ' ');
  213. while(cursor - n++ > 0 && text[cursor - n] != ' ');
  214. insert(NULL, 1-n);
  215. break;
  216. case XK_y: /* paste selection */
  217. XConvertSelection(dc->dpy, XA_PRIMARY, utf8, None, win, CurrentTime);
  218. /* causes SelectionNotify event */
  219. return;
  220. }
  221. }
  222. switch(ksym) {
  223. default:
  224. if(!iscntrl((int)*buf))
  225. insert(buf, MIN(strlen(buf), sizeof text - cursor));
  226. break;
  227. case XK_BackSpace:
  228. if(cursor == 0)
  229. return;
  230. for(n = 1; cursor - n > 0 && !UTF8_CODEPOINT(text[cursor - n]); n++);
  231. insert(NULL, -n);
  232. break;
  233. case XK_Delete:
  234. if(cursor == len)
  235. return;
  236. for(n = 1; cursor + n < len && !UTF8_CODEPOINT(text[cursor + n]); n++);
  237. cursor += n;
  238. insert(NULL, -n);
  239. break;
  240. case XK_End:
  241. if(cursor < len) {
  242. cursor = len;
  243. break;
  244. }
  245. while(next) {
  246. sel = curr = next;
  247. calcoffsets();
  248. }
  249. while(sel && sel->right)
  250. sel = sel->right;
  251. break;
  252. case XK_Escape:
  253. exit(EXIT_FAILURE);
  254. case XK_Home:
  255. if(sel == matches) {
  256. cursor = 0;
  257. break;
  258. }
  259. sel = curr = matches;
  260. calcoffsets();
  261. break;
  262. case XK_Left:
  263. if(cursor > 0 && (!sel || !sel->left || lines > 0)) {
  264. while(cursor-- > 0 && !UTF8_CODEPOINT(text[cursor]));
  265. break;
  266. }
  267. else if(lines > 0)
  268. return;
  269. case XK_Up:
  270. if(!sel || !sel->left)
  271. return;
  272. sel = sel->left;
  273. if(sel->right == curr) {
  274. curr = prev;
  275. calcoffsets();
  276. }
  277. break;
  278. case XK_Next:
  279. if(!next)
  280. return;
  281. sel = curr = next;
  282. calcoffsets();
  283. break;
  284. case XK_Prior:
  285. if(!prev)
  286. return;
  287. sel = curr = prev;
  288. calcoffsets();
  289. break;
  290. case XK_Return:
  291. case XK_KP_Enter:
  292. fputs((sel && !(e->state & ShiftMask)) ? sel->text : text, stdout);
  293. fflush(stdout);
  294. exit(EXIT_SUCCESS);
  295. case XK_Right:
  296. if(cursor < len) {
  297. while(cursor++ < len && !UTF8_CODEPOINT(text[cursor]));
  298. break;
  299. }
  300. else if(lines > 0)
  301. return;
  302. case XK_Down:
  303. if(!sel || !sel->right)
  304. return;
  305. sel = sel->right;
  306. if(sel == next) {
  307. curr = next;
  308. calcoffsets();
  309. }
  310. break;
  311. case XK_Tab:
  312. if(!sel)
  313. return;
  314. strncpy(text, sel->text, sizeof text);
  315. cursor = strlen(text);
  316. match();
  317. break;
  318. }
  319. drawmenu();
  320. }
  321. void
  322. match(void) {
  323. size_t len;
  324. Item *item, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
  325. len = strlen(text);
  326. matches = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
  327. for(item = allitems; item; item = item->next)
  328. if(!fstrncmp(text, item->text, len + 1))
  329. appenditem(item, &lexact, &exactend);
  330. else if(!fstrncmp(text, item->text, len))
  331. appenditem(item, &lprefix, &prefixend);
  332. else if(fstrstr(item->text, text))
  333. appenditem(item, &lsubstr, &substrend);
  334. if(lexact) {
  335. matches = lexact;
  336. itemend = exactend;
  337. }
  338. if(lprefix) {
  339. if(itemend) {
  340. itemend->right = lprefix;
  341. lprefix->left = itemend;
  342. }
  343. else
  344. matches = lprefix;
  345. itemend = prefixend;
  346. }
  347. if(lsubstr) {
  348. if(itemend) {
  349. itemend->right = lsubstr;
  350. lsubstr->left = itemend;
  351. }
  352. else
  353. matches = lsubstr;
  354. }
  355. curr = prev = next = sel = matches;
  356. calcoffsets();
  357. }
  358. void
  359. paste(void) {
  360. char *p, *q;
  361. int di;
  362. unsigned long dl;
  363. Atom da;
  364. XGetWindowProperty(dc->dpy, win, utf8, 0, sizeof text - cursor, True,
  365. utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
  366. insert(p, (q = strchr(p, '\n')) ? q-p : strlen(p));
  367. XFree(p);
  368. drawmenu();
  369. }
  370. void
  371. readstdin(void) {
  372. char buf[sizeof text], *p;
  373. Item *item, *new;
  374. allitems = NULL;
  375. for(item = NULL; fgets(buf, sizeof buf, stdin); item = new) {
  376. if((p = strchr(buf, '\n')))
  377. *p = '\0';
  378. if(!(new = malloc(sizeof *new)))
  379. eprintf("cannot malloc %u bytes\n", sizeof *new);
  380. if(!(new->text = strdup(buf)))
  381. eprintf("cannot strdup %u bytes\n", strlen(buf));
  382. inputw = MAX(inputw, textw(dc, new->text));
  383. new->next = new->left = new->right = NULL;
  384. if(item)
  385. item->next = new;
  386. else
  387. allitems = new;
  388. }
  389. }
  390. void
  391. run(void) {
  392. XEvent ev;
  393. while(!XNextEvent(dc->dpy, &ev))
  394. switch(ev.type) {
  395. case Expose:
  396. if(ev.xexpose.count == 0)
  397. drawmenu();
  398. break;
  399. case KeyPress:
  400. keypress(&ev.xkey);
  401. break;
  402. case SelectionNotify:
  403. if(ev.xselection.property == utf8)
  404. paste();
  405. break;
  406. case VisibilityNotify:
  407. if(ev.xvisibility.state != VisibilityUnobscured)
  408. XRaiseWindow(dc->dpy, win);
  409. break;
  410. }
  411. }
  412. void
  413. setup(void) {
  414. int x, y, screen;
  415. XSetWindowAttributes wa;
  416. #ifdef XINERAMA
  417. int n;
  418. XineramaScreenInfo *info;
  419. #endif
  420. screen = DefaultScreen(dc->dpy);
  421. root = RootWindow(dc->dpy, screen);
  422. utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
  423. normcol[ColBG] = getcolor(dc, normbgcolor);
  424. normcol[ColFG] = getcolor(dc, normfgcolor);
  425. selcol[ColBG] = getcolor(dc, selbgcolor);
  426. selcol[ColFG] = getcolor(dc, selfgcolor);
  427. /* menu geometry */
  428. mh = (dc->font.height + 2) * (lines + 1);
  429. #ifdef XINERAMA
  430. if((info = XineramaQueryScreens(dc->dpy, &n))) {
  431. int i, di;
  432. unsigned int du;
  433. Window dw;
  434. XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
  435. for(i = 0; i < n; i++)
  436. if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
  437. break;
  438. x = info[i].x_org;
  439. y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
  440. mw = info[i].width;
  441. XFree(info);
  442. }
  443. else
  444. #endif
  445. {
  446. x = 0;
  447. y = topbar ? 0 : DisplayHeight(dc->dpy, screen) - mh;
  448. mw = DisplayWidth(dc->dpy, screen);
  449. }
  450. /* menu window */
  451. wa.override_redirect = True;
  452. wa.background_pixmap = ParentRelative;
  453. wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
  454. win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0,
  455. DefaultDepth(dc->dpy, screen), CopyFromParent,
  456. DefaultVisual(dc->dpy, screen),
  457. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  458. grabkeyboard();
  459. setcanvas(dc, mw, mh);
  460. inputw = MIN(inputw, mw/3);
  461. promptw = prompt ? MIN(textw(dc, prompt), mw/5) : 0;
  462. XMapRaised(dc->dpy, win);
  463. text[0] = '\0';
  464. match();
  465. }
  466. void
  467. usage(void) {
  468. fputs("usage: dmenu [-b] [-i] [-l lines] [-p prompt] [-fn font] [-nb color]\n"
  469. " [-nf color] [-sb color] [-sf color] [-v]\n", stderr);
  470. exit(EXIT_FAILURE);
  471. }
  472. int
  473. main(int argc, char *argv[]) {
  474. int i;
  475. progname = "dmenu";
  476. dc = initdraw();
  477. for(i = 1; i < argc; i++)
  478. /* single flags */
  479. if(!strcmp(argv[i], "-v")) {
  480. fputs("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n", stdout);
  481. exit(EXIT_SUCCESS);
  482. }
  483. else if(!strcmp(argv[i], "-b"))
  484. topbar = False;
  485. else if(!strcmp(argv[i], "-i")) {
  486. fstrncmp = strncasecmp;
  487. fstrstr = cistrstr;
  488. }
  489. else if(i == argc-1)
  490. usage();
  491. /* double flags */
  492. else if(!strcmp(argv[i], "-l"))
  493. lines = atoi(argv[++i]);
  494. else if(!strcmp(argv[i], "-p"))
  495. prompt = argv[++i];
  496. else if(!strcmp(argv[i], "-fn"))
  497. initfont(dc, argv[++i]);
  498. else if(!strcmp(argv[i], "-nb"))
  499. normbgcolor = argv[++i];
  500. else if(!strcmp(argv[i], "-nf"))
  501. normfgcolor = argv[++i];
  502. else if(!strcmp(argv[i], "-sb"))
  503. selbgcolor = argv[++i];
  504. else if(!strcmp(argv[i], "-sf"))
  505. selfgcolor = argv[++i];
  506. else
  507. usage();
  508. readstdin();
  509. setup();
  510. run();
  511. return EXIT_FAILURE; /* should not reach */
  512. }