Xinqi Bao's Git

underline match
[dmenu.git] / dmenu.c
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 <unistd.h>
8 #include <X11/keysym.h>
9 #include <X11/Xlib.h>
10 #include <X11/Xutil.h>
11 #include "dmenu.h"
12
13 typedef struct Item Item;
14 struct Item {
15 char *text;
16 Item *next; /* traverses all items */
17 Item *left, *right; /* traverses items matching current search pattern */
18 };
19
20 /* forward declarations */
21 static void appenditem(Item *i, Item **list, Item **last);
22 static void calcoffsetsh(void);
23 static void calcoffsetsv(void);
24 static char *cistrstr(const char *s, const char *sub);
25 static void cleanup(void);
26 static void dinput(void);
27 static void drawitem(const char *s, unsigned long col[ColLast]);
28 static void drawmenuh(void);
29 static void drawmenuv(void);
30 static void match(void);
31 static void readstdin(void);
32
33 /* variables */
34 static char **argp = NULL;
35 static char *maxname = NULL;
36 static unsigned int cmdw = 0;
37 static unsigned int lines = 0;
38 static Item *allitems = NULL; /* first of all items */
39 static Item *item = NULL; /* first of pattern matching items */
40 static Item *sel = NULL;
41 static Item *next = NULL;
42 static Item *prev = NULL;
43 static Item *curr = NULL;
44 static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
45 static char *(*fstrstr)(const char *, const char *) = strstr;
46 static void (*calcoffsets)(void) = calcoffsetsh;
47
48 void
49 appenditem(Item *i, Item **list, Item **last) {
50 if(!(*last))
51 *list = i;
52 else
53 (*last)->right = i;
54 i->left = *last;
55 i->right = NULL;
56 *last = i;
57 }
58
59 void
60 calcoffsetsh(void) {
61 unsigned int w, x;
62
63 w = promptw + cmdw + textw(&dc, "<") + textw(&dc, ">");
64 for(x = w, next = curr; next; next = next->right)
65 if((x += MIN(textw(&dc, next->text), mw / 3)) > mw)
66 break;
67 for(x = w, prev = curr; prev && prev->left; prev = prev->left)
68 if((x += MIN(textw(&dc, prev->left->text), mw / 3)) > mw)
69 break;
70 }
71
72 void
73 calcoffsetsv(void) {
74 unsigned int i;
75
76 next = prev = curr;
77 for(i = 0; i < lines && next; i++)
78 next = next->right;
79 mh = (dc.font.height + 2) * (i + 1);
80 for(i = 0; i < lines && prev && prev->left; i++)
81 prev = prev->left;
82 }
83
84 char *
85 cistrstr(const char *s, const char *sub) {
86 int c, csub;
87 unsigned int len;
88
89 if(!sub)
90 return (char *)s;
91 if((c = tolower(*sub++)) != '\0') {
92 len = strlen(sub);
93 do {
94 do {
95 if((csub = *s++) == '\0')
96 return NULL;
97 }
98 while(tolower(csub) != c);
99 }
100 while(strncasecmp(s, sub, len) != 0);
101 s--;
102 }
103 return (char *)s;
104 }
105
106 void
107 cleanup(void) {
108 Item *itm;
109
110 while(allitems) {
111 itm = allitems->next;
112 free(allitems->text);
113 free(allitems);
114 allitems = itm;
115 }
116 cleanupdraw(&dc);
117 XDestroyWindow(dpy, win);
118 XUngrabKeyboard(dpy, CurrentTime);
119 XCloseDisplay(dpy);
120 }
121
122 void
123 dinput(void) {
124 cleanup();
125 argp[0] = "dinput";
126 argp[1] = text;
127 execvp("dinput", argp);
128 eprint("cannot exec dinput\n");
129 }
130
131 void
132 drawbar(void) {
133 dc.x = 0;
134 dc.y = 0;
135 dc.w = mw;
136 dc.h = mh;
137 drawbox(&dc, normcol);
138 dc.h = dc.font.height + 2;
139 dc.y = topbar ? 0 : mh - dc.h;
140 /* print prompt? */
141 if(prompt) {
142 dc.w = promptw;
143 drawbox(&dc, selcol);
144 drawtext(&dc, prompt, selcol);
145 dc.x += dc.w;
146 }
147 dc.w = mw - dc.x;
148 /* print command */
149 if(cmdw && item && lines == 0)
150 dc.w = cmdw;
151 drawtext(&dc, text, normcol);
152 if(lines > 0)
153 drawmenuv();
154 else if(curr)
155 drawmenuh();
156 commitdraw(&dc, win);
157 }
158
159 void
160 drawitem(const char *s, unsigned long col[ColLast]) {
161 const char *p;
162 unsigned int w = textnw(&dc, text, strlen(text));
163
164 drawbox(&dc, col);
165 drawtext(&dc, s, col);
166 for(p = fstrstr(s, text); *text && (p = fstrstr(p, text)); p++)
167 drawline(&dc, textnw(&dc, s, p-s) + dc.h/2 - 1, dc.h-2, w, 1, col);
168 }
169
170 void
171 drawmenuh(void) {
172 Item *i;
173
174 dc.x += cmdw;
175 dc.w = textw(&dc, "<");
176 drawtext(&dc, curr->left ? "<" : NULL, normcol);
177 dc.x += dc.w;
178 for(i = curr; i != next; i = i->right) {
179 dc.w = MIN(textw(&dc, i->text), mw / 3);
180 drawitem(i->text, (sel == i) ? selcol : normcol);
181 dc.x += dc.w;
182 }
183 dc.w = textw(&dc, ">");
184 dc.x = mw - dc.w;
185 drawtext(&dc, next ? ">" : NULL, normcol);
186 }
187
188 void
189 drawmenuv(void) {
190 Item *i;
191 XWindowAttributes wa;
192
193 dc.y = topbar ? dc.h : 0;
194 dc.w = mw - dc.x;
195 for(i = curr; i != next; i = i->right) {
196 drawitem(i->text, (sel == i) ? selcol : normcol);
197 dc.y += dc.h;
198 }
199 if(!XGetWindowAttributes(dpy, win, &wa))
200 eprint("cannot get window attributes");
201 XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
202 }
203
204 void
205 kpress(XKeyEvent *e) {
206 char buf[sizeof text];
207 int num;
208 unsigned int i, len;
209 KeySym ksym;
210
211 len = strlen(text);
212 num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
213 if(ksym == XK_KP_Enter)
214 ksym = XK_Return;
215 else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
216 ksym = (ksym - XK_KP_0) + XK_0;
217 else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
218 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
219 || IsPrivateKeypadKey(ksym))
220 return;
221 /* first check if a control mask is omitted */
222 if(e->state & ControlMask) {
223 switch(tolower(ksym)) {
224 default:
225 return;
226 case XK_a:
227 ksym = XK_Home;
228 break;
229 case XK_b:
230 ksym = XK_Left;
231 break;
232 case XK_c:
233 ksym = XK_Escape;
234 break;
235 case XK_e:
236 ksym = XK_End;
237 break;
238 case XK_f:
239 ksym = XK_Right;
240 break;
241 case XK_h:
242 ksym = XK_BackSpace;
243 break;
244 case XK_i:
245 ksym = XK_Tab;
246 break;
247 case XK_j:
248 case XK_m:
249 ksym = XK_Return;
250 break;
251 case XK_n:
252 ksym = XK_Down;
253 break;
254 case XK_p:
255 ksym = XK_Up;
256 break;
257 case XK_u:
258 text[0] = '\0';
259 match();
260 break;
261 case XK_w:
262 if(len == 0)
263 return;
264 i = len;
265 while(i-- > 0 && text[i] == ' ');
266 while(i-- > 0 && text[i] != ' ');
267 text[++i] = '\0';
268 match();
269 break;
270 }
271 }
272 switch(ksym) {
273 default:
274 num = MIN(num, sizeof text);
275 if(num && !iscntrl((int) buf[0])) {
276 memcpy(text + len, buf, num + 1);
277 len += num;
278 match();
279 }
280 break;
281 case XK_BackSpace:
282 if(len == 0)
283 return;
284 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
285 len -= i;
286 text[len] = '\0';
287 match();
288 break;
289 case XK_End:
290 while(next) {
291 sel = curr = next;
292 calcoffsets();
293 }
294 while(sel && sel->right)
295 sel = sel->right;
296 break;
297 case XK_Escape:
298 exit(EXIT_FAILURE);
299 case XK_Home:
300 sel = curr = item;
301 calcoffsets();
302 break;
303 case XK_Left:
304 case XK_Up:
305 if(!sel || !sel->left)
306 return;
307 sel = sel->left;
308 if(sel->right == curr) {
309 curr = prev;
310 calcoffsets();
311 }
312 break;
313 case XK_Next:
314 if(!next)
315 return;
316 sel = curr = next;
317 calcoffsets();
318 break;
319 case XK_Prior:
320 if(!prev)
321 return;
322 sel = curr = prev;
323 calcoffsets();
324 break;
325 case XK_Return:
326 if(e->state & ShiftMask)
327 dinput();
328 fprintf(stdout, "%s", sel ? sel->text : text);
329 fflush(stdout);
330 exit(EXIT_SUCCESS);
331 case XK_Right:
332 case XK_Down:
333 if(!sel || !sel->right)
334 return;
335 sel = sel->right;
336 if(sel == next) {
337 curr = next;
338 calcoffsets();
339 }
340 break;
341 case XK_Tab:
342 if(sel)
343 strncpy(text, sel->text, sizeof text);
344 dinput();
345 break;
346 }
347 drawbar();
348 }
349
350 void
351 match(void) {
352 unsigned int len;
353 Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
354
355 len = strlen(text);
356 item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
357 for(i = allitems; i; i = i->next)
358 if(!fstrncmp(text, i->text, len + 1))
359 appenditem(i, &lexact, &exactend);
360 else if(!fstrncmp(text, i->text, len))
361 appenditem(i, &lprefix, &prefixend);
362 else if(fstrstr(i->text, text))
363 appenditem(i, &lsubstr, &substrend);
364 if(lexact) {
365 item = lexact;
366 itemend = exactend;
367 }
368 if(lprefix) {
369 if(itemend) {
370 itemend->right = lprefix;
371 lprefix->left = itemend;
372 }
373 else
374 item = lprefix;
375 itemend = prefixend;
376 }
377 if(lsubstr) {
378 if(itemend) {
379 itemend->right = lsubstr;
380 lsubstr->left = itemend;
381 }
382 else
383 item = lsubstr;
384 }
385 curr = prev = next = sel = item;
386 calcoffsets();
387 }
388
389 void
390 readstdin(void) {
391 char *p, buf[sizeof text];
392 unsigned int len = 0, max = 0;
393 Item *i, *new;
394
395 i = NULL;
396 while(fgets(buf, sizeof buf, stdin)) {
397 len = strlen(buf);
398 if(buf[len-1] == '\n')
399 buf[--len] = '\0';
400 if(!(p = strdup(buf)))
401 eprint("cannot strdup %u bytes\n", len);
402 if((max = MAX(max, len)) == len)
403 maxname = p;
404 if(!(new = malloc(sizeof *new)))
405 eprint("cannot malloc %u bytes\n", sizeof *new);
406 new->next = new->left = new->right = NULL;
407 new->text = p;
408 if(!i)
409 allitems = new;
410 else
411 i->next = new;
412 i = new;
413 }
414 }
415
416 int
417 main(int argc, char *argv[]) {
418 unsigned int i;
419
420 /* command line args */
421 progname = "dmenu";
422 for(i = 1; i < argc; i++)
423 if(!strcmp(argv[i], "-i")) {
424 fstrncmp = strncasecmp;
425 fstrstr = cistrstr;
426 }
427 else if(!strcmp(argv[i], "-b"))
428 topbar = False;
429 else if(!strcmp(argv[i], "-l")) {
430 if(++i < argc) lines = atoi(argv[i]);
431 if(lines > 0)
432 calcoffsets = calcoffsetsv;
433 }
434 else if(!strcmp(argv[i], "-fn")) {
435 if(++i < argc) font = argv[i];
436 }
437 else if(!strcmp(argv[i], "-nb")) {
438 if(++i < argc) normbgcolor = argv[i];
439 }
440 else if(!strcmp(argv[i], "-nf")) {
441 if(++i < argc) normfgcolor = argv[i];
442 }
443 else if(!strcmp(argv[i], "-p")) {
444 if(++i < argc) prompt = argv[i];
445 }
446 else if(!strcmp(argv[i], "-sb")) {
447 if(++i < argc) selbgcolor = argv[i];
448 }
449 else if(!strcmp(argv[i], "-sf")) {
450 if(++i < argc) selfgcolor = argv[i];
451 }
452 else if(!strcmp(argv[i], "-v")) {
453 printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
454 exit(EXIT_SUCCESS);
455 }
456 else {
457 fputs("usage: dmenu [-i] [-b] [-l <lines>] [-fn <font>] [-nb <color>]\n"
458 " [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
459 exit(EXIT_FAILURE);
460 }
461 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
462 fprintf(stderr, "dmenu: warning: no locale support\n");
463 if(!(dpy = XOpenDisplay(NULL)))
464 eprint("cannot open display\n");
465 if(atexit(&cleanup) != 0)
466 eprint("cannot register cleanup\n");
467 screen = DefaultScreen(dpy);
468 root = RootWindow(dpy, screen);
469 if(!(argp = malloc(sizeof *argp * (argc+2))))
470 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
471 memcpy(argp + 2, argv + 1, sizeof *argp * argc);
472
473 readstdin();
474 grabkeyboard();
475 setup(lines);
476 if(maxname)
477 cmdw = MIN(textw(&dc, maxname), mw / 3);
478 match();
479 run();
480 return 0;
481 }