all repos — dwm @ da2bbd371c522d63d737d43a127601a3fdbcb9d8

fork of suckless dynamic window manager

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