Xinqi Bao's Git

170a3e04c1494a0be2690ae5237e7374e54fa7a0
[dmenu.git] / main.c
1 /*
2 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
3 * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
4 * See LICENSE file for license details.
5 */
6
7 #include "dmenu.h"
8
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <sys/select.h>
15 #include <sys/time.h>
16 #include <X11/cursorfont.h>
17 #include <X11/Xutil.h>
18 #include <X11/keysym.h>
19
20 typedef struct Item Item;
21 struct Item {
22 Item *next; /* traverses all items */
23 Item *left, *right; /* traverses items matching current search pattern */
24 char *text;
25 };
26
27 /* static */
28
29 static char text[4096];
30 static int mx, my, mw, mh;
31 static int ret = 0;
32 static int nitem = 0;
33 static unsigned int cmdw = 0;
34 static Bool running = True;
35 static Item *allitems = NULL; /* first of all items */
36 static Item *item = NULL; /* first of pattern matching items */
37 static Item *sel = NULL;
38 static Item *next = NULL;
39 static Item *prev = NULL;
40 static Item *curr = NULL;
41 static Window root;
42 static Window win;
43
44 static void
45 calcoffsets(void) {
46 unsigned int tw, w;
47
48 if(!curr)
49 return;
50
51 w = cmdw + 2 * SPACE;
52 for(next = curr; next; next=next->right) {
53 tw = textw(next->text);
54 if(tw > mw / 3)
55 tw = mw / 3;
56 w += tw;
57 if(w > mw)
58 break;
59 }
60
61 w = cmdw + 2 * SPACE;
62 for(prev = curr; prev && prev->left; prev=prev->left) {
63 tw = textw(prev->left->text);
64 if(tw > mw / 3)
65 tw = mw / 3;
66 w += tw;
67 if(w > mw)
68 break;
69 }
70 }
71
72 static void
73 drawmenu(void) {
74 Item *i;
75
76 dc.x = 0;
77 dc.y = 0;
78 dc.w = mw;
79 dc.h = mh;
80 drawtext(NULL, dc.norm);
81
82 /* print command */
83 if(cmdw && item)
84 dc.w = cmdw;
85 drawtext(text[0] ? text : NULL, dc.norm);
86 dc.x += cmdw;
87
88 if(curr) {
89 dc.w = SPACE;
90 drawtext((curr && curr->left) ? "<" : NULL, dc.norm);
91 dc.x += dc.w;
92
93 /* determine maximum items */
94 for(i = curr; i != next; i=i->right) {
95 dc.w = textw(i->text);
96 if(dc.w > mw / 3)
97 dc.w = mw / 3;
98 drawtext(i->text, (sel == i) ? dc.sel : dc.norm);
99 dc.x += dc.w;
100 }
101
102 dc.x = mw - SPACE;
103 dc.w = SPACE;
104 drawtext(next ? ">" : NULL, dc.norm);
105 }
106 XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, mw, mh, 0, 0);
107 XFlush(dpy);
108 }
109
110 static void
111 match(char *pattern) {
112 unsigned int plen;
113 Item *i, *j;
114
115 if(!pattern)
116 return;
117
118 plen = strlen(pattern);
119 item = j = NULL;
120 nitem = 0;
121
122 for(i = allitems; i; i=i->next)
123 if(!plen || !strncmp(pattern, i->text, plen)) {
124 if(!j)
125 item = i;
126 else
127 j->right = i;
128 i->left = j;
129 i->right = NULL;
130 j = i;
131 nitem++;
132 }
133 for(i = allitems; i; i=i->next)
134 if(plen && strncmp(pattern, i->text, plen)
135 && strstr(i->text, pattern)) {
136 if(!j)
137 item = i;
138 else
139 j->right = i;
140 i->left = j;
141 i->right = NULL;
142 j = i;
143 nitem++;
144 }
145
146 curr = prev = next = sel = item;
147 calcoffsets();
148 }
149
150 static void
151 kpress(XKeyEvent * e) {
152 char buf[32];
153 int num, prev_nitem;
154 unsigned int i, len;
155 KeySym ksym;
156
157 len = strlen(text);
158 buf[0] = 0;
159 num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
160
161 if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
162 || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
163 || IsPrivateKeypadKey(ksym))
164 return;
165
166 /* first check if a control mask is omitted */
167 if(e->state & ControlMask) {
168 switch (ksym) {
169 default: /* ignore other control sequences */
170 return;
171 break;
172 case XK_h:
173 case XK_H:
174 ksym = XK_BackSpace;
175 break;
176 case XK_u:
177 case XK_U:
178 text[0] = 0;
179 match(text);
180 drawmenu();
181 return;
182 break;
183 }
184 }
185 switch(ksym) {
186 case XK_Left:
187 if(!(sel && sel->left))
188 return;
189 sel=sel->left;
190 if(sel->right == curr) {
191 curr = prev;
192 calcoffsets();
193 }
194 break;
195 case XK_Tab:
196 if(!sel)
197 return;
198 strncpy(text, sel->text, sizeof(text));
199 match(text);
200 break;
201 case XK_Right:
202 if(!(sel && sel->right))
203 return;
204 sel=sel->right;
205 if(sel == next) {
206 curr = next;
207 calcoffsets();
208 }
209 break;
210 case XK_Return:
211 if((e->state & ShiftMask) && text)
212 fprintf(stdout, "%s", text);
213 else if(sel)
214 fprintf(stdout, "%s", sel->text);
215 else if(text)
216 fprintf(stdout, "%s", text);
217 fflush(stdout);
218 running = False;
219 break;
220 case XK_Escape:
221 ret = 1;
222 running = False;
223 break;
224 case XK_BackSpace:
225 if((i = len)) {
226 prev_nitem = nitem;
227 do {
228 text[--i] = 0;
229 match(text);
230 } while(i && nitem && prev_nitem == nitem);
231 match(text);
232 }
233 break;
234 default:
235 if(num && !iscntrl((int) buf[0])) {
236 buf[num] = 0;
237 if(len > 0)
238 strncat(text, buf, sizeof(text));
239 else
240 strncpy(text, buf, sizeof(text));
241 match(text);
242 }
243 }
244 drawmenu();
245 }
246
247 static char *
248 readstdin(void) {
249 static char *maxname = NULL;
250 char *p, buf[1024];
251 unsigned int len = 0, max = 0;
252 Item *i, *new;
253
254 i = 0;
255 while(fgets(buf, sizeof(buf), stdin)) {
256 len = strlen(buf);
257 if (buf[len - 1] == '\n')
258 buf[len - 1] = 0;
259 p = estrdup(buf);
260 if(max < len) {
261 maxname = p;
262 max = len;
263 }
264
265 new = emalloc(sizeof(Item));
266 new->next = new->left = new->right = NULL;
267 new->text = p;
268 if(!i)
269 allitems = new;
270 else
271 i->next = new;
272 i = new;
273 }
274
275 return maxname;
276 }
277
278 /* extern */
279
280 int screen;
281 Display *dpy;
282 DC dc = {0};
283
284 int
285 main(int argc, char *argv[]) {
286 char *maxname;
287 fd_set rd;
288 struct timeval timeout;
289 Item *i;
290 XEvent ev;
291 XSetWindowAttributes wa;
292
293 if(argc == 2 && !strncmp("-v", argv[1], 3)) {
294 fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
295 exit(EXIT_SUCCESS);
296 }
297 else if(argc != 1)
298 eprint("usage: dmenu [-v]\n");
299
300 dpy = XOpenDisplay(0);
301 if(!dpy)
302 eprint("dmenu: cannot open display\n");
303 screen = DefaultScreen(dpy);
304 root = RootWindow(dpy, screen);
305
306 /* Note, the select() construction allows to grab all keypresses as
307 * early as possible, to not loose them. But if there is no standard
308 * input supplied, we will make sure to exit after MAX_WAIT_STDIN
309 * seconds. This is convenience behavior for rapid typers.
310 */
311 while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
312 GrabModeAsync, CurrentTime) != GrabSuccess)
313 usleep(1000);
314
315 timeout.tv_usec = 0;
316 timeout.tv_sec = STDIN_TIMEOUT;
317 FD_ZERO(&rd);
318 FD_SET(STDIN_FILENO, &rd);
319 if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
320 goto UninitializedEnd;
321 maxname = readstdin();
322
323 /* style */
324 dc.sel[ColBG] = getcolor(SELBGCOLOR);
325 dc.sel[ColFG] = getcolor(SELFGCOLOR);
326 dc.norm[ColBG] = getcolor(NORMBGCOLOR);
327 dc.norm[ColFG] = getcolor(NORMFGCOLOR);
328 setfont(FONT);
329
330 wa.override_redirect = 1;
331 wa.background_pixmap = ParentRelative;
332 wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
333
334 mx = my = 0;
335 mw = DisplayWidth(dpy, screen);
336 mh = dc.font.height + 2;
337
338 win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
339 DefaultDepth(dpy, screen), CopyFromParent,
340 DefaultVisual(dpy, screen),
341 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
342 XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
343
344 /* pixmap */
345 dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
346 dc.gc = XCreateGC(dpy, root, 0, 0);
347 XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
348
349 if(maxname)
350 cmdw = textw(maxname);
351 if(cmdw > mw / 3)
352 cmdw = mw / 3;
353
354 text[0] = 0;
355 match(text);
356 XMapRaised(dpy, win);
357 drawmenu();
358 XSync(dpy, False);
359
360 /* main event loop */
361 while(running && !XNextEvent(dpy, &ev)) {
362 switch (ev.type) {
363 default: /* ignore all crap */
364 break;
365 case KeyPress:
366 kpress(&ev.xkey);
367 break;
368 case Expose:
369 if(ev.xexpose.count == 0)
370 drawmenu();
371 break;
372 }
373 }
374
375 while(allitems) {
376 i = allitems->next;
377 free(allitems->text);
378 free(allitems);
379 allitems = i;
380 }
381 if(dc.font.set)
382 XFreeFontSet(dpy, dc.font.set);
383 else
384 XFreeFont(dpy, dc.font.xfont);
385 XFreePixmap(dpy, dc.drawable);
386 XFreeGC(dpy, dc.gc);
387 XDestroyWindow(dpy, win);
388 UninitializedEnd:
389 XUngrabKeyboard(dpy, CurrentTime);
390 XCloseDisplay(dpy);
391
392 return ret;
393 }