all repos — dwm @ 4641aa2925731ac180b08c80f57db176391ea4a9

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