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.

498 lines
10 KiB

19 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 <ctype.h>
  7. #include <stdlib.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <sys/stat.h>
  11. #include <sys/wait.h>
  12. #include <time.h>
  13. #include <unistd.h>
  14. #include <X11/Xlib.h>
  15. #include <X11/cursorfont.h>
  16. #include <X11/Xutil.h>
  17. #include <X11/keysym.h>
  18. #include <blitz.h>
  19. #include <cext.h>
  20. typedef struct Item Item;
  21. struct Item {
  22. Item *next; /* traverses all items */
  23. Item *left, *right; /* traverses items matching current search pattern */
  24. char *text;
  25. };
  26. static char *title = nil;
  27. static Bool done = False;
  28. static int ret = 0;
  29. static char text[4096];
  30. static BlitzColor selcolor;
  31. static BlitzColor normcolor;
  32. static Window win;
  33. static XRectangle mrect;
  34. static Item *allitem = nil; /* first of all items */
  35. static Item *item = nil; /* first of pattern matching items */
  36. static Item *sel = nil;
  37. static Item *nextoff = nil;
  38. static Item *prevoff = nil;
  39. static Item *curroff = nil;
  40. static int nitem = 0;
  41. static unsigned int cmdw = 0;
  42. static unsigned int twidth = 0;
  43. static unsigned int cwidth = 0;
  44. static Blitz blz = {0};
  45. static BlitzBrush brush = {0};
  46. static const int seek = 30; /* 30px */
  47. static void draw_menu(void);
  48. static void handle_kpress(XKeyEvent * e);
  49. static char version[] = "wmiimenu - " VERSION ", (C)opyright MMIV-MMVI Anselm R. Garbe\n";
  50. static void
  51. usage()
  52. {
  53. fprintf(stderr, "%s", "usage: wmiimenu [-v] [-t <title>]\n");
  54. exit(1);
  55. }
  56. static void
  57. update_offsets()
  58. {
  59. unsigned int tw, w = cmdw + 2 * seek;
  60. if(!curroff)
  61. return;
  62. for(nextoff = curroff; nextoff; nextoff=nextoff->right) {
  63. tw = blitz_textwidth(brush.font, nextoff->text);
  64. if(tw > mrect.width / 3)
  65. tw = mrect.width / 3;
  66. w += tw + mrect.height;
  67. if(w > mrect.width)
  68. break;
  69. }
  70. w = cmdw + 2 * seek;
  71. for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) {
  72. tw = blitz_textwidth(brush.font, prevoff->left->text);
  73. if(tw > mrect.width / 3)
  74. tw = mrect.width / 3;
  75. w += tw + mrect.height;
  76. if(w > mrect.width)
  77. break;
  78. }
  79. }
  80. static void
  81. update_items(char *pattern)
  82. {
  83. unsigned int plen = strlen(pattern);
  84. Item *i, *j;
  85. if(!pattern)
  86. return;
  87. if(!title || *pattern)
  88. cmdw = cwidth;
  89. else
  90. cmdw = twidth;
  91. item = j = nil;
  92. nitem = 0;
  93. for(i = allitem; i; i=i->next)
  94. if(!plen || !strncmp(pattern, i->text, plen)) {
  95. if(!j)
  96. item = i;
  97. else
  98. j->right = i;
  99. i->left = j;
  100. i->right = nil;
  101. j = i;
  102. nitem++;
  103. }
  104. for(i = allitem; i; i=i->next)
  105. if(plen && strncmp(pattern, i->text, plen)
  106. && strstr(i->text, pattern)) {
  107. if(!j)
  108. item = i;
  109. else
  110. j->right = i;
  111. i->left = j;
  112. i->right = nil;
  113. j = i;
  114. nitem++;
  115. }
  116. curroff = prevoff = nextoff = sel = item;
  117. update_offsets();
  118. }
  119. /* creates brush structs for brush mode drawing */
  120. static void
  121. draw_menu()
  122. {
  123. unsigned int offx = 0;
  124. Item *i;
  125. brush.align = WEST;
  126. brush.rect = mrect;
  127. brush.rect.x = 0;
  128. brush.rect.y = 0;
  129. brush.color = normcolor;
  130. brush.border = False;
  131. blitz_draw_tile(&brush);
  132. /* print command */
  133. if(!title || text[0]) {
  134. brush.color = normcolor;
  135. cmdw = cwidth;
  136. if(cmdw && item)
  137. brush.rect.width = cmdw;
  138. blitz_draw_label(&brush, text);
  139. }
  140. else {
  141. cmdw = twidth;
  142. brush.color = selcolor;
  143. brush.rect.width = cmdw;
  144. blitz_draw_label(&brush, title);
  145. }
  146. offx += brush.rect.width;
  147. brush.align = CENTER;
  148. if(curroff) {
  149. brush.color = normcolor;
  150. brush.rect.x = offx;
  151. brush.rect.width = seek;
  152. offx += brush.rect.width;
  153. blitz_draw_label(&brush, (curroff && curroff->left) ? "<" : nil);
  154. /* determine maximum items */
  155. for(i = curroff; i != nextoff; i=i->right) {
  156. brush.color = normcolor;
  157. brush.border = False;
  158. brush.rect.x = offx;
  159. brush.rect.width = blitz_textwidth(brush.font, i->text);
  160. if(brush.rect.width > mrect.width / 3)
  161. brush.rect.width = mrect.width / 3;
  162. brush.rect.width += mrect.height;
  163. if(sel == i) {
  164. brush.color = selcolor;
  165. brush.border = True;
  166. }
  167. blitz_draw_label(&brush, i->text);
  168. offx += brush.rect.width;
  169. }
  170. brush.color = normcolor;
  171. brush.border = False;
  172. brush.rect.x = mrect.width - seek;
  173. brush.rect.width = seek;
  174. blitz_draw_label(&brush, nextoff ? ">" : nil);
  175. }
  176. XCopyArea(blz.dpy, brush.drawable, win, brush.gc, 0, 0, mrect.width,
  177. mrect.height, 0, 0);
  178. XSync(blz.dpy, False);
  179. }
  180. static void
  181. handle_kpress(XKeyEvent * e)
  182. {
  183. KeySym ksym;
  184. char buf[32];
  185. int num, prev_nitem;
  186. unsigned int i, len = strlen(text);
  187. buf[0] = 0;
  188. num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
  189. if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
  190. || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
  191. || IsPrivateKeypadKey(ksym))
  192. return;
  193. /* first check if a control mask is omitted */
  194. if(e->state & ControlMask) {
  195. switch (ksym) {
  196. case XK_H:
  197. case XK_h:
  198. ksym = XK_BackSpace;
  199. break;
  200. case XK_I:
  201. case XK_i:
  202. ksym = XK_Tab;
  203. break;
  204. case XK_J:
  205. case XK_j:
  206. ksym = XK_Return;
  207. break;
  208. case XK_N:
  209. case XK_n:
  210. ksym = XK_Right;
  211. break;
  212. case XK_P:
  213. case XK_p:
  214. ksym = XK_Left;
  215. break;
  216. case XK_U:
  217. case XK_u:
  218. text[0] = 0;
  219. update_items(text);
  220. draw_menu();
  221. return;
  222. break;
  223. case XK_bracketleft:
  224. ksym = XK_Escape;
  225. break;
  226. default: /* ignore other control sequences */
  227. return;
  228. break;
  229. }
  230. }
  231. switch (ksym) {
  232. case XK_Left:
  233. if(!(sel && sel->left))
  234. return;
  235. sel=sel->left;
  236. if(sel->right == curroff) {
  237. curroff = prevoff;
  238. update_offsets();
  239. }
  240. break;
  241. case XK_Tab:
  242. if(!sel)
  243. return;
  244. cext_strlcpy(text, sel->text, sizeof(text));
  245. update_items(text);
  246. break;
  247. case XK_Right:
  248. if(!(sel && sel->right))
  249. return;
  250. sel=sel->right;
  251. if(sel == nextoff) {
  252. curroff = nextoff;
  253. update_offsets();
  254. }
  255. break;
  256. case XK_Return:
  257. if(e->state & ShiftMask) {
  258. if(text)
  259. fprintf(stdout, "%s", text);
  260. }
  261. else if(sel)
  262. fprintf(stdout, "%s", sel->text);
  263. else if(text)
  264. fprintf(stdout, "%s", text);
  265. fflush(stdout);
  266. done = True;
  267. break;
  268. case XK_Escape:
  269. ret = 1;
  270. done = True;
  271. break;
  272. case XK_BackSpace:
  273. if((i = len)) {
  274. prev_nitem = nitem;
  275. do {
  276. text[--i] = 0;
  277. update_items(text);
  278. } while(i && nitem && prev_nitem == nitem);
  279. update_items(text);
  280. }
  281. break;
  282. default:
  283. if((num == 1) && !iscntrl((int) buf[0])) {
  284. buf[num] = 0;
  285. if(len > 0)
  286. cext_strlcat(text, buf, sizeof(text));
  287. else
  288. cext_strlcpy(text, buf, sizeof(text));
  289. update_items(text);
  290. }
  291. }
  292. draw_menu();
  293. }
  294. static char *
  295. read_allitems()
  296. {
  297. static char *maxname = nil;
  298. char *p, buf[1024];
  299. unsigned int len = 0, max = 0;
  300. Item *i, *new;
  301. i = nil;
  302. while(fgets(buf, sizeof(buf), stdin)) {
  303. len = strlen(buf);
  304. if (buf[len - 1] == '\n')
  305. buf[len - 1] = 0;
  306. p = cext_estrdup(buf);
  307. if(max < len) {
  308. maxname = p;
  309. max = len;
  310. }
  311. new = cext_emalloc(sizeof(Item));
  312. new->next = new->left = new->right = nil;
  313. new->text = p;
  314. if(!i)
  315. allitem = new;
  316. else
  317. i->next = new;
  318. i = new;
  319. }
  320. return maxname;
  321. }
  322. int
  323. main(int argc, char *argv[])
  324. {
  325. int i;
  326. XSetWindowAttributes wa;
  327. char *maxname, *p;
  328. BlitzFont font = {0};
  329. GC gc;
  330. Drawable pmap;
  331. XEvent ev;
  332. /* command line args */
  333. for(i = 1; i < argc; i++) {
  334. if (argv[i][0] == '-')
  335. switch (argv[i][1]) {
  336. case 'v':
  337. fprintf(stdout, "%s", version);
  338. exit(0);
  339. break;
  340. case 't':
  341. if(++i < argc)
  342. title = argv[i];
  343. else
  344. usage();
  345. break;
  346. default:
  347. usage();
  348. break;
  349. }
  350. else
  351. usage();
  352. }
  353. blz.dpy = XOpenDisplay(0);
  354. if(!blz.dpy) {
  355. fprintf(stderr, "%s", "wmiimenu: cannot open dpy\n");
  356. exit(1);
  357. }
  358. blz.screen = DefaultScreen(blz.dpy);
  359. blz.root = RootWindow(blz.dpy, blz.screen);
  360. maxname = read_allitems();
  361. /* grab as early as possible, but after reading all items!!! */
  362. while(XGrabKeyboard
  363. (blz.dpy, blz.root, True, GrabModeAsync,
  364. GrabModeAsync, CurrentTime) != GrabSuccess)
  365. usleep(1000);
  366. font.fontstr = getenv("WMII_FONT");
  367. if (!font.fontstr)
  368. font.fontstr = cext_estrdup(BLITZ_FONT);
  369. blitz_loadfont(&blz, &font);
  370. if((p = getenv("WMII_NORMCOLORS")))
  371. cext_strlcpy(normcolor.colstr, p, sizeof(normcolor.colstr));
  372. if(strlen(normcolor.colstr) != 23)
  373. cext_strlcpy(normcolor.colstr, BLITZ_NORMCOLORS, sizeof(normcolor.colstr));
  374. blitz_loadcolor(&blz, &normcolor);
  375. if((p = getenv("WMII_SELCOLORS")))
  376. cext_strlcpy(selcolor.colstr, p, sizeof(selcolor.colstr));
  377. if(strlen(selcolor.colstr) != 23)
  378. cext_strlcpy(selcolor.colstr, BLITZ_SELCOLORS, sizeof(selcolor.colstr));
  379. blitz_loadcolor(&blz, &selcolor);
  380. wa.override_redirect = 1;
  381. wa.background_pixmap = ParentRelative;
  382. wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask
  383. | SubstructureRedirectMask | SubstructureNotifyMask;
  384. mrect.width = DisplayWidth(blz.dpy, blz.screen);
  385. mrect.height = font.ascent + font.descent + 4;
  386. mrect.y = DisplayHeight(blz.dpy, blz.screen) - mrect.height;
  387. mrect.x = 0;
  388. win = XCreateWindow(blz.dpy, blz.root, mrect.x, mrect.y,
  389. mrect.width, mrect.height, 0, DefaultDepth(blz.dpy, blz.screen),
  390. CopyFromParent, DefaultVisual(blz.dpy, blz.screen),
  391. CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
  392. XDefineCursor(blz.dpy, win, XCreateFontCursor(blz.dpy, XC_xterm));
  393. XSync(blz.dpy, False);
  394. /* pixmap */
  395. gc = XCreateGC(blz.dpy, win, 0, 0);
  396. pmap = XCreatePixmap(blz.dpy, win, mrect.width, mrect.height,
  397. DefaultDepth(blz.dpy, blz.screen));
  398. XSync(blz.dpy, False);
  399. brush.blitz = &blz;
  400. brush.color = normcolor;
  401. brush.drawable = pmap;
  402. brush.gc = gc;
  403. brush.font = &font;
  404. if(maxname)
  405. cwidth = blitz_textwidth(brush.font, maxname) + mrect.height;
  406. if(cwidth > mrect.width / 3)
  407. cwidth = mrect.width / 3;
  408. if(title) {
  409. twidth = blitz_textwidth(brush.font, title) + mrect.height;
  410. if(twidth > mrect.width / 3)
  411. twidth = mrect.width / 3;
  412. }
  413. cmdw = title ? twidth : cwidth;
  414. text[0] = 0;
  415. update_items(text);
  416. XMapRaised(blz.dpy, win);
  417. draw_menu();
  418. XSync(blz.dpy, False);
  419. /* main event loop */
  420. while(!XNextEvent(blz.dpy, &ev)) {
  421. switch (ev.type) {
  422. case KeyPress:
  423. handle_kpress(&ev.xkey);
  424. break;
  425. case Expose:
  426. if(ev.xexpose.count == 0) {
  427. draw_menu();
  428. }
  429. break;
  430. default:
  431. break;
  432. }
  433. if(done)
  434. break;
  435. }
  436. XUngrabKeyboard(blz.dpy, CurrentTime);
  437. XFreePixmap(blz.dpy, pmap);
  438. XFreeGC(blz.dpy, gc);
  439. XDestroyWindow(blz.dpy, win);
  440. XCloseDisplay(blz.dpy);
  441. return ret;
  442. }