added gridmenu dbf7e039
Anselm R. Garbe · 2006-07-10 16:49 3 file(s) · +617 −3
Makefile +35 −3
3 3
4 4
include config.mk
5 5
6 -
SRC      = wm.c
7 -
OBJ      = ${SRC:.c=.o}
6 +
SRC = wm.c
7 +
OBJ = ${SRC:.c=.o}
8 +
MAN = gridwm.1
9 +
BIN = gridwm gridmenu     
8 10
9 -
all: gridwm
11 +
all: config gridwm
10 12
	@echo finished
11 13
14 +
config:
15 +
	@echo gridwm build options:
16 +
	@echo "LIBS     = ${LIBS}"
17 +
	@echo "CFLAGS   = ${CFLAGS}"
18 +
	@echo "LDFLAGS  = ${LDFLAGS}"
19 +
	@echo "CC       = ${CC}"
20 +
12 21
.c.o:
13 22
	@echo CC $<
14 23
	@${CC} -c ${CFLAGS} $<
21 30
22 31
clean:
23 32
	rm -f gridwm *.o
33 +
34 +
dist: clean
35 +
	mkdir -p gridwm-${VERSION}
36 +
	cp -R Makefile README LICENSE config.mk ${SRC} ${MAN} gridwm-${VERSION}
37 +
	tar -cf gridwm-${VERSION}.tar gridwm-${VERSION}
38 +
	gzip gridwm-${VERSION}.tar
39 +
	rm -rf gridwm-${VERSION}
40 +
41 +
install: all
42 +
	@mkdir -p ${DESTDIR}${PREFIX}/bin
43 +
	@cp -f ${BIN} ${DESTDIR}${PREFIX}/bin
44 +
	@echo installed executable files to ${DESTDIR}${PREFIX}/bin
45 +
	@mkdir -p ${DESTDIR}${MANPREFIX}/man1
46 +
	@cp -f ${MAN1} ${DESTDIR}${MANPREFIX}/man1
47 +
	@echo installed manual pages to ${DESTDIR}${MANPREFIX}/man1
48 +
49 +
uninstall:
50 +
	for i in ${BIN}; do \
51 +
		rm -f ${DESTDIR}${PREFIX}/bin/`basename $$i`; \
52 +
	done
53 +
	for i in ${MAN1}; do \
54 +
		rm -f ${DESTDIR}${MANPREFIX}/man1/`basename $$i`; \
55 +
	done
gridmenu.1 (added) +84 −0
1 +
.TH GRIDMENU 1 grid-0.0
2 +
.SH NAME
3 +
gridmenu \- grid window manager menu
4 +
.SH SYNOPSIS
5 +
.B gridmenu
6 +
.RB [ \-v ]
7 +
.RB [ \-t
8 +
.IR title ]
9 +
.SH DESCRIPTION
10 +
.SS Overview
11 +
.B gridmenu
12 +
is a generic, highly customizable, and efficient menu for the X Window System,
13 +
originally designed for
14 +
.BR grid (1).
15 +
It supports arbitrary, user defined menu contents.
16 +
.SS Options
17 +
.TP
18 +
.B \-v
19 +
prints version information to stdout, then exits.
20 +
.TP
21 +
.BI \-t " title"
22 +
displays
23 +
.I title
24 +
above the menu.
25 +
.SS Usage
26 +
.B gridmenu
27 +
reads a list of newline-separated items from stdin and creates a menu.
28 +
When the user selects an item or enters any text and presses Enter, his choice
29 +
is printed to stdout and
30 +
.B gridmenu
31 +
terminates.
32 +
.SS Keyboard Control 
33 +
.B gridmenu
34 +
is completely controlled by the keyboard.  The following keys are recognized:
35 +
.TP 2
36 +
Any printable character
37 +
appends the character to the text in the input field. This works as a filter:
38 +
only items containing this text will be displayed.
39 +
.TP 2
40 +
Left/Right (Control-p/Control-n)
41 +
select the previous/next item.
42 +
.TP 2
43 +
Tab (Control-i)
44 +
copy the selected item to the input field.
45 +
.TP 2
46 +
Enter (Control-j)
47 +
confirm selection and quit (print the selected item to stdout).
48 +
.TP 2
49 +
Shift-Enter (Shift-Control-j)
50 +
confirm selection and quit (print the text in the input field to stdout).
51 +
.TP 2
52 +
Escape (Control-[)
53 +
quit without selecting an item.
54 +
.TP 2
55 +
Backspace (Control-h)
56 +
remove enough characters from the input field to change its filtering effect.
57 +
.TP 2
58 +
Control-u
59 +
remove all characters from the input field.
60 +
.SS Exit codes
61 +
.B gridmenu
62 +
returns
63 +
.B 0
64 +
if Enter is pressed on termination,
65 +
.B 1
66 +
if Escape is pressed.
67 +
.SH ENVIRONMENT
68 +
.TP
69 +
GRID_FONT
70 +
The X11 font used to display each item in the menu.
71 +
.br
72 +
Default: fixed
73 +
.TP
74 +
GRID_NORMCOLORS
75 +
The foreground, background, and border colors of a label. Syntactically, three blank-separated color values of the form #RRGGBB are expected.
76 +
.br
77 +
Default: #222222 #eeeeee #666666
78 +
.TP
79 +
GRID_SELCOLORS
80 +
Like GRID_NORMCOLORS, but for the selected label.
81 +
.br
82 +
Default: #ffffff #335577 #447799
83 +
.SH SEE ALSO
84 +
.BR gridwm (1)
gridmenu.c (added) +498 −0
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 +
23 +
typedef struct Item Item;
24 +
25 +
struct Item {
26 +
	Item *next;		/* traverses all items */
27 +
	Item *left, *right;	/* traverses items matching current search pattern */
28 +
	char *text;
29 +
};
30 +
31 +
static char *title = nil;
32 +
static Bool done = False;
33 +
static int ret = 0;
34 +
static char text[4096];
35 +
static BlitzColor selcolor;
36 +
static BlitzColor normcolor;
37 +
static Window win;
38 +
static XRectangle mrect;
39 +
static Item *allitem = nil;	/* first of all items */
40 +
static Item *item = nil;	/* first of pattern matching items */
41 +
static Item *sel = nil;
42 +
static Item *nextoff = nil;
43 +
static Item *prevoff = nil;
44 +
static Item *curroff = nil;
45 +
static int nitem = 0;
46 +
static unsigned int cmdw = 0;
47 +
static unsigned int twidth = 0;
48 +
static unsigned int cwidth = 0;
49 +
static Blitz blz = {0};
50 +
static BlitzBrush brush = {0};
51 +
static const int seek = 30;		/* 30px */
52 +
53 +
static void draw_menu(void);
54 +
static void handle_kpress(XKeyEvent * e);
55 +
56 +
static char version[] = "wmiimenu - " VERSION ", (C)opyright MMIV-MMVI Anselm R. Garbe\n";
57 +
58 +
static void
59 +
usage()
60 +
{
61 +
	fprintf(stderr, "%s", "usage: wmiimenu [-v] [-t <title>]\n");
62 +
	exit(1);
63 +
}
64 +
65 +
static void
66 +
update_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 +
93 +
static void
94 +
update_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 */
140 +
static void
141 +
draw_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 +
208 +
static void
209 +
handle_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 +
326 +
static char *
327 +
read_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 +
358 +
int
359 +
main(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 +
}