menu.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. /*
  2. * calmwm - the calm window manager
  3. *
  4. * Copyright (c) 2008 Owain G. Ainsworth <oga@openbsd.org>
  5. * Copyright (c) 2004 Marius Aamodt Eriksen <marius@monkey.org>
  6. *
  7. * Permission to use, copy, modify, and distribute this software for any
  8. * purpose with or without fee is hereby granted, provided that the above
  9. * copyright notice and this permission notice appear in all copies.
  10. *
  11. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  12. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  13. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  14. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  15. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  16. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  17. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  18. *
  19. * $OpenBSD: menu.c,v 1.87 2015/07/12 14:21:09 okan Exp $
  20. */
  21. #include <sys/types.h>
  22. #include <sys/queue.h>
  23. #include <ctype.h>
  24. #include <err.h>
  25. #include <errno.h>
  26. #include <limits.h>
  27. #include <stdarg.h>
  28. #include <stdio.h>
  29. #include <stdlib.h>
  30. #include <string.h>
  31. #include <unistd.h>
  32. #include "calmwm.h"
  33. #define PROMPT_SCHAR "\xc2\xbb"
  34. #define PROMPT_ECHAR "\xc2\xab"
  35. enum ctltype {
  36. CTL_NONE = -1,
  37. CTL_ERASEONE = 0, CTL_WIPE, CTL_UP, CTL_DOWN, CTL_RETURN,
  38. CTL_TAB, CTL_ABORT, CTL_ALL
  39. };
  40. struct menu_ctx {
  41. struct screen_ctx *sc;
  42. char searchstr[MENU_MAXENTRY + 1];
  43. char dispstr[MENU_MAXENTRY*2 + 1];
  44. char promptstr[MENU_MAXENTRY + 1];
  45. int hasprompt;
  46. int list;
  47. int listing;
  48. int changed;
  49. int noresult;
  50. int prev;
  51. int entry;
  52. int num;
  53. int flags;
  54. struct geom geom;
  55. void (*match)(struct menu_q *, struct menu_q *, char *);
  56. void (*print)(struct menu *, int);
  57. };
  58. static struct menu *menu_handle_key(XEvent *, struct menu_ctx *,
  59. struct menu_q *, struct menu_q *);
  60. static void menu_handle_move(XEvent *, struct menu_ctx *,
  61. struct menu_q *);
  62. static struct menu *menu_handle_release(XEvent *, struct menu_ctx *,
  63. struct menu_q *);
  64. static void menu_draw(struct menu_ctx *, struct menu_q *,
  65. struct menu_q *);
  66. static void menu_draw_entry(struct menu_ctx *, struct menu_q *,
  67. int, int);
  68. static int menu_calc_entry(struct menu_ctx *, int, int);
  69. static struct menu *menu_complete_path(struct menu_ctx *);
  70. static int menu_keycode(XKeyEvent *, enum ctltype *, char *);
  71. struct menu *
  72. menu_filter(struct screen_ctx *sc, struct menu_q *menuq, const char *prompt,
  73. const char *initial, int flags,
  74. void (*match)(struct menu_q *, struct menu_q *, char *),
  75. void (*print)(struct menu *, int))
  76. {
  77. struct menu_ctx mc;
  78. struct menu_q resultq;
  79. struct menu *mi = NULL;
  80. XEvent e;
  81. Window focuswin;
  82. int evmask, focusrevert;
  83. int xsave, ysave, xcur, ycur;
  84. TAILQ_INIT(&resultq);
  85. (void)memset(&mc, 0, sizeof(mc));
  86. xu_ptr_getpos(sc->rootwin, &xsave, &ysave);
  87. mc.sc = sc;
  88. mc.flags = flags;
  89. mc.match = match;
  90. mc.print = print;
  91. mc.entry = mc.prev = -1;
  92. mc.geom.x = xsave;
  93. mc.geom.y = ysave;
  94. if (mc.flags & CWM_MENU_LIST)
  95. mc.list = 1;
  96. if (initial != NULL)
  97. (void)strlcpy(mc.searchstr, initial, sizeof(mc.searchstr));
  98. else
  99. mc.searchstr[0] = '\0';
  100. evmask = MENUMASK;
  101. if (prompt != NULL) {
  102. evmask |= KEYMASK; /* accept keys as well */
  103. (void)strlcpy(mc.promptstr, prompt, sizeof(mc.promptstr));
  104. mc.hasprompt = 1;
  105. }
  106. XSelectInput(X_Dpy, sc->menuwin, evmask);
  107. XMapRaised(X_Dpy, sc->menuwin);
  108. if (xu_ptr_grab(sc->menuwin, MENUGRABMASK,
  109. Conf.cursor[CF_QUESTION]) < 0) {
  110. XUnmapWindow(X_Dpy, sc->menuwin);
  111. return(NULL);
  112. }
  113. XGetInputFocus(X_Dpy, &focuswin, &focusrevert);
  114. XSetInputFocus(X_Dpy, sc->menuwin, RevertToPointerRoot, CurrentTime);
  115. /* make sure keybindings don't remove keys from the menu stream */
  116. XGrabKeyboard(X_Dpy, sc->menuwin, True,
  117. GrabModeAsync, GrabModeAsync, CurrentTime);
  118. for (;;) {
  119. mc.changed = 0;
  120. XWindowEvent(X_Dpy, sc->menuwin, evmask, &e);
  121. switch (e.type) {
  122. case KeyPress:
  123. if ((mi = menu_handle_key(&e, &mc, menuq, &resultq))
  124. != NULL)
  125. goto out;
  126. /* FALLTHROUGH */
  127. case Expose:
  128. menu_draw(&mc, menuq, &resultq);
  129. break;
  130. case MotionNotify:
  131. menu_handle_move(&e, &mc, &resultq);
  132. break;
  133. case ButtonRelease:
  134. if ((mi = menu_handle_release(&e, &mc, &resultq))
  135. != NULL)
  136. goto out;
  137. break;
  138. default:
  139. break;
  140. }
  141. }
  142. out:
  143. if ((mc.flags & CWM_MENU_DUMMY) == 0 && mi->dummy) {
  144. /* no mouse based match */
  145. free(mi);
  146. mi = NULL;
  147. }
  148. XSetInputFocus(X_Dpy, focuswin, focusrevert, CurrentTime);
  149. /* restore if user didn't move */
  150. xu_ptr_getpos(sc->rootwin, &xcur, &ycur);
  151. if (xcur == mc.geom.x && ycur == mc.geom.y)
  152. xu_ptr_setpos(sc->rootwin, xsave, ysave);
  153. xu_ptr_ungrab();
  154. XMoveResizeWindow(X_Dpy, sc->menuwin, 0, 0, 1, 1);
  155. XUnmapWindow(X_Dpy, sc->menuwin);
  156. XUngrabKeyboard(X_Dpy, CurrentTime);
  157. return(mi);
  158. }
  159. static struct menu *
  160. menu_complete_path(struct menu_ctx *mc)
  161. {
  162. struct screen_ctx *sc = mc->sc;
  163. struct menu *mi, *mr;
  164. struct menu_q menuq;
  165. mr = xcalloc(1, sizeof(*mr));
  166. TAILQ_INIT(&menuq);
  167. if ((mi = menu_filter(sc, &menuq, mc->searchstr, NULL,
  168. CWM_MENU_DUMMY, search_match_path_any, NULL)) != NULL) {
  169. mr->abort = mi->abort;
  170. mr->dummy = mi->dummy;
  171. if (mi->text[0] != '\0')
  172. snprintf(mr->text, sizeof(mr->text), "%s \"%s\"",
  173. mc->searchstr, mi->text);
  174. else if (!mr->abort)
  175. strlcpy(mr->text, mc->searchstr, sizeof(mr->text));
  176. }
  177. menuq_clear(&menuq);
  178. return(mr);
  179. }
  180. static struct menu *
  181. menu_handle_key(XEvent *e, struct menu_ctx *mc, struct menu_q *menuq,
  182. struct menu_q *resultq)
  183. {
  184. struct menu *mi;
  185. enum ctltype ctl;
  186. char chr[32];
  187. size_t len;
  188. int clen, i;
  189. wchar_t wc;
  190. if (menu_keycode(&e->xkey, &ctl, chr) < 0)
  191. return(NULL);
  192. switch (ctl) {
  193. case CTL_ERASEONE:
  194. if ((len = strlen(mc->searchstr)) > 0) {
  195. clen = 1;
  196. while (mbtowc(&wc, &mc->searchstr[len-clen], MB_CUR_MAX) == -1)
  197. clen++;
  198. for (i = 1; i <= clen; i++)
  199. mc->searchstr[len - i] = '\0';
  200. mc->changed = 1;
  201. }
  202. break;
  203. case CTL_UP:
  204. mi = TAILQ_LAST(resultq, menu_q);
  205. if (mi == NULL)
  206. break;
  207. TAILQ_REMOVE(resultq, mi, resultentry);
  208. TAILQ_INSERT_HEAD(resultq, mi, resultentry);
  209. break;
  210. case CTL_DOWN:
  211. mi = TAILQ_FIRST(resultq);
  212. if (mi == NULL)
  213. break;
  214. TAILQ_REMOVE(resultq, mi, resultentry);
  215. TAILQ_INSERT_TAIL(resultq, mi, resultentry);
  216. break;
  217. case CTL_RETURN:
  218. /*
  219. * Return whatever the cursor is currently on. Else
  220. * even if dummy is zero, we need to return something.
  221. */
  222. if ((mi = TAILQ_FIRST(resultq)) == NULL) {
  223. mi = xmalloc(sizeof(*mi));
  224. (void)strlcpy(mi->text,
  225. mc->searchstr, sizeof(mi->text));
  226. mi->dummy = 1;
  227. }
  228. mi->abort = 0;
  229. return(mi);
  230. case CTL_WIPE:
  231. mc->searchstr[0] = '\0';
  232. mc->changed = 1;
  233. break;
  234. case CTL_TAB:
  235. if ((mi = TAILQ_FIRST(resultq)) != NULL) {
  236. /*
  237. * - We are in exec_path menu mode
  238. * - It is equal to the input
  239. * We got a command, launch the file menu
  240. */
  241. if ((mc->flags & CWM_MENU_FILE) &&
  242. (strncmp(mc->searchstr, mi->text,
  243. strlen(mi->text))) == 0)
  244. return(menu_complete_path(mc));
  245. /*
  246. * Put common prefix of the results into searchstr
  247. */
  248. (void)strlcpy(mc->searchstr,
  249. mi->text, sizeof(mc->searchstr));
  250. while ((mi = TAILQ_NEXT(mi, resultentry)) != NULL) {
  251. i = 0;
  252. while (tolower(mc->searchstr[i]) ==
  253. tolower(mi->text[i]))
  254. i++;
  255. mc->searchstr[i] = '\0';
  256. }
  257. mc->changed = 1;
  258. }
  259. break;
  260. case CTL_ALL:
  261. mc->list = !mc->list;
  262. break;
  263. case CTL_ABORT:
  264. mi = xmalloc(sizeof(*mi));
  265. mi->text[0] = '\0';
  266. mi->dummy = 1;
  267. mi->abort = 1;
  268. return(mi);
  269. default:
  270. break;
  271. }
  272. if (chr[0] != '\0') {
  273. mc->changed = 1;
  274. (void)strlcat(mc->searchstr, chr, sizeof(mc->searchstr));
  275. }
  276. mc->noresult = 0;
  277. if (mc->changed && mc->searchstr[0] != '\0') {
  278. (*mc->match)(menuq, resultq, mc->searchstr);
  279. /* If menuq is empty, never show we've failed */
  280. mc->noresult = TAILQ_EMPTY(resultq) && !TAILQ_EMPTY(menuq);
  281. } else if (mc->changed)
  282. TAILQ_INIT(resultq);
  283. if (!mc->list && mc->listing && !mc->changed) {
  284. TAILQ_INIT(resultq);
  285. mc->listing = 0;
  286. }
  287. return(NULL);
  288. }
  289. static void
  290. menu_draw(struct menu_ctx *mc, struct menu_q *menuq, struct menu_q *resultq)
  291. {
  292. struct screen_ctx *sc = mc->sc;
  293. struct menu *mi;
  294. struct geom area;
  295. int n, xsave, ysave;
  296. if (mc->list) {
  297. if (TAILQ_EMPTY(resultq)) {
  298. /* Copy them all over. */
  299. TAILQ_FOREACH(mi, menuq, entry)
  300. TAILQ_INSERT_TAIL(resultq, mi, resultentry);
  301. mc->listing = 1;
  302. } else if (mc->changed)
  303. mc->listing = 0;
  304. }
  305. mc->num = 0;
  306. mc->geom.w = 0;
  307. mc->geom.h = 0;
  308. if (mc->hasprompt) {
  309. (void)snprintf(mc->dispstr, sizeof(mc->dispstr), "%s%s%s%s",
  310. mc->promptstr, PROMPT_SCHAR, mc->searchstr, PROMPT_ECHAR);
  311. mc->geom.w = xu_xft_width(sc->xftfont, mc->dispstr,
  312. strlen(mc->dispstr));
  313. mc->geom.h = sc->xftfont->height + 1;
  314. mc->num = 1;
  315. }
  316. TAILQ_FOREACH(mi, resultq, resultentry) {
  317. if (mc->print != NULL)
  318. (*mc->print)(mi, mc->listing);
  319. else
  320. (void)snprintf(mi->print, sizeof(mi->print),
  321. "%s", mi->text);
  322. mc->geom.w = MAX(mc->geom.w, xu_xft_width(sc->xftfont,
  323. mi->print, MIN(strlen(mi->print), MENU_MAXENTRY)));
  324. mc->geom.h += sc->xftfont->height + 1;
  325. mc->num++;
  326. }
  327. area = screen_area(sc, mc->geom.x, mc->geom.y, CWM_GAP);
  328. area.w += area.x - Conf.bwidth * 2;
  329. area.h += area.y - Conf.bwidth * 2;
  330. xsave = mc->geom.x;
  331. ysave = mc->geom.y;
  332. /* Never hide the top, or left side, of the menu. */
  333. if (mc->geom.x + mc->geom.w >= area.w)
  334. mc->geom.x = area.w - mc->geom.w;
  335. if (mc->geom.x < area.x) {
  336. mc->geom.x = area.x;
  337. mc->geom.w = MIN(mc->geom.w, (area.w - area.x));
  338. }
  339. if (mc->geom.y + mc->geom.h >= area.h)
  340. mc->geom.y = area.h - mc->geom.h;
  341. if (mc->geom.y < area.y) {
  342. mc->geom.y = area.y;
  343. mc->geom.h = MIN(mc->geom.h, (area.h - area.y));
  344. }
  345. if (mc->geom.x != xsave || mc->geom.y != ysave)
  346. xu_ptr_setpos(sc->rootwin, mc->geom.x, mc->geom.y);
  347. XClearWindow(X_Dpy, sc->menuwin);
  348. XMoveResizeWindow(X_Dpy, sc->menuwin, mc->geom.x, mc->geom.y,
  349. mc->geom.w, mc->geom.h);
  350. if (mc->hasprompt) {
  351. xu_xft_draw(sc, mc->dispstr, CWM_COLOR_MENU_FONT,
  352. 0, sc->xftfont->ascent);
  353. n = 1;
  354. } else
  355. n = 0;
  356. TAILQ_FOREACH(mi, resultq, resultentry) {
  357. int y = n * (sc->xftfont->height + 1) + sc->xftfont->ascent + 1;
  358. /* Stop drawing when menu doesn't fit inside the screen. */
  359. if (mc->geom.y + y > area.h)
  360. break;
  361. xu_xft_draw(sc, mi->print, CWM_COLOR_MENU_FONT, 0, y);
  362. n++;
  363. }
  364. if (mc->hasprompt && n > 1)
  365. menu_draw_entry(mc, resultq, 1, 1);
  366. }
  367. static void
  368. menu_draw_entry(struct menu_ctx *mc, struct menu_q *resultq,
  369. int entry, int active)
  370. {
  371. struct screen_ctx *sc = mc->sc;
  372. struct menu *mi;
  373. int color, i = 0;
  374. if (mc->hasprompt)
  375. i = 1;
  376. TAILQ_FOREACH(mi, resultq, resultentry)
  377. if (entry == i++)
  378. break;
  379. if (mi == NULL)
  380. return;
  381. color = (active) ? CWM_COLOR_MENU_FG : CWM_COLOR_MENU_BG;
  382. XftDrawRect(sc->xftdraw, &sc->xftcolor[color], 0,
  383. (sc->xftfont->height + 1) * entry, mc->geom.w,
  384. (sc->xftfont->height + 1) + sc->xftfont->descent);
  385. color = (active) ? CWM_COLOR_MENU_FONT_SEL : CWM_COLOR_MENU_FONT;
  386. xu_xft_draw(sc, mi->print, color,
  387. 0, (sc->xftfont->height + 1) * entry + sc->xftfont->ascent + 1);
  388. }
  389. static void
  390. menu_handle_move(XEvent *e, struct menu_ctx *mc, struct menu_q *resultq)
  391. {
  392. mc->prev = mc->entry;
  393. mc->entry = menu_calc_entry(mc, e->xbutton.x, e->xbutton.y);
  394. if (mc->prev == mc->entry)
  395. return;
  396. if (mc->prev != -1)
  397. menu_draw_entry(mc, resultq, mc->prev, 0);
  398. if (mc->entry != -1) {
  399. (void)xu_ptr_regrab(MENUGRABMASK, Conf.cursor[CF_NORMAL]);
  400. menu_draw_entry(mc, resultq, mc->entry, 1);
  401. } else
  402. (void)xu_ptr_regrab(MENUGRABMASK, Conf.cursor[CF_DEFAULT]);
  403. }
  404. static struct menu *
  405. menu_handle_release(XEvent *e, struct menu_ctx *mc, struct menu_q *resultq)
  406. {
  407. struct menu *mi;
  408. int entry, i = 0;
  409. entry = menu_calc_entry(mc, e->xbutton.x, e->xbutton.y);
  410. if (mc->hasprompt)
  411. i = 1;
  412. TAILQ_FOREACH(mi, resultq, resultentry)
  413. if (entry == i++)
  414. break;
  415. if (mi == NULL) {
  416. mi = xmalloc(sizeof(*mi));
  417. mi->text[0] = '\0';
  418. mi->dummy = 1;
  419. }
  420. return(mi);
  421. }
  422. static int
  423. menu_calc_entry(struct menu_ctx *mc, int x, int y)
  424. {
  425. struct screen_ctx *sc = mc->sc;
  426. int entry;
  427. entry = y / (sc->xftfont->height + 1);
  428. /* in bounds? */
  429. if (x < 0 || x > mc->geom.w || y < 0 ||
  430. y > (sc->xftfont->height + 1) * mc->num ||
  431. entry < 0 || entry >= mc->num)
  432. entry = -1;
  433. if (mc->hasprompt && entry == 0)
  434. entry = -1;
  435. return(entry);
  436. }
  437. static int
  438. menu_keycode(XKeyEvent *ev, enum ctltype *ctl, char *chr)
  439. {
  440. KeySym ks;
  441. unsigned int state = ev->state;
  442. *ctl = CTL_NONE;
  443. chr[0] = '\0';
  444. ks = XkbKeycodeToKeysym(X_Dpy, ev->keycode, 0,
  445. (state & ShiftMask) ? 1 : 0);
  446. /* Look for control characters. */
  447. switch (ks) {
  448. case XK_BackSpace:
  449. *ctl = CTL_ERASEONE;
  450. break;
  451. case XK_Return:
  452. *ctl = CTL_RETURN;
  453. break;
  454. case XK_Tab:
  455. *ctl = CTL_TAB;
  456. break;
  457. case XK_Up:
  458. *ctl = CTL_UP;
  459. break;
  460. case XK_Down:
  461. *ctl = CTL_DOWN;
  462. break;
  463. case XK_Escape:
  464. *ctl = CTL_ABORT;
  465. break;
  466. }
  467. if (*ctl == CTL_NONE && (state & ControlMask)) {
  468. switch (ks) {
  469. case XK_s:
  470. case XK_S:
  471. /* Emacs "next" */
  472. *ctl = CTL_DOWN;
  473. break;
  474. case XK_r:
  475. case XK_R:
  476. /* Emacs "previous" */
  477. *ctl = CTL_UP;
  478. break;
  479. case XK_u:
  480. case XK_U:
  481. *ctl = CTL_WIPE;
  482. break;
  483. case XK_h:
  484. case XK_H:
  485. *ctl = CTL_ERASEONE;
  486. break;
  487. case XK_a:
  488. case XK_A:
  489. *ctl = CTL_ALL;
  490. break;
  491. }
  492. }
  493. if (*ctl == CTL_NONE && (state & Mod1Mask)) {
  494. switch (ks) {
  495. case XK_j:
  496. case XK_J:
  497. /* Vi "down" */
  498. *ctl = CTL_DOWN;
  499. break;
  500. case XK_k:
  501. case XK_K:
  502. /* Vi "up" */
  503. *ctl = CTL_UP;
  504. break;
  505. }
  506. }
  507. if (*ctl != CTL_NONE)
  508. return(0);
  509. if (XLookupString(ev, chr, 32, &ks, NULL) < 0)
  510. return(-1);
  511. return(0);
  512. }
  513. void
  514. menuq_add(struct menu_q *mq, void *ctx, const char *fmt, ...)
  515. {
  516. va_list ap;
  517. struct menu *mi;
  518. mi = xcalloc(1, sizeof(*mi));
  519. mi->ctx = ctx;
  520. va_start(ap, fmt);
  521. (void)vsnprintf(mi->text, sizeof(mi->text), fmt, ap);
  522. va_end(ap);
  523. TAILQ_INSERT_TAIL(mq, mi, entry);
  524. }
  525. void
  526. menuq_clear(struct menu_q *mq)
  527. {
  528. struct menu *mi;
  529. while ((mi = TAILQ_FIRST(mq)) != NULL) {
  530. TAILQ_REMOVE(mq, mi, entry);
  531. free(mi);
  532. }
  533. }