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.
 
 
 
 
 
 

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