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.
 
 
 
 
 
 

396 lines
7.6 KiB

  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 <sys/select.h>
  13. #include <sys/time.h>
  14. #include <X11/cursorfont.h>
  15. #include <X11/Xutil.h>
  16. #include <X11/keysym.h>
  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 int mx, my, mw, mh;
  26. static int ret = 0;
  27. static int nitem = 0;
  28. static unsigned int cmdw = 0;
  29. static Bool running = True;
  30. static Item *allitems = NULL; /* first of all items */
  31. static Item *item = NULL; /* first of pattern matching items */
  32. static Item *sel = NULL;
  33. static Item *next = NULL;
  34. static Item *prev = NULL;
  35. static Item *curr = NULL;
  36. static Window root;
  37. static Window win;
  38. static void
  39. calcoffsets() {
  40. unsigned int tw, w;
  41. if(!curr)
  42. return;
  43. w = cmdw + 2 * SPACE;
  44. for(next = curr; next; next=next->right) {
  45. tw = textw(next->text);
  46. if(tw > mw / 3)
  47. tw = mw / 3;
  48. w += tw;
  49. if(w > mw)
  50. break;
  51. }
  52. w = cmdw + 2 * SPACE;
  53. for(prev = curr; prev && prev->left; prev=prev->left) {
  54. tw = textw(prev->left->text);
  55. if(tw > mw / 3)
  56. tw = mw / 3;
  57. w += tw;
  58. if(w > mw)
  59. break;
  60. }
  61. }
  62. static void
  63. drawmenu() {
  64. Item *i;
  65. dc.x = 0;
  66. dc.y = 0;
  67. dc.w = mw;
  68. dc.h = mh;
  69. drawtext(NULL, dc.norm);
  70. /* print command */
  71. if(cmdw && item)
  72. dc.w = cmdw;
  73. drawtext(text[0] ? text : NULL, dc.norm);
  74. dc.x += cmdw;
  75. if(curr) {
  76. dc.w = SPACE;
  77. drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
  78. dc.x += dc.w;
  79. /* determine maximum items */
  80. for(i = curr; i != next; i=i->right) {
  81. dc.w = textw(i->text);
  82. if(dc.w > mw / 3)
  83. dc.w = mw / 3;
  84. drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
  85. dc.x += dc.w;
  86. }
  87. dc.x = mw - SPACE;
  88. dc.w = SPACE;
  89. drawtext(next ? ">" : NULL, dc.norm);
  90. }
  91. XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
  92. XFlush(dpy);
  93. }
  94. static void
  95. match(char *pattern) {
  96. unsigned int plen;
  97. Item *i, *j;
  98. if(!pattern)
  99. return;
  100. plen = strlen(pattern);
  101. item = j = NULL;
  102. nitem = 0;
  103. for(i = allitems; i; i=i->next)
  104. if(!plen || !strncmp(pattern, i->text, plen)) {
  105. if(!j)
  106. item = i;
  107. else
  108. j->right = i;
  109. i->left = j;
  110. i->right = NULL;
  111. j = i;
  112. nitem++;
  113. }
  114. for(i = allitems; i; i=i->next)
  115. if(plen && strncmp(pattern, i->text, plen)
  116. && strstr(i->text, pattern)) {
  117. if(!j)
  118. item = i;
  119. else
  120. j->right = i;
  121. i->left = j;
  122. i->right = NULL;
  123. j = i;
  124. nitem++;
  125. }
  126. curr = prev = next = sel = item;
  127. calcoffsets();
  128. }
  129. static void
  130. kpress(XKeyEvent * e) {
  131. char buf[32];
  132. int num, prev_nitem;
  133. unsigned int i, len;
  134. KeySym ksym;
  135. len = strlen(text);
  136. buf[0] = 0;
  137. num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
  138. if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
  139. || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
  140. || IsPrivateKeypadKey(ksym))
  141. return;
  142. /* first check if a control mask is omitted */
  143. if(e->state & ControlMask) {
  144. switch (ksym) {
  145. default: /* ignore other control sequences */
  146. return;
  147. break;
  148. case XK_h:
  149. case XK_H:
  150. ksym = XK_BackSpace;
  151. break;
  152. case XK_u:
  153. case XK_U:
  154. text[0] = 0;
  155. match(text);
  156. drawmenu();
  157. return;
  158. break;
  159. }
  160. }
  161. switch(ksym) {
  162. case XK_Left:
  163. if(!(sel && sel->left))
  164. return;
  165. sel=sel->left;
  166. if(sel->right == curr) {
  167. curr = prev;
  168. calcoffsets();
  169. }
  170. break;
  171. case XK_Tab:
  172. if(!sel)
  173. return;
  174. strncpy(text, sel->text, sizeof(text));
  175. match(text);
  176. break;
  177. case XK_Right:
  178. if(!(sel && sel->right))
  179. return;
  180. sel=sel->right;
  181. if(sel == next) {
  182. curr = next;
  183. calcoffsets();
  184. }
  185. break;
  186. case XK_Return:
  187. if(e->state & ShiftMask) {
  188. if(text)
  189. fprintf(stdout, "%s", text);
  190. }
  191. else if(sel)
  192. fprintf(stdout, "%s", sel->text);
  193. else if(text)
  194. fprintf(stdout, "%s", text);
  195. fflush(stdout);
  196. running = False;
  197. break;
  198. case XK_Escape:
  199. ret = 1;
  200. running = False;
  201. break;
  202. case XK_BackSpace:
  203. if((i = len)) {
  204. prev_nitem = nitem;
  205. do {
  206. text[--i] = 0;
  207. match(text);
  208. } while(i && nitem && prev_nitem == nitem);
  209. match(text);
  210. }
  211. break;
  212. default:
  213. if(num && !iscntrl((int) buf[0])) {
  214. buf[num] = 0;
  215. if(len > 0)
  216. strncat(text, buf, sizeof(text));
  217. else
  218. strncpy(text, buf, sizeof(text));
  219. match(text);
  220. }
  221. }
  222. drawmenu();
  223. }
  224. static char *
  225. readstdin() {
  226. static char *maxname = NULL;
  227. char *p, buf[1024];
  228. unsigned int len = 0, max = 0;
  229. Item *i, *new;
  230. i = 0;
  231. while(fgets(buf, sizeof(buf), stdin)) {
  232. len = strlen(buf);
  233. if (buf[len - 1] == '\n')
  234. buf[len - 1] = 0;
  235. p = estrdup(buf);
  236. if(max < len) {
  237. maxname = p;
  238. max = len;
  239. }
  240. new = emalloc(sizeof(Item));
  241. new->next = new->left = new->right = NULL;
  242. new->text = p;
  243. if(!i)
  244. allitems = new;
  245. else
  246. i->next = new;
  247. i = new;
  248. }
  249. return maxname;
  250. }
  251. /* extern */
  252. int screen;
  253. Display *dpy;
  254. DC dc = {0};
  255. int
  256. main(int argc, char *argv[]) {
  257. char *maxname;
  258. fd_set rd;
  259. struct timeval timeout;
  260. Item *i;
  261. XEvent ev;
  262. XSetWindowAttributes wa;
  263. if(argc == 2 && !strncmp("-v", argv[1], 3)) {
  264. fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
  265. exit(EXIT_SUCCESS);
  266. }
  267. else if(argc != 1)
  268. eprint("usage: dmenu [-v]\n");
  269. dpy = XOpenDisplay(0);
  270. if(!dpy)
  271. eprint("dmenu: cannot open display\n");
  272. screen = DefaultScreen(dpy);
  273. root = RootWindow(dpy, screen);
  274. /* Note, the select() construction allows to grab all keypresses as
  275. * early as possible, to not loose them. But if there is no standard
  276. * input supplied, we will make sure to exit after MAX_WAIT_STDIN
  277. * seconds. This is convenience behavior for rapid typers.
  278. */
  279. while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
  280. GrabModeAsync, CurrentTime) != GrabSuccess)
  281. usleep(1000);
  282. timeout.tv_usec = 0;
  283. timeout.tv_sec = STDIN_TIMEOUT;
  284. FD_ZERO(&rd);
  285. FD_SET(STDIN_FILENO, &rd);
  286. if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
  287. goto UninitializedEnd;
  288. maxname = readstdin();
  289. /* style */
  290. dc.sel[ColBG] = getcolor(SELBGCOLOR);
  291. dc.sel[ColFG] = getcolor(SELFGCOLOR);
  292. dc.norm[ColBG] = getcolor(NORMBGCOLOR);
  293. dc.norm[ColFG] = getcolor(NORMFGCOLOR);
  294. setfont(FONT);
  295. wa.override_redirect = 1;
  296. wa.background_pixmap = ParentRelative;
  297. wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
  298. mx = my = 0;
  299. mw = DisplayWidth(dpy, screen);
  300. mh = dc.font.height + 2;
  301. win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
  302. DefaultDepth(dpy, screen), CopyFromParent,
  303. DefaultVisual(dpy, screen),
  304. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  305. XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
  306. /* pixmap */
  307. dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
  308. dc.gc = XCreateGC(dpy, root, 0, 0);
  309. XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
  310. if(maxname)
  311. cmdw = textw(maxname);
  312. if(cmdw > mw / 3)
  313. cmdw = mw / 3;
  314. text[0] = 0;
  315. match(text);
  316. XMapRaised(dpy, win);
  317. drawmenu();
  318. XSync(dpy, False);
  319. /* main event loop */
  320. while(running && !XNextEvent(dpy, &ev)) {
  321. switch (ev.type) {
  322. default: /* ignore all crap */
  323. break;
  324. case KeyPress:
  325. kpress(&ev.xkey);
  326. break;
  327. case Expose:
  328. if(ev.xexpose.count == 0)
  329. drawmenu();
  330. break;
  331. }
  332. }
  333. while(allitems) {
  334. i = allitems->next;
  335. free(allitems->text);
  336. free(allitems);
  337. allitems = i;
  338. }
  339. if(dc.font.set)
  340. XFreeFontSet(dpy, dc.font.set);
  341. else
  342. XFreeFont(dpy, dc.font.xfont);
  343. XFreePixmap(dpy, dc.drawable);
  344. XFreeGC(dpy, dc.gc);
  345. XDestroyWindow(dpy, win);
  346. UninitializedEnd:
  347. XUngrabKeyboard(dpy, CurrentTime);
  348. XCloseDisplay(dpy);
  349. return ret;
  350. }