Xinqi Bao's Git

3203014d8db280d1fd5241cedaca8d243f41559f
[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) {
212 if(text)
213 fprintf(stdout, "%s", text);
214 }
215 else if(sel)
216 fprintf(stdout, "%s", sel->text);
217 else if(text)
218 fprintf(stdout, "%s", text);
219 fflush(stdout);
220 running = False;
221 break;
222 case XK_Escape:
223 ret = 1;
224 running = False;
225 break;
226 case XK_BackSpace:
227 if((i = len)) {
228 prev_nitem = nitem;
229 do {
230 text[--i] = 0;
231 match(text);
232 } while(i && nitem && prev_nitem == nitem);
233 match(text);
234 }
235 break;
236 default:
237 if(num && !iscntrl((int) buf[0])) {
238 buf[num] = 0;
239 if(len > 0)
240 strncat(text, buf, sizeof(text));
241 else
242 strncpy(text, buf, sizeof(text));
243 match(text);
244 }
245 }
246 drawmenu();
247 }
248
249 static char *
250 readstdin(void) {
251 static char *maxname = NULL;
252 char *p, buf[1024];
253 unsigned int len = 0, max = 0;
254 Item *i, *new;
255
256 i = 0;
257 while(fgets(buf, sizeof(buf), stdin)) {
258 len = strlen(buf);
259 if (buf[len - 1] == '\n')
260 buf[len - 1] = 0;
261 p = estrdup(buf);
262 if(max < len) {
263 maxname = p;
264 max = len;
265 }
266
267 new = emalloc(sizeof(Item));
268 new->next = new->left = new->right = NULL;
269 new->text = p;
270 if(!i)
271 allitems = new;
272 else
273 i->next = new;
274 i = new;
275 }
276
277 return maxname;
278 }
279
280 /* extern */
281
282 int screen;
283 Display *dpy;
284 DC dc = {0};
285
286 int
287 main(int argc, char *argv[]) {
288 char *maxname;
289 fd_set rd;
290 struct timeval timeout;
291 Item *i;
292 XEvent ev;
293 XSetWindowAttributes wa;
294
295 if(argc == 2 && !strncmp("-v", argv[1], 3)) {
296 fputs("dmenu-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
297 exit(EXIT_SUCCESS);
298 }
299 else if(argc != 1)
300 eprint("usage: dmenu [-v]\n");
301
302 dpy = XOpenDisplay(0);
303 if(!dpy)
304 eprint("dmenu: cannot open display\n");
305 screen = DefaultScreen(dpy);
306 root = RootWindow(dpy, screen);
307
308 /* Note, the select() construction allows to grab all keypresses as
309 * early as possible, to not loose them. But if there is no standard
310 * input supplied, we will make sure to exit after MAX_WAIT_STDIN
311 * seconds. This is convenience behavior for rapid typers.
312 */
313 while(XGrabKeyboard(dpy, root, True, GrabModeAsync,
314 GrabModeAsync, CurrentTime) != GrabSuccess)
315 usleep(1000);
316
317 timeout.tv_usec = 0;
318 timeout.tv_sec = STDIN_TIMEOUT;
319 FD_ZERO(&rd);
320 FD_SET(STDIN_FILENO, &rd);
321 if(select(ConnectionNumber(dpy) + 1, &rd, NULL, NULL, &timeout) < 1)
322 goto UninitializedEnd;
323 maxname = readstdin();
324
325 /* style */
326 dc.sel[ColBG] = getcolor(SELBGCOLOR);
327 dc.sel[ColFG] = getcolor(SELFGCOLOR);
328 dc.norm[ColBG] = getcolor(NORMBGCOLOR);
329 dc.norm[ColFG] = getcolor(NORMFGCOLOR);
330 setfont(FONT);
331
332 wa.override_redirect = 1;
333 wa.background_pixmap = ParentRelative;
334 wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask;
335
336 mx = my = 0;
337 mw = DisplayWidth(dpy, screen);
338 mh = dc.font.height + 2;
339
340 win = XCreateWindow(dpy, root, mx, my, mw, mh, 0,
341 DefaultDepth(dpy, screen), CopyFromParent,
342 DefaultVisual(dpy, screen),
343 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
344 XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm));
345
346 /* pixmap */
347 dc.drawable = XCreatePixmap(dpy, root, mw, mh, DefaultDepth(dpy, screen));
348 dc.gc = XCreateGC(dpy, root, 0, 0);
349 XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
350
351 if(maxname)
352 cmdw = textw(maxname);
353 if(cmdw > mw / 3)
354 cmdw = mw / 3;
355
356 text[0] = 0;
357 match(text);
358 XMapRaised(dpy, win);
359 drawmenu();
360 XSync(dpy, False);
361
362 /* main event loop */
363 while(running && !XNextEvent(dpy, &ev)) {
364 switch (ev.type) {
365 default: /* ignore all crap */
366 break;
367 case KeyPress:
368 kpress(&ev.xkey);
369 break;
370 case Expose:
371 if(ev.xexpose.count == 0)
372 drawmenu();
373 break;
374 }
375 }
376
377 while(allitems) {
378 i = allitems->next;
379 free(allitems->text);
380 free(allitems);
381 allitems = i;
382 }
383 if(dc.font.set)
384 XFreeFontSet(dpy, dc.font.set);
385 else
386 XFreeFont(dpy, dc.font.xfont);
387 XFreePixmap(dpy, dc.drawable);
388 XFreeGC(dpy, dc.gc);
389 XDestroyWindow(dpy, win);
390 UninitializedEnd:
391 XUngrabKeyboard(dpy, CurrentTime);
392 XCloseDisplay(dpy);
393
394 return ret;
395 }