dmenu for lunch applications in dwm
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.

426 lines
8.1 KiB

18 years ago
  1. /*
  2. * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
  3. * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
  4. * See LICENSE file for license details.
  5. */
  6. #include "dmenu.h"
  7. #include <ctype.h>
  8. #include <stdlib.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <unistd.h>
  12. #include <X11/cursorfont.h>
  13. #include <X11/Xutil.h>
  14. #include <X11/keysym.h>
  15. typedef struct Item Item;
  16. struct Item {
  17. Item *next; /* traverses all items */
  18. Item *left, *right; /* traverses items matching current search pattern */
  19. char *text;
  20. };
  21. static Display *dpy;
  22. static Window root;
  23. static Window win;
  24. static Bool done = False;
  25. static Item *allitem = NULL; /* first of all items */
  26. static Item *item = NULL; /* first of pattern matching items */
  27. static Item *sel = NULL;
  28. static Item *nextoff = NULL;
  29. static Item *prevoff = NULL;
  30. static Item *curroff = NULL;
  31. static int screen, mx, my, mw, mh;
  32. static char *title = NULL;
  33. static char text[4096];
  34. static int ret = 0;
  35. static int nitem = 0;
  36. static unsigned int cmdw = 0;
  37. static unsigned int tw = 0;
  38. static unsigned int cw = 0;
  39. static const int seek = 30; /* 30px */
  40. static Brush brush = {0};
  41. static void draw_menu();
  42. static void kpress(XKeyEvent * e);
  43. static char version[] = "dmenu - " VERSION ", (C)opyright MMVI Anselm R. Garbe\n";
  44. static void
  45. update_offsets()
  46. {
  47. unsigned int tw, w = cmdw + 2 * seek;
  48. if(!curroff)
  49. return;
  50. for(nextoff = curroff; nextoff; nextoff=nextoff->right) {
  51. tw = textw(&brush.font, nextoff->text);
  52. if(tw > mw / 3)
  53. tw = mw / 3;
  54. w += tw + brush.font.height;
  55. if(w > mw)
  56. break;
  57. }
  58. w = cmdw + 2 * seek;
  59. for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) {
  60. tw = textw(&brush.font, prevoff->left->text);
  61. if(tw > mw / 3)
  62. tw = mw / 3;
  63. w += tw + brush.font.height;
  64. if(w > mw)
  65. break;
  66. }
  67. }
  68. static void
  69. update_items(char *pattern)
  70. {
  71. unsigned int plen = strlen(pattern);
  72. Item *i, *j;
  73. if(!pattern)
  74. return;
  75. if(!title || *pattern)
  76. cmdw = cw;
  77. else
  78. cmdw = tw;
  79. item = j = NULL;
  80. nitem = 0;
  81. for(i = allitem; i; i=i->next)
  82. if(!plen || !strncmp(pattern, i->text, plen)) {
  83. if(!j)
  84. item = i;
  85. else
  86. j->right = i;
  87. i->left = j;
  88. i->right = NULL;
  89. j = i;
  90. nitem++;
  91. }
  92. for(i = allitem; i; i=i->next)
  93. if(plen && strncmp(pattern, i->text, plen)
  94. && strstr(i->text, pattern)) {
  95. if(!j)
  96. item = i;
  97. else
  98. j->right = i;
  99. i->left = j;
  100. i->right = NULL;
  101. j = i;
  102. nitem++;
  103. }
  104. curroff = prevoff = nextoff = sel = item;
  105. update_offsets();
  106. }
  107. /* creates brush structs for brush mode drawing */
  108. static void
  109. draw_menu()
  110. {
  111. Item *i;
  112. brush.x = 0;
  113. brush.y = 0;
  114. brush.w = mw;
  115. brush.h = mh;
  116. draw(dpy, &brush, False, 0);
  117. /* print command */
  118. if(!title || text[0]) {
  119. cmdw = cw;
  120. if(cmdw && item)
  121. brush.w = cmdw;
  122. draw(dpy, &brush, False, text);
  123. }
  124. else {
  125. cmdw = tw;
  126. brush.w = cmdw;
  127. draw(dpy, &brush, False, title);
  128. }
  129. brush.x += brush.w;
  130. if(curroff) {
  131. brush.w = seek;
  132. draw(dpy, &brush, False, (curroff && curroff->left) ? "<" : 0);
  133. brush.x += brush.w;
  134. /* determine maximum items */
  135. for(i = curroff; i != nextoff; i=i->right) {
  136. brush.border = False;
  137. brush.w = textw(&brush.font, i->text);
  138. if(brush.w > mw / 3)
  139. brush.w = mw / 3;
  140. brush.w += brush.font.height;
  141. if(sel == i) {
  142. swap((void **)&brush.fg, (void **)&brush.bg);
  143. draw(dpy, &brush, True, i->text);
  144. swap((void **)&brush.fg, (void **)&brush.bg);
  145. }
  146. else
  147. draw(dpy, &brush, False, i->text);
  148. brush.x += brush.w;
  149. }
  150. brush.x = mw - seek;
  151. brush.w = seek;
  152. draw(dpy, &brush, False, nextoff ? ">" : 0);
  153. }
  154. XCopyArea(dpy, brush.drawable, win, brush.gc, 0, 0, mw, mh, 0, 0);
  155. XFlush(dpy);
  156. }
  157. static void
  158. kpress(XKeyEvent * e)
  159. {
  160. KeySym ksym;
  161. char buf[32];
  162. int num, prev_nitem;
  163. unsigned int i, len = strlen(text);
  164. buf[0] = 0;
  165. num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
  166. if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
  167. || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
  168. || IsPrivateKeypadKey(ksym))
  169. return;
  170. /* first check if a control mask is omitted */
  171. if(e->state & ControlMask) {
  172. switch (ksym) {
  173. default: /* ignore other control sequences */
  174. return;
  175. break;
  176. case XK_h:
  177. ksym = XK_BackSpace;
  178. break;
  179. case XK_U:
  180. case XK_u:
  181. text[0] = 0;
  182. update_items(text);
  183. draw_menu();
  184. return;
  185. break;
  186. case XK_bracketleft:
  187. ksym = XK_Escape;
  188. break;
  189. }
  190. }
  191. switch(ksym) {
  192. case XK_Left:
  193. if(!(sel && sel->left))
  194. return;
  195. sel=sel->left;
  196. if(sel->right == curroff) {
  197. curroff = prevoff;
  198. update_offsets();
  199. }
  200. break;
  201. case XK_Tab:
  202. if(!sel)
  203. return;
  204. strncpy(text, sel->text, sizeof(text));
  205. update_items(text);
  206. break;
  207. case XK_Right:
  208. if(!(sel && sel->right))
  209. return;
  210. sel=sel->right;
  211. if(sel == nextoff) {
  212. curroff = nextoff;
  213. update_offsets();
  214. }
  215. break;
  216. case XK_Return:
  217. if(e->state & ShiftMask) {
  218. if(text)
  219. fprintf(stdout, "%s", text);
  220. }
  221. else if(sel)
  222. fprintf(stdout, "%s", sel->text);
  223. else if(text)
  224. fprintf(stdout, "%s", text);
  225. fflush(stdout);
  226. done = True;
  227. break;
  228. case XK_Escape:
  229. ret = 1;
  230. done = True;
  231. break;
  232. case XK_BackSpace:
  233. if((i = len)) {
  234. prev_nitem = nitem;
  235. do {
  236. text[--i] = 0;
  237. update_items(text);
  238. } while(i && nitem && prev_nitem == nitem);
  239. update_items(text);
  240. }
  241. break;
  242. default:
  243. if(num && !iscntrl((int) buf[0])) {
  244. buf[num] = 0;
  245. if(len > 0)
  246. strncat(text, buf, sizeof(text));
  247. else
  248. strncpy(text, buf, sizeof(text));
  249. update_items(text);
  250. }
  251. }
  252. draw_menu();
  253. }
  254. static char *
  255. read_allitems()
  256. {
  257. static char *maxname = NULL;
  258. char *p, buf[1024];
  259. unsigned int len = 0, max = 0;
  260. Item *i, *new;
  261. i = 0;
  262. while(fgets(buf, sizeof(buf), stdin)) {
  263. len = strlen(buf);
  264. if (buf[len - 1] == '\n')
  265. buf[len - 1] = 0;
  266. p = estrdup(buf);
  267. if(max < len) {
  268. maxname = p;
  269. max = len;
  270. }
  271. new = emalloc(sizeof(Item));
  272. new->next = new->left = new->right = NULL;
  273. new->text = p;
  274. if(!i)
  275. allitem = new;
  276. else
  277. i->next = new;
  278. i = new;
  279. }
  280. return maxname;
  281. }
  282. int
  283. main(int argc, char *argv[])
  284. {
  285. int i;
  286. XSetWindowAttributes wa;
  287. char *maxname;
  288. XEvent ev;
  289. /* command line args */
  290. for(i = 1; i < argc; i++) {
  291. if (argv[i][0] == '-')
  292. switch (argv[i][1]) {
  293. case 'v':
  294. fprintf(stdout, "%s", version);
  295. exit(0);
  296. break;
  297. case 't':
  298. if(++i < argc) {
  299. title = argv[i];
  300. break;
  301. }
  302. default:
  303. eprint("usage: dmenu [-v] [-t <title>]\n");
  304. break;
  305. }
  306. else
  307. eprint("usage: dmenu [-v] [-t <title>]\n");
  308. }
  309. dpy = XOpenDisplay(0);
  310. if(!dpy)
  311. eprint("dmenu: cannot open dpy\n");
  312. screen = DefaultScreen(dpy);
  313. root = RootWindow(dpy, screen);
  314. maxname = read_allitems();
  315. /* grab as early as possible, but after reading all items!!! */
  316. while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
  317. GrabModeAsync, CurrentTime) != GrabSuccess)
  318. usleep(1000);
  319. /* style */
  320. loadcolors(dpy, screen, &brush, BGCOLOR, FGCOLOR, BORDERCOLOR);
  321. loadfont(dpy, &brush.font, FONT);
  322. wa.override_redirect = 1;
  323. wa.background_pixmap = ParentRelative;
  324. wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
  325. mx = my = 0;
  326. mw = DisplayWidth(dpy, screen);
  327. mh = texth(&brush.font);
  328. win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
  329. DefaultDepth(dpy, screen), CopyFromParent,
  330. DefaultVisual(dpy, screen),
  331. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  332. XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
  333. XFlush(dpy);
  334. /* pixmap */
  335. brush.gc = XCreateGC(dpy, root, 0, 0);
  336. brush.drawable = XCreatePixmap(dpy, win, mw, mh,
  337. DefaultDepth(dpy, screen));
  338. XFlush(dpy);
  339. if(maxname)
  340. cw = textw(&brush.font, maxname) + brush.font.height;
  341. if(cw > mw / 3)
  342. cw = mw / 3;
  343. if(title) {
  344. tw = textw(&brush.font, title) + brush.font.height;
  345. if(tw > mw / 3)
  346. tw = mw / 3;
  347. }
  348. cmdw = title ? tw : cw;
  349. text[0] = 0;
  350. update_items(text);
  351. XMapRaised(dpy, win);
  352. draw_menu();
  353. XFlush(dpy);
  354. /* main event loop */
  355. while(!done && !XNextEvent(dpy, &ev)) {
  356. switch (ev.type) {
  357. case KeyPress:
  358. kpress(&ev.xkey);
  359. break;
  360. case Expose:
  361. if(ev.xexpose.count == 0)
  362. draw_menu();
  363. break;
  364. default:
  365. break;
  366. }
  367. }
  368. XUngrabKeyboard(dpy, CurrentTime);
  369. XFreePixmap(dpy, brush.drawable);
  370. XFreeGC(dpy, brush.gc);
  371. XDestroyWindow(dpy, win);
  372. XCloseDisplay(dpy);
  373. return ret;
  374. }