Xinqi Bao's Git

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