gridmenu.c
changeset 1 f10194d4b76d
child 3 e969f3575b7a
equal deleted inserted replaced
0:491f34c11291 1:f10194d4b76d
       
     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 }