all repos — dwm @ 8a34fa50f75f4d6d8af234ac0c4f6d40b988d700

fork of suckless dynamic window manager

gridmenu.c (view raw)

  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 <ctype.h>
  8#include <stdlib.h>
  9#include <stdio.h>
 10#include <string.h>
 11#include <sys/stat.h>
 12#include <sys/wait.h>
 13#include <time.h>
 14#include <unistd.h>
 15#include <X11/Xlib.h>
 16#include <X11/cursorfont.h>
 17#include <X11/Xutil.h>
 18#include <X11/keysym.h>
 19
 20#include <blitz.h>
 21#include <cext.h>
 22
 23typedef struct Item Item;
 24
 25struct Item {
 26	Item *next;		/* traverses all items */
 27	Item *left, *right;	/* traverses items matching current search pattern */
 28	char *text;
 29};
 30
 31static char *title = nil;
 32static Bool done = False;
 33static int ret = 0;
 34static char text[4096];
 35static BlitzColor selcolor;
 36static BlitzColor normcolor;
 37static Window win;
 38static XRectangle mrect;
 39static Item *allitem = nil;	/* first of all items */
 40static Item *item = nil;	/* first of pattern matching items */
 41static Item *sel = nil;
 42static Item *nextoff = nil;
 43static Item *prevoff = nil;
 44static Item *curroff = nil;
 45static int nitem = 0;
 46static unsigned int cmdw = 0;
 47static unsigned int twidth = 0;
 48static unsigned int cwidth = 0;
 49static Blitz blz = {0};
 50static BlitzBrush brush = {0};
 51static const int seek = 30;		/* 30px */
 52
 53static void draw_menu(void);
 54static void handle_kpress(XKeyEvent * e);
 55
 56static char version[] = "wmiimenu - " VERSION ", (C)opyright MMIV-MMVI Anselm R. Garbe\n";
 57
 58static void
 59usage()
 60{
 61	fprintf(stderr, "%s", "usage: wmiimenu [-v] [-t <title>]\n");
 62	exit(1);
 63}
 64
 65static void
 66update_offsets()
 67{
 68	unsigned int tw, w = cmdw + 2 * seek;
 69
 70	if(!curroff)
 71		return;
 72
 73	for(nextoff = curroff; nextoff; nextoff=nextoff->right) {
 74		tw = blitz_textwidth(brush.font, nextoff->text);
 75		if(tw > mrect.width / 3)
 76			tw = mrect.width / 3;
 77		w += tw + mrect.height;
 78		if(w > mrect.width)
 79			break;
 80	}
 81
 82	w = cmdw + 2 * seek;
 83	for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) {
 84		tw = blitz_textwidth(brush.font, prevoff->left->text);
 85		if(tw > mrect.width / 3)
 86			tw = mrect.width / 3;
 87		w += tw + mrect.height;
 88		if(w > mrect.width)
 89			break;
 90	}
 91}
 92
 93static void
 94update_items(char *pattern)
 95{
 96	unsigned int plen = strlen(pattern);
 97	Item *i, *j;
 98
 99	if(!pattern)
100		return;
101
102	if(!title || *pattern)
103		cmdw = cwidth;
104	else
105		cmdw = twidth;
106
107	item = j = nil;
108	nitem = 0;
109
110	for(i = allitem; i; i=i->next)
111		if(!plen || !strncmp(pattern, i->text, plen)) {
112			if(!j)
113				item = i;
114			else
115				j->right = i;
116			i->left = j;
117			i->right = nil;
118			j = i;
119			nitem++;
120		}
121	for(i = allitem; i; i=i->next)
122		if(plen && strncmp(pattern, i->text, plen)
123				&& strstr(i->text, pattern)) {
124			if(!j)
125				item = i;
126			else
127				j->right = i;
128			i->left = j;
129			i->right = nil;
130			j = i;
131			nitem++;
132		}
133
134	curroff = prevoff = nextoff = sel = item;
135
136	update_offsets();
137}
138
139/* creates brush structs for brush mode drawing */
140static void
141draw_menu()
142{
143	unsigned int offx = 0;
144
145	Item *i;
146
147	brush.align = WEST;
148
149	brush.rect = mrect;
150	brush.rect.x = 0;
151	brush.rect.y = 0;
152	brush.color = normcolor;
153	brush.border = False;
154	blitz_draw_tile(&brush);
155
156	/* print command */
157	if(!title || text[0]) {
158		brush.color = normcolor;
159		cmdw = cwidth;
160		if(cmdw && item)
161			brush.rect.width = cmdw;
162		blitz_draw_label(&brush, text);
163	}
164	else {
165		cmdw = twidth;
166		brush.color = selcolor;
167		brush.rect.width = cmdw;
168		blitz_draw_label(&brush, title);
169	}
170	offx += brush.rect.width;
171
172	brush.align = CENTER;
173	if(curroff) {
174		brush.color = normcolor;
175		brush.rect.x = offx;
176		brush.rect.width = seek;
177		offx += brush.rect.width;
178		blitz_draw_label(&brush, (curroff && curroff->left) ? "<" : nil);
179
180		/* determine maximum items */
181		for(i = curroff; i != nextoff; i=i->right) {
182			brush.color = normcolor;
183			brush.border = False;
184			brush.rect.x = offx;
185			brush.rect.width = blitz_textwidth(brush.font, i->text);
186			if(brush.rect.width > mrect.width / 3)
187				brush.rect.width = mrect.width / 3;
188			brush.rect.width += mrect.height;
189			if(sel == i) {
190				brush.color = selcolor;
191				brush.border = True;
192			}
193			blitz_draw_label(&brush, i->text);
194			offx += brush.rect.width;
195		}
196
197		brush.color = normcolor;
198		brush.border = False;
199		brush.rect.x = mrect.width - seek;
200		brush.rect.width = seek;
201		blitz_draw_label(&brush, nextoff ? ">" : nil);
202	}
203	XCopyArea(blz.dpy, brush.drawable, win, brush.gc, 0, 0, mrect.width,
204			mrect.height, 0, 0);
205	XSync(blz.dpy, False);
206}
207
208static void
209handle_kpress(XKeyEvent * e)
210{
211	KeySym ksym;
212	char buf[32];
213	int num, prev_nitem;
214	unsigned int i, len = strlen(text);
215
216	buf[0] = 0;
217	num = XLookupString(e, buf, sizeof(buf), &ksym, 0);
218
219	if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
220			|| IsMiscFunctionKey(ksym) || IsPFKey(ksym)
221			|| IsPrivateKeypadKey(ksym))
222		return;
223
224	/* first check if a control mask is omitted */
225	if(e->state & ControlMask) {
226		switch (ksym) {
227		case XK_H:
228		case XK_h:
229			ksym = XK_BackSpace;
230			break;
231		case XK_I:
232		case XK_i:
233			ksym = XK_Tab;
234			break;
235		case XK_J:
236		case XK_j:
237			ksym = XK_Return;
238			break;
239		case XK_N:
240		case XK_n:
241			ksym = XK_Right;
242			break;
243		case XK_P:
244		case XK_p:
245			ksym = XK_Left;
246			break;
247		case XK_U:
248		case XK_u:
249			text[0] = 0;
250			update_items(text);
251			draw_menu();
252			return;
253			break;
254		case XK_bracketleft:
255			ksym = XK_Escape;
256			break;
257		default:	/* ignore other control sequences */
258			return;
259			break;
260		}
261	}
262	switch (ksym) {
263	case XK_Left:
264		if(!(sel && sel->left))
265			return;
266		sel=sel->left;
267		if(sel->right == curroff) {
268			curroff = prevoff;
269			update_offsets();
270		}
271		break;
272	case XK_Tab:
273		if(!sel)
274			return;
275		cext_strlcpy(text, sel->text, sizeof(text));
276		update_items(text);
277		break;
278	case XK_Right:
279		if(!(sel && sel->right))
280			return;
281		sel=sel->right;
282		if(sel == nextoff) {
283			curroff = nextoff;
284			update_offsets();
285		}
286		break;
287	case XK_Return:
288		if(e->state & ShiftMask) {
289			if(text)
290				fprintf(stdout, "%s", text);
291		}
292		else if(sel)
293			fprintf(stdout, "%s", sel->text);
294		else if(text)
295			fprintf(stdout, "%s", text);
296		fflush(stdout);
297		done = True;
298		break;
299	case XK_Escape:
300		ret = 1;
301		done = True;
302		break;
303	case XK_BackSpace:
304		if((i = len)) {
305			prev_nitem = nitem;
306			do {
307				text[--i] = 0;
308				update_items(text);
309			} while(i && nitem && prev_nitem == nitem);
310			update_items(text);
311		}
312		break;
313	default:
314		if((num == 1) && !iscntrl((int) buf[0])) {
315			buf[num] = 0;
316			if(len > 0)
317				cext_strlcat(text, buf, sizeof(text));
318			else
319				cext_strlcpy(text, buf, sizeof(text));
320			update_items(text);
321		}
322	}
323	draw_menu();
324}
325
326static char *
327read_allitems()
328{
329	static char *maxname = nil;
330	char *p, buf[1024];
331	unsigned int len = 0, max = 0;
332	Item *i, *new;
333
334	i = nil;
335	while(fgets(buf, sizeof(buf), stdin)) {
336		len = strlen(buf);
337		if (buf[len - 1] == '\n')
338			buf[len - 1] = 0;
339		p = cext_estrdup(buf);
340		if(max < len) {
341			maxname = p;
342			max = len;
343		}
344
345		new = cext_emalloc(sizeof(Item));
346		new->next = new->left = new->right = nil;
347		new->text = p;
348		if(!i)
349			allitem = new;
350		else 
351			i->next = new;
352		i = new;
353	}
354
355	return maxname;
356}
357
358int
359main(int argc, char *argv[])
360{
361	int i;
362	XSetWindowAttributes wa;
363	char *maxname, *p;
364	BlitzFont font = {0};
365	GC gc;
366	Drawable pmap;
367	XEvent ev;
368
369	/* command line args */
370	for(i = 1; i < argc; i++) {
371		if (argv[i][0] == '-')
372			switch (argv[i][1]) {
373			case 'v':
374				fprintf(stdout, "%s", version);
375				exit(0);
376				break;
377			case 't':
378				if(++i < argc)
379					title = argv[i];
380				else
381					usage();
382				break;
383			default:
384				usage();
385				break;
386			}
387		else
388			usage();
389	}
390
391	blz.dpy = XOpenDisplay(0);
392	if(!blz.dpy) {
393		fprintf(stderr, "%s", "wmiimenu: cannot open dpy\n");
394		exit(1);
395	}
396	blz.screen = DefaultScreen(blz.dpy);
397	blz.root = RootWindow(blz.dpy, blz.screen);
398
399	maxname = read_allitems();
400
401	/* grab as early as possible, but after reading all items!!! */
402	while(XGrabKeyboard
403			(blz.dpy, blz.root, True, GrabModeAsync,
404			 GrabModeAsync, CurrentTime) != GrabSuccess)
405		usleep(1000);
406
407	font.fontstr = getenv("WMII_FONT");
408	if (!font.fontstr)
409		font.fontstr = cext_estrdup(BLITZ_FONT);
410	blitz_loadfont(&blz, &font);
411
412	if((p = getenv("WMII_NORMCOLORS")))
413		cext_strlcpy(normcolor.colstr, p, sizeof(normcolor.colstr));
414	if(strlen(normcolor.colstr) != 23)
415		cext_strlcpy(normcolor.colstr, BLITZ_NORMCOLORS, sizeof(normcolor.colstr));
416	blitz_loadcolor(&blz, &normcolor);
417
418	if((p = getenv("WMII_SELCOLORS")))
419		cext_strlcpy(selcolor.colstr, p, sizeof(selcolor.colstr));
420	if(strlen(selcolor.colstr) != 23)
421		cext_strlcpy(selcolor.colstr, BLITZ_SELCOLORS, sizeof(selcolor.colstr));
422	blitz_loadcolor(&blz, &selcolor);
423
424	wa.override_redirect = 1;
425	wa.background_pixmap = ParentRelative;
426	wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask
427		| SubstructureRedirectMask | SubstructureNotifyMask;
428
429	mrect.width = DisplayWidth(blz.dpy, blz.screen);
430	mrect.height = font.ascent + font.descent + 4;
431	mrect.y = DisplayHeight(blz.dpy, blz.screen) - mrect.height;
432	mrect.x = 0;
433
434	win = XCreateWindow(blz.dpy, blz.root, mrect.x, mrect.y,
435			mrect.width, mrect.height, 0, DefaultDepth(blz.dpy, blz.screen),
436			CopyFromParent, DefaultVisual(blz.dpy, blz.screen),
437			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
438	XDefineCursor(blz.dpy, win, XCreateFontCursor(blz.dpy, XC_xterm));
439	XSync(blz.dpy, False);
440
441	/* pixmap */
442	gc = XCreateGC(blz.dpy, win, 0, 0);
443	pmap = XCreatePixmap(blz.dpy, win, mrect.width, mrect.height,
444			DefaultDepth(blz.dpy, blz.screen));
445
446	XSync(blz.dpy, False);
447
448	brush.blitz = &blz;
449	brush.color = normcolor;
450	brush.drawable = pmap;
451	brush.gc = gc;
452	brush.font = &font;
453
454	if(maxname)
455		cwidth = blitz_textwidth(brush.font, maxname) + mrect.height;
456	if(cwidth > mrect.width / 3)
457		cwidth = mrect.width / 3;
458
459	if(title) {
460		twidth = blitz_textwidth(brush.font, title) + mrect.height;
461		if(twidth > mrect.width / 3)
462			twidth = mrect.width / 3;
463	}
464
465	cmdw = title ? twidth : cwidth;
466
467	text[0] = 0;
468	update_items(text);
469	XMapRaised(blz.dpy, win);
470	draw_menu();
471	XSync(blz.dpy, False);
472
473	/* main event loop */
474	while(!XNextEvent(blz.dpy, &ev)) {
475		switch (ev.type) {
476			case KeyPress:
477				handle_kpress(&ev.xkey);
478				break;
479			case Expose:
480				if(ev.xexpose.count == 0) {
481					draw_menu();
482				}
483				break;
484			default:
485				break;
486		}
487		if(done)
488			break;
489	}
490
491	XUngrabKeyboard(blz.dpy, CurrentTime);
492	XFreePixmap(blz.dpy, pmap);
493	XFreeGC(blz.dpy, gc);
494	XDestroyWindow(blz.dpy, win);
495	XCloseDisplay(blz.dpy);
496
497	return ret;
498}