all repos — dwm @ 8a8b7956b6de80decbfd3bff6d2ad6e5bb69b2bd

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