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