Xinqi Bao's Git

b460701ae963cbfd7cbce3d16025df4303d22cca
[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 drawbox(&dc, 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 unsigned long *col;
160 Item *i;
161
162 dc.x += cmdw;
163 dc.w = textw(&dc, "<");
164 drawtext(&dc, curr->left ? "<" : NULL, normcol);
165 dc.x += dc.w;
166 for(i = curr; i != next; i = i->right) {
167 dc.w = MIN(textw(&dc, i->text), mw / 3);
168 col = (sel == i) ? selcol : normcol;
169 drawbox(&dc, col);
170 drawtext(&dc, i->text, col);
171 dc.x += dc.w;
172 }
173 dc.w = textw(&dc, ">");
174 dc.x = mw - dc.w;
175 drawtext(&dc, next ? ">" : NULL, normcol);
176 }
177
178 void
179 drawmenuv(void) {
180 Item *i;
181 XWindowAttributes wa;
182
183 dc.y = topbar ? dc.h : 0;
184 dc.w = mw - dc.x;
185 for(i = curr; i != next; i = i->right) {
186 drawtext(&dc, i->text, (sel == i) ? selcol : normcol);
187 dc.y += dc.h;
188 }
189 if(!XGetWindowAttributes(dpy, win, &wa))
190 eprint("cannot get window attributes");
191 XMoveResizeWindow(dpy, win, wa.x, wa.y + (topbar ? 0 : wa.height - mh), mw, mh);
192 }
193
194 void
195 kpress(XKeyEvent *e) {
196 char buf[sizeof text];
197 int num;
198 unsigned int i, len;
199 KeySym ksym;
200
201 len = strlen(text);
202 num = XLookupString(e, buf, sizeof buf, &ksym, NULL);
203 if(ksym == XK_KP_Enter)
204 ksym = XK_Return;
205 else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
206 ksym = (ksym - XK_KP_0) + XK_0;
207 else if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
208 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
209 || IsPrivateKeypadKey(ksym))
210 return;
211 /* first check if a control mask is omitted */
212 if(e->state & ControlMask) {
213 switch(tolower(ksym)) {
214 default:
215 return;
216 case XK_a:
217 ksym = XK_Home;
218 break;
219 case XK_b:
220 ksym = XK_Left;
221 break;
222 case XK_c:
223 ksym = XK_Escape;
224 break;
225 case XK_e:
226 ksym = XK_End;
227 break;
228 case XK_f:
229 ksym = XK_Right;
230 break;
231 case XK_h:
232 ksym = XK_BackSpace;
233 break;
234 case XK_i:
235 ksym = XK_Tab;
236 break;
237 case XK_j:
238 case XK_m:
239 ksym = XK_Return;
240 break;
241 case XK_n:
242 ksym = XK_Down;
243 break;
244 case XK_p:
245 ksym = XK_Up;
246 break;
247 case XK_u:
248 text[0] = '\0';
249 match();
250 break;
251 case XK_w:
252 if(len == 0)
253 return;
254 i = len;
255 while(i-- > 0 && text[i] == ' ');
256 while(i-- > 0 && text[i] != ' ');
257 text[++i] = '\0';
258 match();
259 break;
260 }
261 }
262 switch(ksym) {
263 default:
264 num = MIN(num, sizeof text);
265 if(num && !iscntrl((int) buf[0])) {
266 memcpy(text + len, buf, num + 1);
267 len += num;
268 match();
269 }
270 break;
271 case XK_BackSpace:
272 if(len == 0)
273 return;
274 for(i = 1; len - i > 0 && !IS_UTF8_1ST_CHAR(text[len - i]); i++);
275 len -= i;
276 text[len] = '\0';
277 match();
278 break;
279 case XK_End:
280 while(next) {
281 sel = curr = next;
282 calcoffsets();
283 }
284 while(sel && sel->right)
285 sel = sel->right;
286 break;
287 case XK_Escape:
288 exit(EXIT_FAILURE);
289 case XK_Home:
290 sel = curr = item;
291 calcoffsets();
292 break;
293 case XK_Left:
294 case XK_Up:
295 if(!sel || !sel->left)
296 return;
297 sel = sel->left;
298 if(sel->right == curr) {
299 curr = prev;
300 calcoffsets();
301 }
302 break;
303 case XK_Next:
304 if(!next)
305 return;
306 sel = curr = next;
307 calcoffsets();
308 break;
309 case XK_Prior:
310 if(!prev)
311 return;
312 sel = curr = prev;
313 calcoffsets();
314 break;
315 case XK_Return:
316 if(e->state & ShiftMask)
317 dinput();
318 fprintf(stdout, "%s", sel ? sel->text : text);
319 fflush(stdout);
320 exit(EXIT_SUCCESS);
321 case XK_Right:
322 case XK_Down:
323 if(!sel || !sel->right)
324 return;
325 sel = sel->right;
326 if(sel == next) {
327 curr = next;
328 calcoffsets();
329 }
330 break;
331 case XK_Tab:
332 if(sel)
333 strncpy(text, sel->text, sizeof text);
334 dinput();
335 break;
336 }
337 drawbar();
338 }
339
340 void
341 match(void) {
342 unsigned int len;
343 Item *i, *itemend, *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
344
345 len = strlen(text);
346 item = lexact = lprefix = lsubstr = itemend = exactend = prefixend = substrend = NULL;
347 for(i = allitems; i; i = i->next)
348 if(!fstrncmp(text, i->text, len + 1))
349 appenditem(i, &lexact, &exactend);
350 else if(!fstrncmp(text, i->text, len))
351 appenditem(i, &lprefix, &prefixend);
352 else if(fstrstr(i->text, text))
353 appenditem(i, &lsubstr, &substrend);
354 if(lexact) {
355 item = lexact;
356 itemend = exactend;
357 }
358 if(lprefix) {
359 if(itemend) {
360 itemend->right = lprefix;
361 lprefix->left = itemend;
362 }
363 else
364 item = lprefix;
365 itemend = prefixend;
366 }
367 if(lsubstr) {
368 if(itemend) {
369 itemend->right = lsubstr;
370 lsubstr->left = itemend;
371 }
372 else
373 item = lsubstr;
374 }
375 curr = prev = next = sel = item;
376 calcoffsets();
377 }
378
379 void
380 readstdin(void) {
381 char *p, buf[sizeof text];
382 unsigned int len = 0, max = 0;
383 Item *i, *new;
384
385 i = NULL;
386 while(fgets(buf, sizeof buf, stdin)) {
387 len = strlen(buf);
388 if(buf[len-1] == '\n')
389 buf[--len] = '\0';
390 if(!(p = strdup(buf)))
391 eprint("cannot strdup %u bytes\n", len);
392 if((max = MAX(max, len)) == len)
393 maxname = p;
394 if(!(new = malloc(sizeof *new)))
395 eprint("cannot malloc %u bytes\n", sizeof *new);
396 new->next = new->left = new->right = NULL;
397 new->text = p;
398 if(!i)
399 allitems = new;
400 else
401 i->next = new;
402 i = new;
403 }
404 }
405
406 int
407 main(int argc, char *argv[]) {
408 unsigned int i;
409
410 /* command line args */
411 progname = "dmenu";
412 for(i = 1; i < argc; i++)
413 if(!strcmp(argv[i], "-i")) {
414 fstrncmp = strncasecmp;
415 fstrstr = cistrstr;
416 }
417 else if(!strcmp(argv[i], "-b"))
418 topbar = False;
419 else if(!strcmp(argv[i], "-l")) {
420 if(++i < argc) lines = atoi(argv[i]);
421 if(lines > 0)
422 calcoffsets = calcoffsetsv;
423 }
424 else if(!strcmp(argv[i], "-fn")) {
425 if(++i < argc) font = argv[i];
426 }
427 else if(!strcmp(argv[i], "-nb")) {
428 if(++i < argc) normbgcolor = argv[i];
429 }
430 else if(!strcmp(argv[i], "-nf")) {
431 if(++i < argc) normfgcolor = argv[i];
432 }
433 else if(!strcmp(argv[i], "-p")) {
434 if(++i < argc) prompt = argv[i];
435 }
436 else if(!strcmp(argv[i], "-sb")) {
437 if(++i < argc) selbgcolor = argv[i];
438 }
439 else if(!strcmp(argv[i], "-sf")) {
440 if(++i < argc) selfgcolor = argv[i];
441 }
442 else if(!strcmp(argv[i], "-v")) {
443 printf("dmenu-"VERSION", © 2006-2010 dmenu engineers, see LICENSE for details\n");
444 exit(EXIT_SUCCESS);
445 }
446 else {
447 fputs("usage: dmenu [-i] [-b] [-l <lines>] [-fn <font>] [-nb <color>]\n"
448 " [-nf <color>] [-p <prompt>] [-sb <color>] [-sf <color>] [-v]\n", stderr);
449 exit(EXIT_FAILURE);
450 }
451 if(!setlocale(LC_CTYPE, "") || !XSupportsLocale())
452 fprintf(stderr, "dmenu: warning: no locale support\n");
453 if(!(dpy = XOpenDisplay(NULL)))
454 eprint("cannot open display\n");
455 if(atexit(&cleanup) != 0)
456 eprint("cannot register cleanup\n");
457 screen = DefaultScreen(dpy);
458 root = RootWindow(dpy, screen);
459 if(!(argp = malloc(sizeof *argp * (argc+2))))
460 eprint("cannot malloc %u bytes\n", sizeof *argp * (argc+2));
461 memcpy(argp + 2, argv + 1, sizeof *argp * argc);
462
463 readstdin();
464 grabkeyboard();
465 setup(lines);
466 if(maxname)
467 cmdw = MIN(textw(&dc, maxname), mw / 3);
468 match();
469 run();
470 return 0;
471 }