Xinqi Bao's Git

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