dmenu.c 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. /* See LICENSE file for copyright and license details. */
  2. #include <ctype.h>
  3. #include <locale.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <strings.h>
  8. #include <time.h>
  9. #include <unistd.h>
  10. #include <X11/Xlib.h>
  11. #include <X11/Xatom.h>
  12. #include <X11/Xutil.h>
  13. #ifdef XINERAMA
  14. #include <X11/extensions/Xinerama.h>
  15. #endif
  16. #include <X11/Xft/Xft.h>
  17. /* Patch incompatibility overrides */
  18. #include "drw.h"
  19. #include "util.h"
  20. /* macros */
  21. #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
  22. * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
  23. #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
  24. #define OPAQUE 0xffU
  25. #define OPACITY "_NET_WM_WINDOW_OPACITY"
  26. /* enums */
  27. enum {
  28. SchemeNorm,
  29. SchemeSel,
  30. SchemeOut,
  31. SchemeBorder,
  32. SchemeMid,
  33. SchemeNormHighlight,
  34. SchemeSelHighlight,
  35. SchemeLast,
  36. }; /* color schemes */
  37. struct item {
  38. char *text;
  39. struct item *left, *right;
  40. int out;
  41. double distance;
  42. };
  43. static char text[BUFSIZ] = "";
  44. static char *embed;
  45. static int bh, mw, mh;
  46. static int inputw = 0, promptw;
  47. static int lrpad; /* sum of left and right padding */
  48. static size_t cursor;
  49. static struct item *items = NULL;
  50. static struct item *matches, *matchend;
  51. static struct item *prev, *curr, *next, *sel;
  52. static int mon = -1, screen;
  53. static Atom clip, utf8;
  54. static Display *dpy;
  55. static Window root, parentwin, win;
  56. static XIC xic;
  57. static int useargb = 0;
  58. static Visual *visual;
  59. static int depth;
  60. static Colormap cmap;
  61. static Drw *drw;
  62. static Clr *scheme[SchemeLast];
  63. #include "patch/include.h"
  64. #include "config.h"
  65. static char * cistrstr(const char *s, const char *sub);
  66. static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
  67. static char *(*fstrstr)(const char *, const char *) = cistrstr;
  68. static unsigned int
  69. textw_clamp(const char *str, unsigned int n)
  70. {
  71. unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
  72. return MIN(w, n);
  73. }
  74. static void appenditem(struct item *item, struct item **list, struct item **last);
  75. static void calcoffsets(void);
  76. static void cleanup(void);
  77. static char * cistrstr(const char *s, const char *sub);
  78. static int drawitem(struct item *item, int x, int y, int w);
  79. static void drawmenu(void);
  80. static void grabfocus(void);
  81. static void grabkeyboard(void);
  82. static void match(void);
  83. static void insert(const char *str, ssize_t n);
  84. static size_t nextrune(int inc);
  85. static void movewordedge(int dir);
  86. static void keypress(XKeyEvent *ev);
  87. static void paste(void);
  88. static void xinitvisual(void);
  89. static void readstdin(void);
  90. static void run(void);
  91. static void setup(void);
  92. static void usage(void);
  93. #include "patch/include.c"
  94. static void
  95. appenditem(struct item *item, struct item **list, struct item **last)
  96. {
  97. if (*last)
  98. (*last)->right = item;
  99. else
  100. *list = item;
  101. item->left = *last;
  102. item->right = NULL;
  103. *last = item;
  104. }
  105. static void
  106. calcoffsets(void)
  107. {
  108. int i, n, rpad = 0;
  109. if (lines > 0) {
  110. n = lines * bh;
  111. } else {
  112. n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">") + rpad);
  113. }
  114. /* calculate which items will begin the next page and previous page */
  115. for (i = 0, next = curr; next; next = next->right)
  116. if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n)
  117. break;
  118. for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
  119. if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n)
  120. break;
  121. }
  122. static void
  123. cleanup(void)
  124. {
  125. size_t i;
  126. XUngrabKey(dpy, AnyKey, AnyModifier, root);
  127. for (i = 0; i < SchemeLast; i++)
  128. free(scheme[i]);
  129. for (i = 0; items && items[i].text; ++i)
  130. free(items[i].text);
  131. free(items);
  132. drw_free(drw);
  133. XSync(dpy, False);
  134. XCloseDisplay(dpy);
  135. }
  136. static char *
  137. cistrstr(const char *s, const char *sub)
  138. {
  139. size_t len;
  140. for (len = strlen(sub); *s; s++)
  141. if (!strncasecmp(s, sub, len))
  142. return (char *)s;
  143. return NULL;
  144. }
  145. static int
  146. drawitem(struct item *item, int x, int y, int w)
  147. {
  148. int r;
  149. char *text = item->text;
  150. if (item == sel)
  151. drw_setscheme(drw, scheme[SchemeSel]);
  152. else if (item->left == sel || item->right == sel)
  153. drw_setscheme(drw, scheme[SchemeMid]);
  154. else if (item->out)
  155. drw_setscheme(drw, scheme[SchemeOut]);
  156. else
  157. drw_setscheme(drw, scheme[SchemeNorm]);
  158. r = drw_text(drw
  159. , x
  160. , y
  161. , w
  162. , bh
  163. , lrpad / 2
  164. , text
  165. , 0
  166. );
  167. drawhighlights(item, x, y, w);
  168. return r;
  169. }
  170. static void
  171. drawmenu(void)
  172. {
  173. unsigned int curpos;
  174. struct item *item;
  175. int x = 0, y = 0, w, rpad = 0, itw = 0, stw = 0;
  176. int fh = drw->fonts->h;
  177. drw_setscheme(drw, scheme[SchemeNorm]);
  178. drw_rect(drw, 0, 0, mw, mh, 1, 1);
  179. if (prompt && *prompt) {
  180. drw_setscheme(drw, scheme[SchemeSel]);
  181. x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0
  182. );
  183. }
  184. /* draw input field */
  185. w = (lines > 0 || !matches) ? mw - x : inputw;
  186. drw_setscheme(drw, scheme[SchemeNorm]);
  187. drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0
  188. );
  189. curpos = TEXTW(text) - TEXTW(&text[cursor]);
  190. if ((curpos += lrpad / 2 - 1) < w) {
  191. drw_setscheme(drw, scheme[SchemeNorm]);
  192. drw_rect(drw, x + curpos, 2 + (bh-fh)/2, caret_width, fh - 4, 1, 0);
  193. }
  194. if (lines > 0) {
  195. /* draw vertical list */
  196. for (item = curr; item != next; item = item->right)
  197. drawitem(item, x, y += bh, mw - x);
  198. } else if (matches) {
  199. /* draw horizontal list */
  200. x += inputw;
  201. w = TEXTW("<");
  202. if (curr->left) {
  203. drw_setscheme(drw, scheme[SchemeNorm]);
  204. drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0
  205. );
  206. }
  207. x += w;
  208. for (item = curr; item != next; item = item->right) {
  209. stw = TEXTW(">");
  210. itw = textw_clamp(item->text, mw - x - stw - rpad);
  211. x = drawitem(item, x, 0, itw);
  212. }
  213. if (next) {
  214. w = TEXTW(">");
  215. drw_setscheme(drw, scheme[SchemeNorm]);
  216. drw_text(drw, mw - w - rpad, 0, w, bh, lrpad / 2
  217. , ">"
  218. , 0
  219. );
  220. }
  221. }
  222. drw_map(drw, win, 0, 0, mw, mh);
  223. }
  224. static void
  225. grabfocus(void)
  226. {
  227. struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
  228. Window focuswin;
  229. int i, revertwin;
  230. for (i = 0; i < 100; ++i) {
  231. XGetInputFocus(dpy, &focuswin, &revertwin);
  232. if (focuswin == win)
  233. return;
  234. XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
  235. nanosleep(&ts, NULL);
  236. }
  237. die("cannot grab focus");
  238. }
  239. static void
  240. grabkeyboard(void)
  241. {
  242. struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
  243. int i;
  244. if (embed)
  245. return;
  246. /* try to grab keyboard, we may have to wait for another process to ungrab */
  247. for (i = 0; i < 1000; i++) {
  248. if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
  249. GrabModeAsync, CurrentTime) == GrabSuccess)
  250. return;
  251. nanosleep(&ts, NULL);
  252. }
  253. die("cannot grab keyboard");
  254. }
  255. static void
  256. match(void)
  257. {
  258. if (fuzzy) {
  259. fuzzymatch();
  260. return;
  261. }
  262. static char **tokv = NULL;
  263. static int tokn = 0;
  264. char buf[sizeof text], *s;
  265. int i, tokc = 0;
  266. size_t len, textsize;
  267. struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
  268. strcpy(buf, text);
  269. /* separate input text into tokens to be matched individually */
  270. for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
  271. if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
  272. die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
  273. len = tokc ? strlen(tokv[0]) : 0;
  274. if (use_prefix) {
  275. matches = lprefix = matchend = prefixend = NULL;
  276. textsize = strlen(text);
  277. } else {
  278. matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
  279. textsize = strlen(text) + 1;
  280. }
  281. for (item = items; item && item->text; item++)
  282. {
  283. for (i = 0; i < tokc; i++)
  284. if (!fstrstr(item->text, tokv[i]))
  285. break;
  286. if (i != tokc) /* not all tokens match */
  287. continue;
  288. /* exact matches go first, then prefixes, then substrings */
  289. if (!tokc || !fstrncmp(text, item->text, textsize))
  290. appenditem(item, &matches, &matchend);
  291. else if (!fstrncmp(tokv[0], item->text, len))
  292. appenditem(item, &lprefix, &prefixend);
  293. else if (!use_prefix)
  294. appenditem(item, &lsubstr, &substrend);
  295. }
  296. if (lprefix) {
  297. if (matches) {
  298. matchend->right = lprefix;
  299. lprefix->left = matchend;
  300. } else
  301. matches = lprefix;
  302. matchend = prefixend;
  303. }
  304. if (!use_prefix && lsubstr)
  305. {
  306. if (matches) {
  307. matchend->right = lsubstr;
  308. lsubstr->left = matchend;
  309. } else
  310. matches = lsubstr;
  311. matchend = substrend;
  312. }
  313. curr = sel = matches;
  314. calcoffsets();
  315. }
  316. static void
  317. insert(const char *str, ssize_t n)
  318. {
  319. if (strlen(text) + n > sizeof text - 1)
  320. return;
  321. /* move existing text out of the way, insert new text, and update cursor */
  322. memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
  323. if (n > 0)
  324. memcpy(&text[cursor], str, n);
  325. cursor += n;
  326. match();
  327. }
  328. static size_t
  329. nextrune(int inc)
  330. {
  331. ssize_t n;
  332. /* return location of next utf8 rune in the given direction (+1 or -1) */
  333. for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
  334. ;
  335. return n;
  336. }
  337. static void
  338. movewordedge(int dir)
  339. {
  340. if (dir < 0) { /* move cursor to the start of the word*/
  341. while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
  342. cursor = nextrune(-1);
  343. while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
  344. cursor = nextrune(-1);
  345. } else { /* move cursor to the end of the word */
  346. while (text[cursor] && strchr(worddelimiters, text[cursor]))
  347. cursor = nextrune(+1);
  348. while (text[cursor] && !strchr(worddelimiters, text[cursor]))
  349. cursor = nextrune(+1);
  350. }
  351. }
  352. static void
  353. keypress(XKeyEvent *ev)
  354. {
  355. char buf[64];
  356. int len;
  357. struct item * item;
  358. KeySym ksym = NoSymbol;
  359. Status status;
  360. len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
  361. switch (status) {
  362. default: /* XLookupNone, XBufferOverflow */
  363. return;
  364. case XLookupChars: /* composed string from input method */
  365. goto insert;
  366. case XLookupKeySym:
  367. case XLookupBoth: /* a KeySym and a string are returned: use keysym */
  368. break;
  369. }
  370. if (ev->state & ControlMask) {
  371. switch(ksym) {
  372. case XK_a: expect("ctrl-a", ev); ksym = XK_Home; break;
  373. case XK_b: expect("ctrl-b", ev); ksym = XK_Left; break;
  374. case XK_c: expect("ctrl-c", ev); ksym = XK_Escape; break;
  375. case XK_d: expect("ctrl-d", ev); ksym = XK_Delete; break;
  376. case XK_e: expect("ctrl-e", ev); ksym = XK_End; break;
  377. case XK_f: expect("ctrl-f", ev); ksym = XK_Right; break;
  378. case XK_g: expect("ctrl-g", ev); ksym = XK_Escape; break;
  379. case XK_h: expect("ctrl-h", ev); ksym = XK_BackSpace; break;
  380. case XK_i: expect("ctrl-i", ev); ksym = XK_Tab; break;
  381. case XK_j: expect("ctrl-j", ev); ksym = XK_Down; break;
  382. case XK_J:/* fallthrough */
  383. case XK_l: expect("ctrl-l", ev); break;
  384. case XK_m: expect("ctrl-m", ev); /* fallthrough */
  385. case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
  386. case XK_n: expect("ctrl-n", ev); ksym = XK_Down; break;
  387. case XK_p: expect("ctrl-p", ev); ksym = XK_Up; break;
  388. case XK_o: expect("ctrl-o", ev); break;
  389. case XK_q: expect("ctrl-q", ev); break;
  390. case XK_r: expect("ctrl-r", ev); break;
  391. case XK_s: expect("ctrl-s", ev); break;
  392. case XK_t: expect("ctrl-t", ev); break;
  393. case XK_k: expect("ctrl-k", ev); ksym = XK_Up; break;
  394. case XK_u: expect("ctrl-u", ev); /* delete left */
  395. insert(NULL, 0 - cursor);
  396. break;
  397. case XK_w: expect("ctrl-w", ev); /* delete word */
  398. while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
  399. insert(NULL, nextrune(-1) - cursor);
  400. while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
  401. insert(NULL, nextrune(-1) - cursor);
  402. break;
  403. case XK_v:
  404. expect("ctrl-v", ev);
  405. case XK_V:
  406. XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
  407. utf8, utf8, win, CurrentTime);
  408. return;
  409. case XK_y: expect("ctrl-y", ev); /* paste selection */
  410. case XK_Y:
  411. XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
  412. utf8, utf8, win, CurrentTime);
  413. return;
  414. case XK_x: expect("ctrl-x", ev); break;
  415. case XK_z: expect("ctrl-z", ev); break;
  416. case XK_Left:
  417. case XK_KP_Left:
  418. movewordedge(-1);
  419. goto draw;
  420. case XK_Right:
  421. case XK_KP_Right:
  422. movewordedge(+1);
  423. goto draw;
  424. case XK_Return:
  425. case XK_KP_Enter:
  426. break;
  427. case XK_bracketleft:
  428. cleanup();
  429. exit(1);
  430. default:
  431. return;
  432. }
  433. } else if (ev->state & Mod1Mask) {
  434. switch(ksym) {
  435. case XK_b:
  436. movewordedge(-1);
  437. goto draw;
  438. case XK_f:
  439. movewordedge(+1);
  440. goto draw;
  441. case XK_g: ksym = XK_Home; break;
  442. case XK_G: ksym = XK_End; break;
  443. case XK_h: ksym = XK_Up; break;
  444. case XK_j: ksym = XK_Next; break;
  445. case XK_k: ksym = XK_Prior; break;
  446. case XK_l: ksym = XK_Down; break;
  447. default:
  448. return;
  449. }
  450. }
  451. switch(ksym) {
  452. default:
  453. insert:
  454. if (!iscntrl((unsigned char)*buf))
  455. insert(buf, len);
  456. break;
  457. case XK_Delete:
  458. case XK_KP_Delete:
  459. if (text[cursor] == '\0')
  460. return;
  461. cursor = nextrune(+1);
  462. /* fallthrough */
  463. case XK_BackSpace:
  464. if (cursor == 0)
  465. return;
  466. insert(NULL, nextrune(-1) - cursor);
  467. break;
  468. case XK_End:
  469. case XK_KP_End:
  470. if (text[cursor] != '\0') {
  471. cursor = strlen(text);
  472. break;
  473. }
  474. if (next) {
  475. /* jump to end of list and position items in reverse */
  476. curr = matchend;
  477. calcoffsets();
  478. curr = prev;
  479. calcoffsets();
  480. while (next && (curr = curr->right))
  481. calcoffsets();
  482. }
  483. sel = matchend;
  484. break;
  485. case XK_Escape:
  486. cleanup();
  487. exit(1);
  488. case XK_Home:
  489. case XK_KP_Home:
  490. if (sel == matches) {
  491. cursor = 0;
  492. break;
  493. }
  494. sel = curr = matches;
  495. calcoffsets();
  496. break;
  497. case XK_Left:
  498. case XK_KP_Left:
  499. if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
  500. cursor = nextrune(-1);
  501. break;
  502. }
  503. if (lines > 0)
  504. return;
  505. /* fallthrough */
  506. case XK_Up:
  507. case XK_KP_Up:
  508. if (sel && sel->left && (sel = sel->left)->right == curr) {
  509. curr = prev;
  510. calcoffsets();
  511. }
  512. break;
  513. case XK_Next:
  514. case XK_KP_Next:
  515. if (!next)
  516. return;
  517. sel = curr = next;
  518. calcoffsets();
  519. break;
  520. case XK_Prior:
  521. case XK_KP_Prior:
  522. if (!prev)
  523. return;
  524. sel = curr = prev;
  525. calcoffsets();
  526. break;
  527. case XK_Return:
  528. case XK_KP_Enter:
  529. puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
  530. if (!(ev->state & ControlMask)) {
  531. cleanup();
  532. exit(0);
  533. }
  534. if (sel)
  535. sel->out = 1;
  536. break;
  537. case XK_Right:
  538. case XK_KP_Right:
  539. if (text[cursor] != '\0') {
  540. cursor = nextrune(+1);
  541. break;
  542. }
  543. if (lines > 0)
  544. return;
  545. /* fallthrough */
  546. case XK_Down:
  547. case XK_KP_Down:
  548. if (sel && sel->right && (sel = sel->right) == next) {
  549. curr = next;
  550. calcoffsets();
  551. }
  552. break;
  553. case XK_Tab:
  554. if (!matches)
  555. break; /* cannot complete no matches */
  556. /* only do tab completion if all matches start with prefix */
  557. for (item = matches; item && item->text; item = item->right)
  558. if (item->text[0] != text[0])
  559. goto draw;
  560. strncpy(text, matches->text, sizeof text - 1);
  561. text[sizeof text - 1] = '\0';
  562. len = cursor = strlen(text); /* length of longest common prefix */
  563. for (item = matches; item && item->text; item = item->right) {
  564. cursor = 0;
  565. while (cursor < len && text[cursor] == item->text[cursor])
  566. cursor++;
  567. len = cursor;
  568. }
  569. memset(text + len, '\0', strlen(text) - len);
  570. break;
  571. }
  572. draw:
  573. drawmenu();
  574. }
  575. static void
  576. paste(void)
  577. {
  578. char *p, *q;
  579. int di;
  580. unsigned long dl;
  581. Atom da;
  582. /* we have been given the current selection, now insert it into input */
  583. if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
  584. utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
  585. == Success && p) {
  586. insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
  587. XFree(p);
  588. }
  589. drawmenu();
  590. }
  591. static void
  592. xinitvisual()
  593. {
  594. XVisualInfo *infos;
  595. XRenderPictFormat *fmt;
  596. int nitems;
  597. int i;
  598. XVisualInfo tpl = {
  599. .screen = screen,
  600. .depth = 32,
  601. .class = TrueColor
  602. };
  603. long masks = VisualScreenMask | VisualDepthMask | VisualClassMask;
  604. infos = XGetVisualInfo(dpy, masks, &tpl, &nitems);
  605. visual = NULL;
  606. for(i = 0; i < nitems; i ++) {
  607. fmt = XRenderFindVisualFormat(dpy, infos[i].visual);
  608. if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
  609. visual = infos[i].visual;
  610. depth = infos[i].depth;
  611. cmap = XCreateColormap(dpy, root, visual, AllocNone);
  612. useargb = 1;
  613. break;
  614. }
  615. }
  616. XFree(infos);
  617. if (!visual || !opacity) {
  618. visual = DefaultVisual(dpy, screen);
  619. depth = DefaultDepth(dpy, screen);
  620. cmap = DefaultColormap(dpy, screen);
  621. }
  622. }
  623. static void
  624. readstdin(void)
  625. {
  626. char *line = NULL;
  627. size_t i, linesiz, itemsiz = 0;
  628. ssize_t len;
  629. /* read each line from stdin and add it to the item list */
  630. for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) {
  631. if (i + 1 >= itemsiz) {
  632. itemsiz += 256;
  633. if (!(items = realloc(items, itemsiz * sizeof(*items))))
  634. die("cannot realloc %zu bytes:", itemsiz * sizeof(*items));
  635. }
  636. if (line[len - 1] == '\n')
  637. line[len - 1] = '\0';
  638. if (!(items[i].text = strdup(line)))
  639. die("strdup:");
  640. items[i].out = 0;
  641. }
  642. free(line);
  643. if (items)
  644. items[i].text = NULL;
  645. lines = MIN(lines, i);
  646. }
  647. static void
  648. run(void)
  649. {
  650. XEvent ev;
  651. while (!XNextEvent(dpy, &ev)) {
  652. if (XFilterEvent(&ev, win))
  653. continue;
  654. switch(ev.type) {
  655. case ButtonPress:
  656. buttonpress(&ev);
  657. break;
  658. case DestroyNotify:
  659. if (ev.xdestroywindow.window != win)
  660. break;
  661. cleanup();
  662. exit(1);
  663. case Expose:
  664. if (ev.xexpose.count == 0)
  665. drw_map(drw, win, 0, 0, mw, mh);
  666. break;
  667. case FocusIn:
  668. /* regrab focus from parent window */
  669. if (ev.xfocus.window != win)
  670. grabfocus();
  671. break;
  672. case KeyPress:
  673. keypress(&ev.xkey);
  674. break;
  675. case SelectionNotify:
  676. if (ev.xselection.property == utf8)
  677. paste();
  678. break;
  679. case VisibilityNotify:
  680. if (ev.xvisibility.state != VisibilityUnobscured)
  681. XRaiseWindow(dpy, win);
  682. break;
  683. }
  684. }
  685. }
  686. static void
  687. setup(void)
  688. {
  689. int x, y, i, j;
  690. unsigned int du;
  691. XSetWindowAttributes swa;
  692. XIM xim;
  693. Window w, dw, *dws;
  694. XWindowAttributes wa;
  695. XClassHint ch = {"dmenu", "dmenu"};
  696. #ifdef XINERAMA
  697. XineramaScreenInfo *info;
  698. Window pw;
  699. int a, di, n, area = 0;
  700. #endif
  701. /* init appearance */
  702. for (j = 0; j < SchemeLast; j++)
  703. scheme[j] = drw_scm_create(drw, (const char**)colors[j], alphas[j], 2);
  704. clip = XInternAtom(dpy, "CLIPBOARD", False);
  705. utf8 = XInternAtom(dpy, "UTF8_STRING", False);
  706. /* calculate menu geometry */
  707. bh = drw->fonts->h + 2;
  708. bh = MAX(bh,lineheight); /* make a menu line AT LEAST 'lineheight' tall */
  709. lines = MAX(lines, 0);
  710. mh = (lines + 1) * bh;
  711. promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
  712. #ifdef XINERAMA
  713. i = 0;
  714. if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
  715. XGetInputFocus(dpy, &w, &di);
  716. if (mon >= 0 && mon < n)
  717. i = mon;
  718. else if (w != root && w != PointerRoot && w != None) {
  719. /* find top-level window containing current input focus */
  720. do {
  721. if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
  722. XFree(dws);
  723. } while (w != root && w != pw);
  724. /* find xinerama screen with which the window intersects most */
  725. if (XGetWindowAttributes(dpy, pw, &wa))
  726. for (j = 0; j < n; j++)
  727. if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
  728. area = a;
  729. i = j;
  730. }
  731. }
  732. /* no focused window is on screen, so use pointer location instead */
  733. if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
  734. for (i = 0; i < n; i++)
  735. if (INTERSECT(x, y, 1, 1, info[i]) != 0)
  736. break;
  737. if (center) {
  738. mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
  739. x = info[i].x_org + ((info[i].width - mw) / 2);
  740. y = info[i].y_org + ((info[i].height - mh) / 2);
  741. } else {
  742. x = info[i].x_org;
  743. y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
  744. mw = info[i].width;
  745. }
  746. XFree(info);
  747. } else
  748. #endif
  749. {
  750. if (!XGetWindowAttributes(dpy, parentwin, &wa))
  751. die("could not get embedding window attributes: 0x%lx",
  752. parentwin);
  753. if (center) {
  754. mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
  755. x = (wa.width - mw) / 2;
  756. y = (wa.height - mh) / 2;
  757. } else {
  758. x = 0;
  759. y = topbar ? 0 : wa.height - mh;
  760. mw = wa.width;
  761. }
  762. }
  763. inputw = mw / 3; /* input width: ~33.33% of monitor width */
  764. match();
  765. /* create menu window */
  766. swa.override_redirect = True;
  767. swa.background_pixel = 0;
  768. swa.colormap = cmap;
  769. swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask
  770. | ButtonPressMask
  771. ;
  772. win = XCreateWindow(
  773. dpy, root,
  774. x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width,
  775. depth, InputOutput, visual,
  776. CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa
  777. );
  778. if (border_width)
  779. XSetWindowBorder(dpy, win, scheme[SchemeBorder][ColBg].pixel);
  780. XSetClassHint(dpy, win, &ch);
  781. /* input methods */
  782. if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
  783. die("XOpenIM failed: could not open input device");
  784. xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
  785. XNClientWindow, win, XNFocusWindow, win, NULL);
  786. XMapRaised(dpy, win);
  787. if (embed) {
  788. XReparentWindow(dpy, win, parentwin, x, y);
  789. XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
  790. if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
  791. for (i = 0; i < du && dws[i] != win; ++i)
  792. XSelectInput(dpy, dws[i], FocusChangeMask);
  793. XFree(dws);
  794. }
  795. grabfocus();
  796. }
  797. drw_resize(drw, mw, mh);
  798. drawmenu();
  799. }
  800. static void
  801. usage(void)
  802. {
  803. die("usage: dmenu [-bv"
  804. "c"
  805. "f"
  806. "s"
  807. "x"
  808. "F"
  809. "] "
  810. "[-cw caret_width] "
  811. "[-l lines] [-p prompt] [-fn font] [-m monitor]"
  812. "\n [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]"
  813. "\n "
  814. " [-ex expectkey]"
  815. " [-o opacity]"
  816. " [-bw width]"
  817. "\n "
  818. " [-h height]"
  819. "\n [-nhb color] [-nhf color] [-shb color] [-shf color]" // highlight colors
  820. "\n");
  821. }
  822. int
  823. main(int argc, char *argv[])
  824. {
  825. XWindowAttributes wa;
  826. int i;
  827. int fast = 0;
  828. if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
  829. fputs("warning: no locale support\n", stderr);
  830. if (!(dpy = XOpenDisplay(NULL)))
  831. die("cannot open display");
  832. screen = DefaultScreen(dpy);
  833. root = RootWindow(dpy, screen);
  834. if (!embed || !(parentwin = strtol(embed, NULL, 0)))
  835. parentwin = root;
  836. if (!XGetWindowAttributes(dpy, parentwin, &wa))
  837. die("could not get embedding window attributes: 0x%lx",
  838. parentwin);
  839. /* These need to be checked before we init the visuals. */
  840. for (i = 1; i < argc; i++) {
  841. if (!strcmp(argv[i], "-o")) { /* opacity, pass -o 0 to disable alpha */
  842. opacity = atoi(argv[++i]);
  843. } else {
  844. continue;
  845. }
  846. argv[i][0] = '\0'; // mark as used
  847. }
  848. xinitvisual();
  849. drw = drw_create(dpy, screen, root, wa.width, wa.height, visual, depth, cmap);
  850. readxresources();
  851. for (i = 1; i < argc; i++) {
  852. if (argv[i][0] == '\0')
  853. continue;
  854. /* these options take no arguments */
  855. if (!strcmp(argv[i], "-v")) { /* prints version information */
  856. puts("dmenu-"VERSION);
  857. exit(0);
  858. } else if (!strcmp(argv[i], "-b")) { /* appears at the bottom of the screen */
  859. topbar = 0;
  860. } else if (!strcmp(argv[i], "-c")) { /* toggles centering of dmenu window on screen */
  861. center = !center;
  862. } else if (!strcmp(argv[i], "-f")) { /* grabs keyboard before reading stdin */
  863. fast = 1;
  864. } else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */
  865. fstrncmp = strncmp;
  866. fstrstr = strstr;
  867. } else if (!strcmp(argv[i], "-x")) { /* invert use_prefix */
  868. use_prefix = !use_prefix;
  869. } else if (!strcmp(argv[i], "-F")) { /* disable/enable fuzzy matching, depends on default */
  870. fuzzy = !fuzzy;
  871. } else if (!strcmp(argv[i], "-ex")) { /* expect key */
  872. expected = argv[++i];
  873. } else if (i + 1 == argc)
  874. usage();
  875. /* these options take one argument */
  876. else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */
  877. lines = atoi(argv[++i]);
  878. else if (!strcmp(argv[i], "-m"))
  879. mon = atoi(argv[++i]);
  880. else if (!strcmp(argv[i], "-o")) /* opacity, pass -o 0 to disable alpha */
  881. opacity = atoi(argv[++i]);
  882. else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
  883. prompt = argv[++i];
  884. else if (!strcmp(argv[i], "-fn")) /* font or font set */
  885. fonts[0] = argv[++i];
  886. else if(!strcmp(argv[i], "-h")) { /* minimum height of one menu line */
  887. lineheight = atoi(argv[++i]);
  888. lineheight = MAX(lineheight, min_lineheight); /* reasonable default in case of value too small/negative */
  889. }
  890. else if (!strcmp(argv[i], "-nb")) /* normal background color */
  891. colors[SchemeNorm][ColBg] = argv[++i];
  892. else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
  893. colors[SchemeNorm][ColFg] = argv[++i];
  894. else if (!strcmp(argv[i], "-sb")) /* selected background color */
  895. colors[SchemeSel][ColBg] = argv[++i];
  896. else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
  897. colors[SchemeSel][ColFg] = argv[++i];
  898. else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */
  899. colors[SchemeNormHighlight][ColBg] = argv[++i];
  900. else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */
  901. colors[SchemeNormHighlight][ColFg] = argv[++i];
  902. else if (!strcmp(argv[i], "-shb")) /* selected hi background color */
  903. colors[SchemeSelHighlight][ColBg] = argv[++i];
  904. else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */
  905. colors[SchemeSelHighlight][ColFg] = argv[++i];
  906. else if (!strcmp(argv[i], "-cw")) /* sets caret witdth */
  907. caret_width = atoi(argv[++i]);
  908. else if (!strcmp(argv[i], "-w")) /* embedding window id */
  909. embed = argv[++i];
  910. else if (!strcmp(argv[i], "-bw")) /* border width around dmenu */
  911. border_width = atoi(argv[++i]);
  912. else {
  913. usage();
  914. }
  915. }
  916. if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts)))
  917. die("no fonts could be loaded.");
  918. lrpad = drw->fonts->h;
  919. if (lineheight == -1)
  920. lineheight = drw->fonts->h * 2.5;
  921. #ifdef __OpenBSD__
  922. if (pledge("stdio rpath", NULL) == -1)
  923. die("pledge");
  924. #endif
  925. if (fast && !isatty(0)) {
  926. grabkeyboard();
  927. readstdin();
  928. } else {
  929. readstdin();
  930. grabkeyboard();
  931. }
  932. setup();
  933. run();
  934. return 1; /* unreachable */
  935. }