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.
 
 
 
 
 
 

492 lines
10 KiB

  1. /* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
  2. * (C)opyright MMVI-MMVII Sander van Dijk <a dot h dot vandijk at gmail dot com>
  3. * See LICENSE file for license details.
  4. */
  5. #include "dmenu.h"
  6. #include <ctype.h>
  7. #include <locale.h>
  8. #include <stdlib.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <unistd.h>
  12. #include <sys/select.h>
  13. #include <sys/time.h>
  14. #include <X11/Xutil.h>
  15. #include <X11/keysym.h>
  16. #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
  17. typedef struct Item Item;
  18. struct Item {
  19. Item *next; /* traverses all items */
  20. Item *left, *right; /* traverses items matching current search pattern */
  21. char *text;
  22. };
  23. /* static */
  24. static char text[4096];
  25. static char *prompt = NULL;
  26. static int mw, mh;
  27. static int ret = 0;
  28. static int nitem = 0;
  29. static unsigned int cmdw = 0;
  30. static unsigned int promptw = 0;
  31. static unsigned int numlockmask = 0;
  32. static Bool running = True;
  33. static Item *allitems = NULL; /* first of all items */
  34. static Item *item = NULL; /* first of pattern matching items */
  35. static Item *sel = NULL;
  36. static Item *next = NULL;
  37. static Item *prev = NULL;
  38. static Item *curr = NULL;
  39. static Window root;
  40. static Window win;
  41. static void
  42. calcoffsets(void) {
  43. unsigned int tw, w;
  44. if(!curr)
  45. return;
  46. w = promptw + cmdw + 2 * SPACE;
  47. for(next = curr; next; next=next->right) {
  48. tw = textw(next->text);
  49. if(tw > mw / 3)
  50. tw = mw / 3;
  51. w += tw;
  52. if(w > mw)
  53. break;
  54. }
  55. w = promptw + cmdw + 2 * SPACE;
  56. for(prev = curr; prev && prev->left; prev=prev->left) {
  57. tw = textw(prev->left->text);
  58. if(tw > mw / 3)
  59. tw = mw / 3;
  60. w += tw;
  61. if(w > mw)
  62. break;
  63. }
  64. }
  65. static void
  66. drawmenu(void) {
  67. Item *i;
  68. dc.x = 0;
  69. dc.y = 0;
  70. dc.w = mw;
  71. dc.h = mh;
  72. drawtext(NULL, dc.norm);
  73. /* print prompt? */
  74. if(promptw) {
  75. dc.w = promptw;
  76. drawtext(prompt, dc.sel);
  77. }
  78. dc.x += promptw;
  79. dc.w = mw - promptw;
  80. /* print command */
  81. if(cmdw && item)
  82. dc.w = cmdw;
  83. drawtext(text[0] ? text : NULL, dc.norm);
  84. dc.x += cmdw;
  85. if(curr) {
  86. dc.w = SPACE;
  87. drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
  88. dc.x += dc.w;
  89. /* determine maximum items */
  90. for(i = curr; i != next; i=i->right) {
  91. dc.w = textw(i->text);
  92. if(dc.w > mw / 3)
  93. dc.w = mw / 3;
  94. drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
  95. dc.x += dc.w;
  96. }
  97. dc.x = mw - SPACE;
  98. dc.w = SPACE;
  99. drawtext(next ? ">" : NULL, dc.norm);
  100. }
  101. XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
  102. XFlush(dpy);
  103. }
  104. static void
  105. match(char *pattern) {
  106. unsigned int plen;
  107. Item *i, *j;
  108. if(!pattern)
  109. return;
  110. plen = strlen(pattern);
  111. item = j = NULL;
  112. nitem = 0;
  113. for(i = allitems; i; i=i->next)
  114. if(!plen || !strncmp(pattern, i->text, plen)) {
  115. if(!j)
  116. item = i;
  117. else
  118. j->right = i;
  119. i->left = j;
  120. i->right = NULL;
  121. j = i;
  122. nitem++;
  123. }
  124. for(i = allitems; i; i=i->next)
  125. if(plen && strncmp(pattern, i->text, plen)
  126. && strstr(i->text, pattern)) {
  127. if(!j)
  128. item = i;
  129. else
  130. j->right = i;
  131. i->left = j;
  132. i->right = NULL;
  133. j = i;
  134. nitem++;
  135. }
  136. curr = prev = next = sel = item;
  137. calcoffsets();
  138. }
  139. static void
  140. kpress(XKeyEvent * e) {
  141. char buf[32];
  142. int num, prev_nitem;
  143. unsigned int i, len;
  144. KeySym ksym;
  145. len = strlen(text);
  146. buf[0] = 0;
  147. num = XLookupString(e, buf, sizeof buf, &ksym, 0);
  148. if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
  149. || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
  150. || IsPrivateKeypadKey(ksym))
  151. return;
  152. /* first check if a control mask is omitted */
  153. if(e->state & ControlMask) {
  154. switch (ksym) {
  155. default: /* ignore other control sequences */
  156. return;
  157. case XK_bracketleft:
  158. ksym = XK_Escape;
  159. break;
  160. case XK_h:
  161. case XK_H:
  162. ksym = XK_BackSpace;
  163. break;
  164. case XK_i:
  165. case XK_I:
  166. ksym = XK_Tab;
  167. break;
  168. case XK_j:
  169. case XK_J:
  170. ksym = XK_Return;
  171. break;
  172. case XK_u:
  173. case XK_U:
  174. text[0] = 0;
  175. match(text);
  176. drawmenu();
  177. return;
  178. }
  179. }
  180. if(CLEANMASK(e->state) & Mod1Mask) {
  181. switch(ksym) {
  182. default: return;
  183. case XK_h:
  184. ksym = XK_Left;
  185. break;
  186. case XK_l:
  187. ksym = XK_Right;
  188. break;
  189. case XK_j:
  190. ksym = XK_Next;
  191. break;
  192. case XK_k:
  193. ksym = XK_Prior;
  194. break;
  195. case XK_g:
  196. ksym = XK_Home;
  197. break;
  198. case XK_G:
  199. ksym = XK_End;
  200. break;
  201. }
  202. }
  203. switch(ksym) {
  204. default:
  205. if(num && !iscntrl((int) buf[0])) {
  206. buf[num] = 0;
  207. if(len > 0)
  208. strncat(text, buf, sizeof text);
  209. else
  210. strncpy(text, buf, sizeof text);
  211. match(text);
  212. }
  213. break;
  214. case XK_BackSpace:
  215. if((i = len)) {
  216. prev_nitem = nitem;
  217. do {
  218. text[--i] = 0;
  219. match(text);
  220. } while(i && nitem && prev_nitem == nitem);
  221. match(text);
  222. }
  223. break;
  224. case XK_End:
  225. if(!item)
  226. return;
  227. while(next) {
  228. sel = curr = next;
  229. calcoffsets();
  230. }
  231. while(sel && sel->right)
  232. sel = sel->right;
  233. break;
  234. case XK_Escape:
  235. ret = 1;
  236. running = False;
  237. break;
  238. case XK_Home:
  239. if(!item)
  240. return;
  241. sel = curr = item;
  242. calcoffsets();
  243. break;
  244. case XK_Left:
  245. if(!(sel && sel->left))
  246. return;
  247. sel=sel->left;
  248. if(sel->right == curr) {
  249. curr = prev;
  250. calcoffsets();
  251. }
  252. break;
  253. case XK_Next:
  254. if(!next)
  255. return;
  256. sel = curr = next;
  257. calcoffsets();
  258. break;
  259. case XK_Prior:
  260. if(!prev)
  261. return;
  262. sel = curr = prev;
  263. calcoffsets();
  264. break;
  265. case XK_Return:
  266. if((e->state & ShiftMask) && text)
  267. fprintf(stdout, "%s", text);
  268. else if(sel)
  269. fprintf(stdout, "%s", sel->text);
  270. else if(text)
  271. fprintf(stdout, "%s", text);
  272. fflush(stdout);
  273. running = False;
  274. break;
  275. case XK_Right:
  276. if(!(sel && sel->right))
  277. return;
  278. sel=sel->right;
  279. if(sel == next) {
  280. curr = next;
  281. calcoffsets();
  282. }
  283. break;
  284. case XK_Tab:
  285. if(!sel)
  286. return;
  287. strncpy(text, sel->text, sizeof text);
  288. match(text);
  289. break;
  290. }
  291. drawmenu();
  292. }
  293. static char *
  294. readstdin(void) {
  295. static char *maxname = NULL;
  296. char *p, buf[1024];
  297. unsigned int len = 0, max = 0;
  298. Item *i, *new;
  299. i = 0;
  300. while(fgets(buf, sizeof buf, stdin)) {
  301. len = strlen(buf);
  302. if (buf[len - 1] == '\n')
  303. buf[len - 1] = 0;
  304. p = estrdup(buf);
  305. if(max < len) {
  306. maxname = p;
  307. max = len;
  308. }
  309. new = emalloc(sizeof(Item));
  310. new->next = new->left = new->right = NULL;
  311. new->text = p;
  312. if(!i)
  313. allitems = new;
  314. else
  315. i->next = new;
  316. i = new;
  317. }
  318. return maxname;
  319. }
  320. /* extern */
  321. int screen;
  322. Display *dpy;
  323. DC dc = {0};
  324. int
  325. main(int argc, char *argv[]) {
  326. Bool bottom = False;
  327. char *font = FONT;
  328. char *maxname;
  329. char *normbg = NORMBGCOLOR;
  330. char *normfg = NORMFGCOLOR;
  331. char *selbg = SELBGCOLOR;
  332. char *selfg = SELFGCOLOR;
  333. fd_set rd;
  334. int i, j;
  335. struct timeval timeout;
  336. Item *itm;
  337. XEvent ev;
  338. XModifierKeymap *modmap;
  339. XSetWindowAttributes wa;
  340. timeout.tv_usec = 0;
  341. timeout.tv_sec = 3;
  342. /* command line args */
  343. for(i = 1; i < argc; i++)
  344. if(!strncmp(argv[i], "-b", 3)) {
  345. bottom = True;
  346. }
  347. else if(!strncmp(argv[i], "-fn", 4)) {
  348. if(++i < argc) font = argv[i];
  349. }
  350. else if(!strncmp(argv[i], "-nb", 4)) {
  351. if(++i < argc) normbg = argv[i];
  352. }
  353. else if(!strncmp(argv[i], "-nf", 4)) {
  354. if(++i < argc) normfg = argv[i];
  355. }
  356. else if(!strncmp(argv[i], "-p", 3)) {
  357. if(++i < argc) prompt = argv[i];
  358. }
  359. else if(!strncmp(argv[i], "-sb", 4)) {
  360. if(++i < argc) selbg = argv[i];
  361. }
  362. else if(!strncmp(argv[i], "-sf", 4)) {
  363. if(++i < argc) selfg = argv[i];
  364. }
  365. else if(!strncmp(argv[i], "-t", 3)) {
  366. if(++i < argc) timeout.tv_sec = atoi(argv[i]);
  367. }
  368. else if(!strncmp(argv[i], "-v", 3)) {
  369. fputs("dmenu-"VERSION", (C)opyright MMVI-MMVII Anselm R. Garbe\n", stdout);
  370. exit(EXIT_SUCCESS);
  371. }
  372. else
  373. eprint("usage: dmenu [-b] [-fn <font>] [-nb <color>] [-nf <color>] [-p <prompt>]\n"
  374. " [-sb <color>] [-sf <color>] [-t <seconds>] [-v]\n", stdout);
  375. setlocale(LC_CTYPE, "");
  376. dpy = XOpenDisplay(0);
  377. if(!dpy)
  378. eprint("dmenu: cannot open display\n");
  379. screen = DefaultScreen(dpy);
  380. root = RootWindow(dpy, screen);
  381. /* Note, the select() construction allows to grab all keypresses as
  382. * early as possible, to not loose them. But if there is no standard
  383. * input supplied, we will make sure to exit after MAX_WAIT_STDIN
  384. * seconds. This is convenience behavior for rapid typers.
  385. */
  386. while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
  387. GrabModeAsync, CurrentTime) != GrabSuccess)
  388. usleep(1000);
  389. FD_ZERO(&rd);
  390. FD_SET(STDIN_FILENO, &rd);
  391. if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
  392. goto UninitializedEnd;
  393. maxname = readstdin();
  394. /* init modifier map */
  395. modmap = XGetModifierMapping(dpy);
  396. for (i = 0; i < 8; i++) {
  397. for (j = 0; j < modmap->max_keypermod; j++) {
  398. if(modmap->modifiermap[i * modmap->max_keypermod + j] == XKeysymToKeycode(dpy, XK_Num_Lock))
  399. numlockmask = (1 << i);
  400. }
  401. }
  402. XFreeModifiermap(modmap);
  403. /* style */
  404. dc.norm[ColBG] = getcolor(normbg);
  405. dc.norm[ColFG] = getcolor(normfg);
  406. dc.sel[ColBG] = getcolor(selbg);
  407. dc.sel[ColFG] = getcolor(selfg);
  408. setfont(font);
  409. /* menu window */
  410. wa.override_redirect = 1;
  411. wa.background_pixmap = ParentRelative;
  412. wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
  413. mw = DisplayWidth(dpy, screen);
  414. mh = dc.font.height + 2;
  415. win = XCreateWindow(dpy, root, 0,
  416. bottom ? DisplayHeight(dpy, screen) - mh : 0, mw, mh, 0,
  417. DefaultDepth(dpy, screen), CopyFromParent,
  418. DefaultVisual(dpy, screen),
  419. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  420. /* pixmap */
  421. dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
  422. dc.gc = XCreateGC(dpy, root, 0, 0);
  423. XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
  424. if(maxname)
  425. cmdw = textw(maxname);
  426. if(cmdw > mw / 3)
  427. cmdw = mw / 3;
  428. if(prompt)
  429. promptw = textw(prompt);
  430. if(promptw > mw / 5)
  431. promptw = mw / 5;
  432. text[0] = 0;
  433. match(text);
  434. XMapRaised(dpy, win);
  435. drawmenu();
  436. XSync(dpy, False);
  437. /* main event loop */
  438. while(running && !XNextEvent(dpy, &ev))
  439. switch (ev.type) {
  440. default: /* ignore all crap */
  441. break;
  442. case KeyPress:
  443. kpress(&ev.xkey);
  444. break;
  445. case Expose:
  446. if(ev.xexpose.count == 0)
  447. drawmenu();
  448. break;
  449. }
  450. /* cleanup */
  451. while(allitems) {
  452. itm = allitems->next;
  453. free(allitems->text);
  454. free(allitems);
  455. allitems = itm;
  456. }
  457. if(dc.font.set)
  458. XFreeFontSet(dpy, dc.font.set);
  459. else
  460. XFreeFont(dpy, dc.font.xfont);
  461. XFreePixmap(dpy, dc.drawable);
  462. XFreeGC(dpy, dc.gc);
  463. XDestroyWindow(dpy, win);
  464. UninitializedEnd:
  465. XUngrabKeyboard(dpy, CurrentTime);
  466. XCloseDisplay(dpy);
  467. return ret;
  468. }