shithub: misc

Download patch

ref: 204d030a6e6bb71cbc4cdf581b91c8c01242d656
author: qwx <qwx@sciops.net>
date: Thu Mar 4 05:04:11 EST 2021

import

--- /dev/null
+++ b/INDEX
@@ -1,0 +1,10 @@
+calc		learning yacc
+cpick		color picker: take snapshot of screen, then scroll to this window's position for colors
+hcol		display a hex color
+lr		obsolete walk(1)-like
+mev		visualize mouse grabbing
+rfx		mouse accuracy game
+solt		first attempts at plan9 programming
+spd		typing accuracy game (try with /lib/theo!)
+tev		print keyboard/mouse events
+wstat		use syscall(1) instead
--- /dev/null
+++ b/calc.c
@@ -1,0 +1,402 @@
+
+#line	2	"/usr/qwx/p/misc/calc.y"
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+int yylex(void);
+int yyparse(void);
+void yyerror(char*);
+
+Biobuf *bf;
+
+#line	14	"/usr/qwx/p/misc/calc.y"
+typedef union {
+	int i;
+} YYSTYPE;
+extern	int	yyerrflag;
+#ifndef	YYMAXDEPTH
+#define	YYMAXDEPTH	150
+#endif
+YYSTYPE	yylval;
+YYSTYPE	yyval;
+#define	NUM	57346
+#define YYEOFCODE 1
+#define YYERRCODE 2
+
+#line	36	"/usr/qwx/p/misc/calc.y"
+
+
+void
+yyerror(char *s)
+{
+	fprint(2, "%s\n", s);
+}
+
+int
+getnum(int c)
+{
+	char s[64], *p;
+
+	for(p=s, *p++=c; isdigit(c=Bgetc(bf));)
+		if(p < s+sizeof(s)-1)
+			*p++ = c;
+	*p = 0;
+	Bungetc(bf);
+	return strtol(s, nil, 10);
+}
+
+int
+yylex(void)
+{
+	int n, c;
+
+	do
+		c = Bgetc(bf);
+	while(c != '\n' && isspace(c));
+	if(isdigit(c)){
+		yylval.i = getnum(c);
+		return NUM;
+	}else if(c == 'd'){
+		n = getnum(Bgetc(bf));
+		yylval.i = nrand(n != 0 ? n : 1);
+		return NUM;
+	}else
+		return c;
+}
+
+void
+main(int argc, char **argv)
+{
+	ARGBEGIN{
+	}ARGEND
+	if((bf = Bfdopen(0, OREAD)) == nil)
+		sysfatal("Bfdopen: %r");
+	yyparse();
+}
+short	yyexca[] =
+{-1, 1,
+	1, -1,
+	-2, 0,
+};
+#define	YYNPROD	10
+#define	YYPRIVATE 57344
+#define	YYLAST	31
+short	yyact[] =
+{
+   6,   7,   8,   9,  10,   2,   1,  17,   0,   0,
+  11,   0,  12,  13,  14,  15,  16,   6,   7,   8,
+   9,  10,   5,   3,   8,   9,  10,   0,   0,   0,
+   4
+};
+short	yypact[] =
+{
+-1000,  19,  12,-1000,  19,-1000,  19,  19,  19,  19,
+  19,  -5,  17,  17,-1000,-1000,-1000,-1000
+};
+short	yypgo[] =
+{
+   0,   5,   6
+};
+short	yyr1[] =
+{
+   0,   2,   2,   1,   1,   1,   1,   1,   1,   1
+};
+short	yyr2[] =
+{
+   0,   0,   3,   1,   3,   3,   3,   3,   3,   3
+};
+short	yychk[] =
+{
+-1000,  -2,  -1,   4,  11,  10,   5,   6,   7,   8,
+   9,  -1,  -1,  -1,  -1,  -1,  -1,  12
+};
+short	yydef[] =
+{
+   1,  -2,   0,   3,   0,   2,   0,   0,   0,   0,
+   0,   0,   5,   6,   7,   8,   9,   4
+};
+short	yytok1[] =
+{
+   1,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+  10,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   9,   0,   0,
+  11,  12,   7,   5,   0,   6,   0,   8
+};
+short	yytok2[] =
+{
+   2,   3,   4
+};
+long	yytok3[] =
+{
+   0
+};
+
+#line	1	"/sys/lib/yaccpar"
+#define YYFLAG 		-1000
+#define	yyclearin	yychar = -1
+#define	yyerrok		yyerrflag = 0
+
+#ifdef	yydebug
+#include	"y.debug"
+#else
+#define	yydebug		0
+char*	yytoknames[1];		/* for debugging */
+char*	yystates[1];		/* for debugging */
+#endif
+
+/*	parser for yacc output	*/
+
+int	yynerrs = 0;		/* number of errors */
+int	yyerrflag = 0;		/* error recovery flag */
+
+extern	int	fprint(int, char*, ...);
+extern	int	sprint(char*, char*, ...);
+
+char*
+yytokname(int yyc)
+{
+	static char x[16];
+
+	if(yyc > 0 && yyc <= sizeof(yytoknames)/sizeof(yytoknames[0]))
+	if(yytoknames[yyc-1])
+		return yytoknames[yyc-1];
+	sprint(x, "<%d>", yyc);
+	return x;
+}
+
+char*
+yystatname(int yys)
+{
+	static char x[16];
+
+	if(yys >= 0 && yys < sizeof(yystates)/sizeof(yystates[0]))
+	if(yystates[yys])
+		return yystates[yys];
+	sprint(x, "<%d>\n", yys);
+	return x;
+}
+
+long
+yylex1(void)
+{
+	long yychar;
+	long *t3p;
+	int c;
+
+	yychar = yylex();
+	if(yychar <= 0) {
+		c = yytok1[0];
+		goto out;
+	}
+	if(yychar < sizeof(yytok1)/sizeof(yytok1[0])) {
+		c = yytok1[yychar];
+		goto out;
+	}
+	if(yychar >= YYPRIVATE)
+		if(yychar < YYPRIVATE+sizeof(yytok2)/sizeof(yytok2[0])) {
+			c = yytok2[yychar-YYPRIVATE];
+			goto out;
+		}
+	for(t3p=yytok3;; t3p+=2) {
+		c = t3p[0];
+		if(c == yychar) {
+			c = t3p[1];
+			goto out;
+		}
+		if(c == 0)
+			break;
+	}
+	c = 0;
+
+out:
+	if(c == 0)
+		c = yytok2[1];	/* unknown char */
+	if(yydebug >= 3)
+		fprint(2, "lex %.4lux %s\n", yychar, yytokname(c));
+	return c;
+}
+
+int
+yyparse(void)
+{
+	struct
+	{
+		YYSTYPE	yyv;
+		int	yys;
+	} yys[YYMAXDEPTH], *yyp, *yypt;
+	short *yyxi;
+	int yyj, yym, yystate, yyn, yyg;
+	long yychar;
+	YYSTYPE save1, save2;
+	int save3, save4;
+
+	save1 = yylval;
+	save2 = yyval;
+	save3 = yynerrs;
+	save4 = yyerrflag;
+
+	yystate = 0;
+	yychar = -1;
+	yynerrs = 0;
+	yyerrflag = 0;
+	yyp = &yys[-1];
+	goto yystack;
+
+ret0:
+	yyn = 0;
+	goto ret;
+
+ret1:
+	yyn = 1;
+	goto ret;
+
+ret:
+	yylval = save1;
+	yyval = save2;
+	yynerrs = save3;
+	yyerrflag = save4;
+	return yyn;
+
+yystack:
+	/* put a state and value onto the stack */
+	if(yydebug >= 4)
+		fprint(2, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+	yyp++;
+	if(yyp >= &yys[YYMAXDEPTH]) {
+		yyerror("yacc stack overflow");
+		goto ret1;
+	}
+	yyp->yys = yystate;
+	yyp->yyv = yyval;
+
+yynewstate:
+	yyn = yypact[yystate];
+	if(yyn <= YYFLAG)
+		goto yydefault; /* simple state */
+	if(yychar < 0)
+		yychar = yylex1();
+	yyn += yychar;
+	if(yyn < 0 || yyn >= YYLAST)
+		goto yydefault;
+	yyn = yyact[yyn];
+	if(yychk[yyn] == yychar) { /* valid shift */
+		yychar = -1;
+		yyval = yylval;
+		yystate = yyn;
+		if(yyerrflag > 0)
+			yyerrflag--;
+		goto yystack;
+	}
+
+yydefault:
+	/* default state action */
+	yyn = yydef[yystate];
+	if(yyn == -2) {
+		if(yychar < 0)
+			yychar = yylex1();
+
+		/* look through exception table */
+		for(yyxi=yyexca;; yyxi+=2)
+			if(yyxi[0] == -1 && yyxi[1] == yystate)
+				break;
+		for(yyxi += 2;; yyxi += 2) {
+			yyn = yyxi[0];
+			if(yyn < 0 || yyn == yychar)
+				break;
+		}
+		yyn = yyxi[1];
+		if(yyn < 0)
+			goto ret0;
+	}
+	if(yyn == 0) {
+		/* error ... attempt to resume parsing */
+		switch(yyerrflag) {
+		case 0:   /* brand new error */
+			yyerror("syntax error");
+			yynerrs++;
+			if(yydebug >= 1) {
+				fprint(2, "%s", yystatname(yystate));
+				fprint(2, "saw %s\n", yytokname(yychar));
+			}
+
+		case 1:
+		case 2: /* incompletely recovered error ... try again */
+			yyerrflag = 3;
+
+			/* find a state where "error" is a legal shift action */
+			while(yyp >= yys) {
+				yyn = yypact[yyp->yys] + YYERRCODE;
+				if(yyn >= 0 && yyn < YYLAST) {
+					yystate = yyact[yyn];  /* simulate a shift of "error" */
+					if(yychk[yystate] == YYERRCODE)
+						goto yystack;
+				}
+
+				/* the current yyp has no shift onn "error", pop stack */
+				if(yydebug >= 2)
+					fprint(2, "error recovery pops state %d, uncovers %d\n",
+						yyp->yys, (yyp-1)->yys );
+				yyp--;
+			}
+			/* there is no state on the stack with an error shift ... abort */
+			goto ret1;
+
+		case 3:  /* no shift yet; clobber input char */
+			if(yydebug >= 2)
+				fprint(2, "error recovery discards %s\n", yytokname(yychar));
+			if(yychar == YYEOFCODE)
+				goto ret1;
+			yychar = -1;
+			goto yynewstate;   /* try again in the same state */
+		}
+	}
+
+	/* reduction by production yyn */
+	if(yydebug >= 2)
+		fprint(2, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+	yypt = yyp;
+	yyp -= yyr2[yyn];
+	yyval = (yyp+1)->yyv;
+	yym = yyn;
+
+	/* consult goto table to find next state */
+	yyn = yyr1[yyn];
+	yyg = yypgo[yyn];
+	yyj = yyg + yyp->yys + 1;
+
+	if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+		yystate = yyact[yyg];
+	switch(yym) {
+		
+case 2:
+#line	26	"/usr/qwx/p/misc/calc.y"
+{ print("%d\n", yypt[-1].yyv.i); } break;
+case 3:
+#line	28	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-0].yyv.i; } break;
+case 4:
+#line	29	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-1].yyv.i; } break;
+case 5:
+#line	30	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-2].yyv.i + yypt[-0].yyv.i; } break;
+case 6:
+#line	31	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-2].yyv.i - yypt[-0].yyv.i; } break;
+case 7:
+#line	32	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-2].yyv.i * yypt[-0].yyv.i; } break;
+case 8:
+#line	33	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-2].yyv.i / yypt[-0].yyv.i; } break;
+case 9:
+#line	34	"/usr/qwx/p/misc/calc.y"
+{ yyval.i = yypt[-2].yyv.i % yypt[-0].yyv.i; } break;
+	}
+	goto yystack;  /* stack new state and value */
+}
--- /dev/null
+++ b/calc.tab.h
@@ -1,0 +1,6 @@
+
+typedef union {
+	int i;
+}	YYSTYPE;
+extern	YYSTYPE	yylval;
+#define	NUM	57346
--- /dev/null
+++ b/calc.y
@@ -1,0 +1,84 @@
+%{
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+int yylex(void);
+int yyparse(void);
+void yyerror(char*);
+
+Biobuf *bf;
+%}
+
+%union{
+	int i;
+}
+
+%token<i> NUM
+%type<i> expr
+
+%left '+' '-'
+%left '*' '/' '%'
+
+%%
+
+input:	| input expr '\n' { print("%d\n", $2); }
+
+expr:	NUM { $$ = $1; }
+	| '(' expr ')' { $$ = $2; }
+	| expr '+' expr { $$ = $1 + $3; }
+	| expr '-' expr { $$ = $1 - $3; }
+	| expr '*' expr { $$ = $1 * $3; }
+	| expr '/' expr { $$ = $1 / $3; }
+	| expr '%' expr { $$ = $1 % $3; }
+
+%%
+
+void
+yyerror(char *s)
+{
+	fprint(2, "%s\n", s);
+}
+
+int
+getnum(int c)
+{
+	char s[64], *p;
+
+	for(p=s, *p++=c; isdigit(c=Bgetc(bf));)
+		if(p < s+sizeof(s)-1)
+			*p++ = c;
+	*p = 0;
+	Bungetc(bf);
+	return strtol(s, nil, 10);
+}
+
+int
+yylex(void)
+{
+	int n, c;
+
+	do
+		c = Bgetc(bf);
+	while(c != '\n' && isspace(c));
+	if(isdigit(c)){
+		yylval.i = getnum(c);
+		return NUM;
+	}else if(c == 'd'){
+		n = getnum(Bgetc(bf));
+		yylval.i = nrand(n != 0 ? n : 1);
+		return NUM;
+	}else
+		return c;
+}
+
+void
+main(int argc, char **argv)
+{
+	ARGBEGIN{
+	}ARGEND
+	if((bf = Bfdopen(0, OREAD)) == nil)
+		sysfatal("Bfdopen: %r");
+	yyparse();
+}
--- /dev/null
+++ b/cpick.c
@@ -1,0 +1,109 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+/* there should really be a color picker in paint(1) instead */
+
+Keyboardctl *kc;
+Mousectl *mc;
+Image *pic, *boxc;
+Point pan;
+Rectangle box;
+
+void
+putcol(Point m)
+{
+	char s[32];
+	uchar buf[4];
+	u32int v;
+	Point p;
+
+	p = addpt(screen->r.min, pan);
+	unloadimage(pic, rectsubpt(Rpt(m, Pt(m.x+1, m.y+1)), p), buf, sizeof buf);
+	loadimage(boxc, boxc->r, buf, sizeof buf);
+	draw(screen, rectaddpt(box, p), boxc, nil, ZP);
+	v = buf[0] << 16 | buf[1] << 8 | buf[2];
+	snprint(s, sizeof s, "%#08ux", v);
+	string(screen, addpt(box.min, p), display->black, ZP, font, s);
+	flushimage(display, 1);
+}
+
+void
+redraw(void)
+{
+	draw(screen, rectaddpt(pic->r, addpt(screen->r.min, pan)), pic, nil, ZP);
+	flushimage(display, 1);
+}
+
+Image *
+iconv(Image *i)
+{
+	Image *ni;
+
+	if(i->chan == XBGR32)
+		return i;
+	if((ni = allocimage(display, i->r, XBGR32, 0, DNofill)) == nil)
+		sysfatal("allocimage: %r");
+	draw(ni, ni->r, i, nil, ZP);
+	freeimage(i);
+	return ni;
+}
+
+void
+threadmain(int, char **)
+{
+	int fd;
+	Mouse m, om;
+	Rune r;
+
+	if(initdraw(nil, nil, "cpick") < 0)
+		sysfatal("initdraw: %r");
+	if((fd = open("/dev/screen", OREAD)) < 0)
+		sysfatal("open: %r");
+	if((pic = readimage(display, fd, 0)) == nil)
+		sysfatal("readimage: %r");
+	close(fd);
+	pic = iconv(pic);
+	box = screen->r;
+	if((boxc = allocimage(display, Rect(0,0,1,1), pic->chan, 1, DNofill)) == nil)
+		sysfatal("allocimage: %r");
+	redraw();
+	if((kc = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+	if((mc = initmouse(nil, _screen->display->image)) == nil)
+		sysfatal("initmouse: %r");
+	Alt a[] = {
+		{mc->resizec, nil, CHANRCV},
+		{mc->c, &m, CHANRCV},
+		{kc->c, &r, CHANRCV},
+		{nil, nil, CHANEND}
+	};
+	for(;;){
+		switch(alt(a)){
+		case 0:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			redraw();
+			break;
+		case 1:
+			if((m.buttons & 4) == 4){
+				pan.x += m.xy.x - om.xy.x;
+				pan.y += m.xy.y - om.xy.y;
+				redraw();
+			}else
+				putcol(m.xy);
+			om = m;
+			break;
+		case 2:
+			switch(r){
+			case Kdel: case 'q': threadexitsall(nil);
+			}
+			break;
+		default:
+			threadexitsall("alt: %r");
+		}
+	}
+}
--- /dev/null
+++ b/hcol.c
@@ -1,0 +1,94 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+char id[16];
+Point ps;
+Image *col;
+Keyboardctl *kc;
+Mousectl *mc;
+
+void
+plaster(void)
+{
+	draw(screen, screen->r, col, nil, ZP);
+	string(screen, ps, display->black, ZP, font, id);
+	flushimage(display, 1);
+}
+
+Image *
+eallocim(ulong col)
+{
+	Image *i;
+
+	i = allocimage(display, Rect(0,0,1,1), RGB24, 1, col);
+	if(i == nil)
+		sysfatal("allocimage: %r");
+	return i;
+}
+
+void
+setid(u32int n)
+{
+	sprint(id, "%#ux", n);
+}
+
+void
+reset(void)
+{
+	ps = addpt(screen->r.min, stringsize(font, "A"));
+	plaster();
+}
+
+void
+threadmain(int, char **)
+{
+	u32int n;
+	char line[16];
+	Rune r;
+
+	if(initdraw(nil, nil, "hcol") < 0)
+		sysfatal("initdraw: %r");
+	kc = initkeyboard(nil);
+	if(kc == nil)
+		sysfatal("initkeyboard: %r");
+	mc = initmouse(nil, screen);
+	if(mc == nil)
+		sysfatal("initmouse: %r");
+	col = eallocim(DBlack);
+	setid(0);
+	reset();
+
+	Alt a[] = {
+		{mc->c, nil, CHANRCV},
+		{mc->resizec, nil, CHANRCV},
+		{kc->c, &r, CHANRCV},
+		{nil, nil, CHANEND}
+	};
+	for(;;)
+		switch(alt(a)){
+		case 1:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			reset();
+			break;
+		case 2:
+			if(r == Kdel)
+				threadexitsall(nil);
+			memset(line, 0, sizeof line);
+			runetochar(line, &r);
+			if(enter(nil, line, 12, mc, kc, nil) < 0)
+				break;
+			n = strtoul(line, nil, 0);
+			setid(n);
+			freeimage(col);
+			col = eallocim(n<<8);
+			if(col == nil)
+				sysfatal("allocimage: %r");
+			plaster();
+			break;
+		}
+}
--- /dev/null
+++ b/lr.c
@@ -1,0 +1,159 @@
+/* lr - retarded recursive file list */
+#include <u.h>
+#include <libc.h>
+#include <String.h>
+
+extern	void	du(char*, Dir*);
+extern	void	err(char*);
+extern	int	seen(Dir*);
+extern	int	warn(char*);
+
+char	*fmt = "%s\n";
+char	*readbuf;
+int	dflag;
+int	nflag;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-dnQ] [file ...]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int doqt;
+	char *s;
+
+	doqt = 1;
+	ARGBEGIN{
+	case 'd': dflag++; break;
+	case 'n': nflag++; dflag++; break;
+	case 'Q': doqt = 0; break;
+	default: usage();
+	}ARGEND
+	if(doqt){
+		doquote = needsrcquote;
+		quotefmtinstall();
+		fmt = "%q\n";
+	}
+	if(argc == 0)
+		du(".", dirstat("."));
+	else
+		while(*argv != nil){
+			s = *argv++;
+			du(s, dirstat(s));
+		}
+	exits(nil);
+}
+
+void
+dufile(char *name, Dir *d)
+{
+	String *s;
+
+	s = s_copy(name);
+	s_append(s, "/");
+	s_append(s, d->name);
+	print(fmt, s_to_c(s));
+	s_free(s);
+}
+
+void
+du(char *name, Dir *dir)
+{
+	int fd, i, n;
+	Dir *buf, *d;
+	String *s;
+
+	if(dir == nil){
+		warn(name);
+		return;
+	}
+	if((dir->qid.type & QTDIR) == 0){
+		if(!nflag)
+			print(fmt, name);
+		return;
+	}
+
+	fd = open(name, OREAD);
+	if(fd < 0){
+		warn(name);
+		return;
+	}
+	n = dirreadall(fd, &buf);
+	if(n < 0)
+		warn(name);
+	close(fd);
+	for(i=n, d=buf; i>0; i--, d++){
+		if((d->qid.type & QTDIR) == 0){
+			if(!nflag)
+				dufile(name, d);
+			continue;
+		}
+		if(strcmp(d->name, ".") == 0 || strcmp(d->name, "..") == 0
+		|| seen(d))
+			continue;	/* don't get stuck */
+
+		s = s_copy(name);
+		s_append(s, "/");
+		s_append(s, d->name);
+		du(s_to_c(s), d);
+		s_free(s);
+	}
+	free(buf);
+	if(dflag)
+		print(fmt, name);
+}
+
+#define	NCACHE	256	/* must be power of two */
+
+typedef struct
+{
+	Dir*	cache;
+	int	n;
+	int	max;
+} Cache;
+Cache cache[NCACHE];
+
+int
+seen(Dir *dir)
+{
+	Dir *dp;
+	int i;
+	Cache *c;
+
+	c = &cache[dir->qid.path&(NCACHE-1)];
+	dp = c->cache;
+	for(i=0; i<c->n; i++, dp++)
+		if(dir->qid.path == dp->qid.path &&
+		   dir->type == dp->type &&
+		   dir->dev == dp->dev)
+			return 1;
+	if(c->n == c->max){
+		if (c->max == 0)
+			c->max = 8;
+		else
+			c->max += c->max/2;
+		c->cache = realloc(c->cache, c->max*sizeof(Dir));
+		if(c->cache == nil)
+			err("malloc failure");
+	}
+	c->cache[c->n++] = *dir;
+	return 0;
+}
+
+void
+err(char *s)
+{
+	fprint(2, "du: %s: %r\n", s);
+	exits(s);
+}
+
+int
+warn(char *s)
+{
+	fprint(2, "du: %s: %r\n", s);
+	return 0;
+}
--- /dev/null
+++ b/mev.c
@@ -1,0 +1,167 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+int grx = 200 / 4, gry = 200 / 4;
+Image *gc, *uc, *nc;
+int mson, fix;
+Point p0, mp;
+Rectangle grabr, rt;
+
+void
+reset(void)
+{
+	Point p;
+
+	p0 = divpt(addpt(screen->r.min, screen->r.max), 2);
+	p = Pt(grx, gry);
+	grabr = Rpt(subpt(p0, p), addpt(p0, p));
+	rt = Rpt(screen->r.min, addpt(screen->r.min, Pt(8*12, font->height)));
+	draw(screen, screen->r, display->black, nil, ZP);
+	flushimage(display, 1);
+}
+
+void
+mproc(void)
+{
+	int fd, n, nerr;
+	char buf[1+5*12], s[16];
+	Mouse m;
+	Point p;
+	Rectangle r;
+
+	fd = open("/dev/mouse", ORDWR);
+	if(fd < 0)
+		sysfatal("open /dev/mouse: %r");
+	memset(&m, 0, sizeof m);
+	nerr = 0;
+	r = Rect(0,0,1,1);
+	mp = p0;
+	for(;;){
+		n = read(fd, buf, sizeof buf);
+		if(n != 1+4*12){
+			if(n < 0 || ++nerr > 10)
+				break;
+			continue;
+		}
+		nerr = 0;
+		switch(*buf){
+		case 'r':
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("resize failed: %r");
+			reset();
+		case 'm':
+			p.x = strtol(buf+1+0*12, nil, 10);
+			p.y = strtol(buf+1+1*12, nil, 10);
+			if(!mson){
+				draw(screen, rectaddpt(r, p), nc, nil, ZP);
+				flushimage(display, 1);
+				break;
+			}
+			if(fix){
+				fix = 0;
+				goto res;
+			}
+			draw(screen, rt, display->black, nil, ZP);
+			sprint(s, "%d,%d", p.x - mp.x, p.y - mp.y);
+			string(screen, screen->r.min, uc, ZP, font, s);
+			draw(screen, rectaddpt(r, mp), uc, nil, ZP);
+			draw(screen, rectaddpt(r, p), gc, nil, ZP);
+			flushimage(display, 1);
+			if(!ptinrect(p, grabr)){
+		res:
+				fprint(fd, "m%d %d", p0.x, p0.y);
+				p = p0;
+			}
+			mp = p;
+			break;
+		}
+	}
+}
+
+void
+grab(void)
+{
+	static char nocurs[2*4+2*2*16];
+	static int fd = -1;
+
+	mson ^= 1;
+	if(mson){
+		fd = open("/dev/cursor", ORDWR|OCEXEC);
+		if(fd < 0){
+			fprint(2, "grab: %r\n");
+			return;
+		}
+		write(fd, nocurs, sizeof nocurs);
+		fix++;
+	}else if(fd >= 0){
+		close(fd);
+		fd = -1;
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-x dx] [-y dy]\n", argv0);
+	sysfatal("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int n, fd, mpid;
+	char buf[256];
+	Rune r;
+
+	ARGBEGIN{
+	case 'x': grx = strtol(EARGF(usage()), nil, 0); break;
+	case 'y': gry = strtol(EARGF(usage()), nil, 0); break;
+	default: usage();
+	}ARGEND
+	if(initdraw(nil, nil, "mev") < 0)
+		sysfatal("initdraw: %r");
+	gc = allocimage(display, Rect(0,0,1,1), RGB24, 1, DGreen);
+	uc = allocimage(display, Rect(0,0,1,1), RGB24, 1, DRed);
+	nc = allocimage(display, Rect(0,0,1,1), RGB24, 1, DBlue);
+	reset();
+	mpid = rfork(RFPROC|RFMEM);
+	switch(mpid){
+	case -1:
+		sysfatal("fork: %r");
+	case 0:
+		mproc();
+		exits(nil);
+	}
+	fd = open("/dev/kbd", OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+		if(buf[0] == 'c'){
+			chartorune(&r, buf+1);
+			if(utfrune(buf, Kdel))
+				break;
+			else if(utfrune(buf, ' '))
+				grab();
+			else if(utfrune(buf, 'b')){
+				draw(screen, screen->r, display->black, nil, ZP);
+				flushimage(display, 1);
+			}
+		}
+	}
+	postnote(PNPROC, mpid, "shutdown");
+	exits(nil);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,22 @@
+</$objtype/mkfile
+TARG=\
+	calc\
+	cpick\
+	hcol\
+	lr\
+	mev\
+	rfx\
+	spd\
+	tev\
+	wstat\
+
+HFILES=
+YFLAGS=-D
+</sys/src/cmd/mkmany
+BIN=$home/bin/$objtype
+
+%.tab.h %.tab.c:D:	%.y
+	$YACC $YFLAGS -s $stem $prereq
+
+(calc).c:R:	\1.tab.c
+	mv $stem1.tab.c $stem1.c
--- /dev/null
+++ b/rfx.c
@@ -1,0 +1,444 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+
+typedef struct Tg Tg;
+struct Tg{
+	Point;
+	Point v;
+	int r;
+	Tg *p;
+	Tg *n;
+};
+Tg troot;
+Tg *tg;
+
+vlong tics;
+int ntg;
+int hits, misses, misclicks;
+uint bmask = 1;
+int oldb;
+enum{
+	NSEC = 1000000000
+};
+/* recommend these are set well outside the confort range, but not so much so
+ * that every game lasts less than a minute */
+vlong div, div0 = NSEC/20;	/* tic duration (ns) */
+int tddd = 220;	/* tic duration decrease delay (tics) */
+int Δ0 = 100;	/* tic duration delta factor */
+int mtdt = 5;	/* tics/tddd % mtdt: delay to tgmax++ */
+int floatdt = 25;	/* floating text delay (tics) */
+int newdt = 8;		/* target spawn delay (tics) */
+int dmax, rmax = 24;	/* max target radius (px) */
+int tgmax = 3;	/* initial max number of targets onscreen */
+int missmax = 10;	/* max targets let go */
+int nogrow;	/* don't grow and shrink targets */
+int done;
+
+enum{
+	CBACK,
+	CTARG,
+	CMISS,
+	CTXT,
+	CEND
+};
+Image *col[CEND];
+Image *fb;
+Rectangle winxy;	/* spawning area */
+Mousectl *mctl;
+Keyboardctl *kctl;
+Channel *ticc;
+
+#define trr()	(rmax+1 - t->r % (rmax+1))
+#define tr()	(nogrow ? rmax : t->r <= rmax ? t->r : trr())
+
+Image *
+eallocim(Rectangle r, int repl, ulong col)
+{
+	Image *i;
+
+	i = allocimage(display, r, screen->chan, repl, col);
+	if(i == nil)
+		sysfatal("allocimage: %r");
+	return i;
+}
+
+void
+tic(void *)
+{
+	vlong t, dt, δ;
+
+	t = nsec();
+	dt = div/1000000;
+	for(;;){
+		if(dt >= 0){
+			sleep(dt);
+			if(nbsend(ticc, nil) < 0)
+				break;
+		}
+		δ = div;
+		dt = (t + 2*δ - nsec()) / 1000000;
+		t += δ;
+	}
+}
+
+Tg *
+push(void)
+{
+	Tg *t;
+
+	t = mallocz(sizeof *t, 1);
+	if(t == nil)
+		sysfatal("talloc: %r");
+	t->p = tg->p;
+	t->n = tg;
+	tg->p->n = t;
+	tg->p = t;
+	return t;
+}
+
+void
+pop(Tg *t)
+{
+	t->p->n = t->n;
+	t->n->p = t->p;
+	free(t);
+}
+
+void
+nuke(void)
+{
+	while(tg->n != tg)
+		pop(tg->n);
+	ntg = 0;
+}
+
+void
+drw(Tg *t)
+{
+	int r;
+
+	r = tr();
+	fillellipse(fb, *t, r, r, col[CTARG], ZP);
+}
+
+void
+clr(Tg *t)
+{
+	draw(fb, Rpt(*t, t->v), col[CBACK], nil, ZP);
+}
+
+void
+txt(char *s, Tg *t, Image *i)
+{
+	Point o;
+
+	if(t == nil)
+		o = subpt(divpt(winxy.max, 2), divpt(stringsize(font, s), 2));
+	else{
+		o = divpt(stringsize(font, s), 2);
+		t->v = addpt(*t, o);
+		t->x -= o.x;
+		t->y -= o.y;
+		o = *t;
+	}
+	string(fb, o, i, ZP, font, s);
+}
+
+void
+misclk(Point o)
+{
+	Tg *t;
+
+	/* FIXME: often doesn't do anything */
+	fillellipse(fb, o, 1, 1, col[CTARG], ZP);
+	t = push();
+	t->v = addpt(o, Pt(2,2));
+	t->x = o.x - 1;
+	t->y = o.y - 1;
+	t->r = dmax+1;
+	misclicks++;
+}
+
+void
+hit(Tg *t)
+{
+	int r;
+
+	r = tr();
+	fillellipse(fb, *t, r, r, col[CBACK], ZP);
+	pop(t);
+	ntg--;
+	hits++;
+}
+
+void
+miss(Tg *t)
+{
+	int r;
+
+	r = nogrow ? rmax : 1;
+	fillellipse(fb, *t, r, r, col[CBACK], ZP);
+	txt("miss", t, col[CMISS]);
+	misses++;
+	ntg--;
+}
+
+void
+grow(Tg *t)
+{
+	if(nogrow)
+		return;
+	drw(t);
+}
+
+void
+shrink(Tg *t)
+{
+	int r;
+
+	if(nogrow)
+		return;
+	r = trr();
+	fillellipse(fb, *t, r, r, col[CBACK], ZP);
+	r--;
+	fillellipse(fb, *t, r, r, col[CTARG], ZP);
+}
+
+void
+spawn(void)
+{
+	Tg *t;
+
+	t = push();
+	ntg++;
+	t->x = winxy.min.x + nrand(winxy.max.x - winxy.min.x);
+	t->y = winxy.min.y + nrand(winxy.max.y - winxy.min.y);
+	if(nogrow){
+		t->r = rmax;
+		drw(t);
+	}
+}
+
+void
+check(int b, Point m)
+{
+	Point o;
+	Tg *t;
+
+	/* FIXME: implement counters for each button */
+	USED(b);
+	m = subpt(m, screen->r.min);
+	for(t=tg->n; t != tg; t=t->n){
+		if(t->r > dmax)
+			continue;
+		o = subpt(*t, m);
+		if(hypot(o.x, o.y) < tr()){
+			hit(t);
+			return;
+		}
+	}
+	misclk(m);
+}
+
+void
+plaster(void)
+{
+	draw(screen, screen->r, fb, nil, ZP);
+	flushimage(display, 1);
+}
+
+void
+score(void)
+{
+	char s[64];
+
+	sprint(s, "hit %d misses %d misclicks %d", hits, misses, misclicks);
+	txt(s, nil, col[CTXT]);
+	plaster();
+}
+
+void
+adv(void)
+{
+	Tg *t;
+
+	tics++;
+	if(tics % tddd == 0){
+		div -= div0 / Δ0;
+		if(tics / tddd % mtdt == 0)
+			tgmax++;
+	}
+
+	for(t=tg->n; t != tg; t=t->n){
+		t->r++;
+		if(t->r <= rmax)
+			grow(t);
+		else if(t->r > rmax && t->r <= dmax)
+			shrink(t);
+		else if(t->r == dmax+1){
+			miss(t);
+			if(misses >= missmax){
+				done++;
+				score();
+			}
+		}else if(t->r > dmax+floatdt){
+			clr(t);
+			pop(t);
+		}
+	}
+	if(tics % newdt == 0 && ntg < tgmax)
+		spawn();
+}
+
+void
+dreset(void)
+{
+	Tg *t;
+
+	winxy = rectsubpt(insetrect(screen->r, rmax), screen->r.min);
+	if(Dx(winxy) < 3*dmax || Dy(winxy) < 3*dmax)
+		sysfatal("window too small");
+	freeimage(fb);
+	fb = eallocim(Rect(0,0,Dx(screen->r),Dy(screen->r)), 0, DNofill);
+	draw(fb, fb->r, col[CBACK], nil, ZP);
+	for(t=tg->n; t != tg; t=t->n){
+		if(t->r > dmax)
+			continue;
+		drw(t);
+	}
+	if(done)
+		score();
+	plaster();
+}
+
+void
+init(void)
+{
+	div = div0;
+	tics = 0;
+	hits = misses = misclicks = 0;
+	done = 0;
+}
+
+void
+usage(void)
+{
+	print("%s [-g] [-d newdt] [-h div0] [-i Δ0] [-m nmiss] [-o rmax] [-r tddd] [-t 1-5]\n", argv0);
+	threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	Rectangle d;
+	Rune r;
+	Mouse m;
+
+	ARGBEGIN{
+	case 'd':
+		newdt = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'g':
+		nogrow++;
+		break;
+	case 'h':
+		div0 = strtoll(EARGF(usage()), nil, 0);
+		if(div0 <= 0)
+			sysfatal("invalid divisor %lld", div0);
+		div0 = NSEC / div0;
+		break;
+	case 'i':
+		Δ0 = strtol(EARGF(usage()), nil, 0);
+		if(Δ0 <= 0)
+			sysfatal("invalid divisor divisor %d", Δ0);
+		break;
+	case 'm':
+		missmax = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'o':
+		rmax = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'r':
+		tddd = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 't':
+		bmask = 31 >> 5 - strtol(EARGF(usage()), nil, 0);
+		break;
+	default:
+		usage();
+	}ARGEND
+	dmax = rmax*2;
+
+	tg = &troot;
+	tg->n = tg->p = tg;
+	if(initdraw(nil, nil, "rfx") < 0)
+		sysfatal("initdraw: %r");
+	d = Rect(0,0,1,1);
+	col[CBACK] = display->black;
+	col[CTARG] = eallocim(d, 1, 0x990000ff);
+	col[CMISS] = eallocim(d, 1, 0x440000ff);
+	col[CTXT] = eallocim(d, 1, 0xbb4400ff);
+	dreset();
+
+	mctl = initmouse(nil, screen);
+	if(mctl == nil)
+		sysfatal("initmouse: %r");
+	kctl = initkeyboard(nil);
+	if(kctl == nil)
+		sysfatal("initkeyboard: %r");
+	ticc = chancreate(sizeof(int), 0);
+	if(ticc == nil)
+		sysfatal("chancreate: %r");
+	srand(time(nil));
+	init();
+	if(proccreate(tic, nil, 8192) < 0)
+		sysfatal("proccreate tproc: %r");
+
+	enum{ATIC, AMOUSE, ARESIZE, AKEY, AEND};
+	Alt a[AEND+1] = {
+		[ATIC] {ticc, nil, CHANRCV},
+		[AMOUSE] {mctl->c, &m, CHANRCV},
+		[ARESIZE] {mctl->resizec, nil, CHANRCV},
+		[AKEY] {kctl->c, &r, CHANRCV},
+		[AEND] {nil, nil, CHANEND}
+	};
+	for(;;){
+		switch(alt(a)){
+		case ATIC:
+			if(done)
+				break;
+			adv();
+			plaster();
+			break;
+		case AMOUSE:
+			if(!done && m.buttons & bmask && m.buttons & ~oldb)
+				check(m.buttons, m.xy);
+			oldb = m.buttons;
+			break;
+		case ARESIZE:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			dreset();
+			break;
+		case AKEY:
+			switch(r){
+			case 'n':
+				nuke();
+				init();
+				draw(fb, fb->r, col[CBACK], nil, ZP);
+				plaster();
+				break;
+			case 'q':
+			case Kdel:
+				nuke();
+				threadexitsall(nil);
+			}
+			break;
+		}
+	}
+}
--- /dev/null
+++ b/solt.c
@@ -1,0 +1,294 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+
+/* FIXME: shitty model, shitty implementation */
+
+enum{
+	NCARDS	= 52,
+	NFLAVOR	= 4,
+	NSTACKS	= 8,
+	NHAND	= NCARDS - ((NSTACKS+1)*(NSTACKS+2))/2 + 1
+};
+
+int c[NCARDS];
+
+typedef struct Card Card;
+struct Card{
+	int	v;
+	char	l[5];
+	Card*	n;
+};
+Card *d[NCARDS];
+Card *s[NSTACKS];
+Card *h[NHAND];
+
+Image *ncol;	/* string color */
+Image *selcol;	/* selection color */
+Point spt;	/* string pos */
+
+Mouse m;
+int resize;
+int kfd, mfd;
+
+
+void *
+emalloc(ulong n)
+{
+	void *b;
+
+	if((b = malloc(n)) == nil)
+		sysfatal("%s: %r", argv0);
+	return b;
+}
+
+void
+shuffle(void)
+{
+	int i, j, n;
+	int t[NCARDS];
+
+	srand(time(nil));
+	for(i = 0; i < NCARDS; i++)
+		t[i] = rand();
+	for(i = 0; i < NCARDS; i++){
+		n = 0;
+		for(j = 0; j < NCARDS; j++)
+			if(t[n] > t[j])
+				n = j;
+		c[i] = n;
+		t[n] = 0x10000;
+	}
+}
+
+char
+mkflavor(int n)
+{
+	switch(n % NFLAVOR){
+	case 0: return 'd';
+	case 1: return 'p';
+	case 2: return 'u';
+	case 3: return 'n';
+	}
+	return ' ';
+}
+
+void
+mkdeck(void)
+{
+	int i, v;
+
+	for(i = 0; i < NCARDS; i++){
+		d[i] = emalloc(sizeof **d);
+		v = c[i];
+		d[i]->v = v;
+		sprint(d[i]->l, "%d%c", 1 + v/NFLAVOR, mkflavor(v));
+		d[i]->n = nil;
+	}
+}
+
+void
+arrange(void)
+{
+	int i, j, n = 0;
+	Card *p;
+
+	for(i = 0; i < NSTACKS; i++)
+		s[i] = d[n++];
+	for(i = 0; i < NSTACKS; i++){
+		p = s[i];
+		for(j = NSTACKS-i-1; j < NSTACKS; j++)
+			p = p->n = d[n++];
+	}
+	for(i = 0; i < NHAND; i++)
+		h[i] = d[n++];
+}
+
+void
+drw(void)
+{
+	int i, n;
+	Card *p;
+	char b[10];
+	Point sxy = spt;
+
+	/* TODO: clear line */
+
+	for(i = 0; i < NSTACKS; i++){
+		p = s[i];
+		n = 0;
+		if(p == nil)
+			sprint(b, "  ()    ");
+		else{
+			while(p->n != nil){
+				n++;
+				p = p->n;
+			}
+			snprint(b, 10, "(%d)%s ", n, p->l);
+		}
+		string(screen, sxy, ncol, ZP, font, b);
+		sxy.x += 8 * font->width;
+	}
+	flushimage(display, 1);
+}
+
+void
+select(Point *p)
+{
+	/* TODO: selecting a card */
+	USED(p);
+}
+
+void
+kth(void *)
+{
+	int k;
+	char buf[256];
+	char *s;
+	Rune r;
+
+	if((kfd = open("/dev/kbd", OREAD)) < 0)
+		sysfatal("%s: open /dev/kbd: %r", argv0);
+	for(;;){
+		if(read(kfd, buf, sizeof(buf)-1) <= 0)
+			sysfatal("%s: read /dev/kbd: %r", argv0);
+		if(buf[0] == 'c' && utfrune(buf, Kdel))
+			threadexitsall(nil);
+		if(buf[0] != 'k' && buf[0] != 'K')
+			continue;
+
+		s = buf + 1;
+		k = 0;
+		while(*s != 0){
+			s += chartorune(&r, s);
+			switch(r){
+			case Kdel:
+				threadexitsall(nil);
+			}
+		}
+	}
+	/* TODO: make other threads exit cleanly? send to threadmain? */
+}
+
+void
+mth(void *)
+{
+	int n, nerr;
+	Mouse m;
+	char buf[1+5*12];
+
+	if((mfd = open("/dev/mouse", OREAD)) < 0)
+		sysfatal("open: %r");
+	memset(&m, 0, sizeof m);
+	nerr = 0;
+
+	for(;;){
+		n = read(mfd, buf, sizeof buf);
+		if(n != 1+4*12){
+			fprint(2, "mproc: bad count %d not 49: %r\n", n);
+			if(n < 0 || ++nerr > 10)
+				break;
+			continue;
+		}
+		nerr = 0;
+
+		switch(buf[0]){
+		case 'r':
+			resize = 1;
+			/* TODO: something */
+			break;
+		case 'm':
+			m.xy.x = atoi(buf+1+0*12);
+			m.xy.y = atoi(buf+1+1*12);
+			m.buttons = atoi(buf+1+2*12);
+			//m.msec = atoi(buf+1+3*12);
+			break;
+		}
+	}
+}
+
+void
+croak(void)
+{
+	Card **p = d;
+
+	close(kfd);
+	close(mfd);
+	while(p != d + NCARDS)
+		free(*p++);
+	freeimage(ncol);
+	freeimage(selcol);
+}
+
+void
+reset(void)
+{
+	draw(screen, screen->r, display->black, nil, ZP);
+	shuffle();
+	mkdeck();
+	arrange();
+	drw();
+}
+
+void
+init(void)
+{
+	/* TODO: why the difference between proc and thread? */
+
+	if(proccreate(kth, nil, mainstacksize) < 0)
+		sysfatal("%s threadcreate kproc: %r", argv0);
+	if(proccreate(mth, nil, mainstacksize) < 0)
+		sysfatal("%s threadcreate mproc: %r", argv0);
+
+	if(initdraw(nil, nil, "solt") < 0)
+		sysfatal("%s initdraw: %r", argv0);
+	ncol = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x551100ff);
+	if(ncol == nil)
+		sysfatal("%s allocimage: %r", argv0);
+	selcol = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x882200ff);
+	if(selcol == nil)
+		sysfatal("%s allocimage: %r", argv0);
+	print("selcol %p %p %d\n", selcol->display->bufp, selcol->display->buf, selcol->display->bufsize);
+	/* problem: font->width == 0 at this point? */
+	spt = Pt(screen->r.min.x + Dx(screen->r)/2 - 8*NSTACKS*4,
+		screen->r.min.y + Dy(screen->r)/2);
+
+	reset();
+	atexit(croak);
+}
+
+void
+usage(void)
+{
+	print("usage: %s\n", argv0);
+	threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND
+
+	/* TODO: playing the game */
+	/* TODO: hand, 4 finish spots, 4 off spots */
+	/* TODO: mouse menu (exit, save card, stack card, restart, new, ) */
+	/* TODO: moving shit around */
+	/* TODO: resize handling */
+	/* TODO: exit by communicating by channel */
+	/* TODO: calculate distance between card slots programatically */
+
+	init();
+	/* FIXME: do shit (setup channels) */
+	for(;;){
+		/*if(recv(kchan, nil) < 0)
+			threadexitsall("%s recv: %r");*/
+		sleep(100);
+	}
+}
--- /dev/null
+++ b/spd.c
@@ -1,0 +1,413 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <thread.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <cursor.h>
+
+enum{
+	KMAX = 24 * UTFmax,
+	DICTLEN = 32<<10,
+	TMAX = 32,
+};
+char dict[DICTLEN][KMAX];
+int ndic;
+char *df = "/lib/words";
+typedef struct Txt Txt;
+struct Txt{
+	Point;
+	char s[KMAX];
+	char *m;
+	int d;
+	Image *c;
+	Txt *p;
+	Txt *n;
+};
+Txt t0, *tl, *cur;
+
+vlong tics;
+int nt, words, miss, mistyp;
+enum{
+	NSEC = 1000000000
+};
+vlong div, div0 = NSEC/2;	/* tic duration (ns) */
+int dage = 16;
+int tddd = 64;	/* tic duration decrease delay (tics) */
+int newdt = 3;	/* target spawn delay (tics) */
+int missmax = 10;	/* max targets let go */
+long tm0;
+int done;
+
+enum{
+	CBACK,
+	CTXT,
+	CSTALE,
+	COLD,
+	CCUR,
+	CEND
+};
+Image *fb, *col[CEND];
+Point fs, wc;
+int dy;
+Mousectl *mc;
+Keyboardctl *kc;
+Channel *tc;
+
+Image *
+eallocim(Rectangle r, int repl, ulong col)
+{
+	Image *i;
+
+	i = allocimage(display, r, screen->chan, repl, col);
+	if(i == nil)
+		sysfatal("allocimage: %r");
+	return i;
+}
+
+void
+tic(void *)
+{
+	vlong t, dt, δ;
+
+	t = nsec();
+	dt = div/1000000;
+	for(;;){
+		if(dt >= 0){
+			sleep(dt);
+			if(nbsend(tc, nil) < 0)
+				break;
+		}
+		δ = div;
+		dt = (t + 2*δ - nsec()) / 1000000;
+		t += δ;
+	}
+}
+
+void
+pop(Txt *t)
+{
+	t->p->n = t->n;
+	t->n->p = t->p;
+	nt--;
+	free(t);
+	if(cur == t)
+		cur = nil;
+}
+
+void
+nuke(void)
+{
+	while(tl->n != tl)
+		pop(tl->n);
+}
+
+void
+drw(Txt *t, Image *c)
+{
+	if(c == nil)
+		c = t->c;
+	string(fb, *t, c, ZP, font, t->s);
+}
+
+void
+drwcur(void)
+{
+	char c;
+
+	c = *cur->m;
+	*cur->m = 0;
+	drw(cur, col[CCUR]);
+	*cur->m = c;
+}
+
+void
+spawn(void)
+{
+	Txt *t;
+
+	t = mallocz(sizeof *t, 1);
+	if(t == nil)
+		sysfatal("talloc: %r");
+	t->p = tl->p;
+	t->n = tl;
+	tl->p->n = t;
+	tl->p = t;
+	nt++;
+	sprint(t->s, dict[nrand(ndic)]);
+	t->c = col[CTXT];
+	t->y = nrand(dy);
+	drw(t, nil);
+}
+
+void
+plaster(void)
+{
+	draw(screen, screen->r, fb, nil, ZP);
+	flushimage(display, 1);
+}
+
+void
+score(void)
+{
+	Point o;
+	long t;
+	char s[72];
+
+	/* FIXME */
+	/* this wpm figure is bullshit */
+	t = (time(nil) - tm0) / 60;
+	sprint(s, "words %d misses %d mistypes %d in %lds",
+		words, miss, mistyp, t);
+	o = subpt(wc, divpt(stringsize(font, s), 2));
+	string(fb, o, col[CCUR], ZP, font, s);
+	plaster();
+}
+
+void
+adv(void)
+{
+	Txt *t;
+
+	tics++;
+	if(tics % tddd == 0)
+		div -= div0 / 16;
+
+	for(t=tl->n; t != tl; t=t->n){
+		t->d++;
+		drw(t, col[CBACK]);
+		t->x += fs.x;
+		if(t->d % dage == 0){
+			switch(t->d / dage){
+			case 1: t->c = col[CSTALE]; break;
+			case 2: t->c = col[COLD]; break;
+			case 3:
+				miss++;
+				if(miss >= missmax){
+					done++;
+					score();
+				}
+				pop(t);
+				continue;
+			}
+		}
+		drw(t, nil);
+	}
+	if(cur != nil)
+		drwcur();
+	if(tics % newdt == 0 && nt < TMAX)
+		spawn();
+	if(tl->n == tl){
+		spawn();
+		tics += newdt - tics % newdt;
+	}
+}
+
+int
+check(Rune k)
+{
+	Txt *t;
+
+	if(cur == nil){
+		for(t=tl->n; t != tl; t=t->n)
+			if(utfrune(t->s, k) == t->s){
+				t->m = t->s + runelen(k);
+				cur = t;
+				drwcur();
+				return 1;
+			}
+	}else if(utfrune(cur->m, k) == cur->m){
+		cur->m += runelen(k);
+		if(cur->m >= cur->s + strlen(cur->s)){
+			words++;
+			drw(cur, col[CBACK]);
+			pop(cur);
+			cur = nil;
+		}else
+			drwcur();
+		return 1;
+	}
+	mistyp++;
+	return 0;
+}
+
+void
+dreset(void)
+{
+	Txt *t;
+
+	dy = Dy(screen->r) - fs.y;
+	if(dy < 4 * fs.y || Dx(screen->r) < KMAX * fs.x / 8)
+		sysfatal("window too small");
+	wc = Pt(Dx(screen->r)/2, Dy(screen->r)/2);
+	freeimage(fb);
+	fb = eallocim(Rect(0,0,Dx(screen->r),Dy(screen->r)), 0, DNofill);
+	draw(fb, fb->r, col[CBACK], nil, ZP);
+	for(t=tl->n; t != tl; t=t->n)
+		drw(t, nil);
+	if(done)
+		score();
+	plaster();
+}
+
+void
+init(void)
+{
+	div = div0;
+	tics = 0;
+	words = miss = mistyp = 0;
+	tm0 = time(nil);
+	done = 0;
+	spawn();
+	plaster();
+}
+
+void
+menu(void)
+{
+	enum{NEW, QUIT};
+	static char *item[] = {
+		[NEW] "new",
+		[QUIT] "quit",
+		nil
+	};
+	static Menu m = {
+		item, nil, 0
+	};
+
+	switch(menuhit(3, mc, &m, nil)){
+	case NEW:
+		nuke();
+		init();
+		draw(fb, fb->r, col[CBACK], nil, ZP);
+		plaster();
+		break;
+	case QUIT:
+		nuke();
+		threadexitsall(nil);
+	}
+}
+
+void
+load(char *f)
+{
+	int n;
+	char *s, (*p)[KMAX];
+	Biobuf *bf;
+
+	bf = Bopen(f, OREAD);
+	if(bf == nil)
+		sysfatal("load: %r");
+	p = dict;
+	while(s = Brdline(bf, '\n'), s != nil && (char*)p < dict[nelem(dict)]){
+		n = Blinelen(bf);
+		if(n > sizeof *p)
+			n = sizeof *p;
+		memcpy(*p, s, n-1);
+		ndic++;
+		p++;
+	}
+	Bterm(bf);
+}
+
+void
+usage(void)
+{
+	print("%s [-d n] [-h hz] [-m n] [-o tics] [-r tics] [dict]\n", argv0);
+	threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	double d;
+	Rectangle r;
+	Rune k;
+	Mouse m;
+
+	ARGBEGIN{
+	case 'd':
+		newdt = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'h':
+		d = strtod(EARGF(usage()), nil);
+		if(d <= 0.0)
+			sysfatal("invalid divisor %f", d);
+		div0 = NSEC / d;
+		break;
+	case 'm':
+		missmax = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'o':
+		dage = strtol(EARGF(usage()), nil, 0);
+		break;
+	case 'r':
+		tddd = strtol(EARGF(usage()), nil, 0);
+		break;
+	default:
+		usage();
+	}ARGEND
+	if(argc > 0)
+		df = *argv;
+	load(df);
+
+	tl = &t0;
+	tl->n = tl->p = tl;
+
+	if(initdraw(nil, nil, "spd") < 0)
+		sysfatal("initdraw: %r");
+	fs = stringsize(font, "A");
+	r = Rect(0,0,1,1);
+	col[CBACK] = display->black;
+	col[CTXT] = eallocim(r, 1, 0xbb4400ff);
+	col[CSTALE] = eallocim(r, 1, 0x990000ff);
+	col[COLD] = eallocim(r, 1, 0x440000ff);
+	col[CCUR] = eallocim(r, 1, 0xdd9300ff);
+	dreset();
+
+	mc = initmouse(nil, screen);
+	if(mc == nil)
+		sysfatal("initmouse: %r");
+	kc = initkeyboard(nil);
+	if(kc == nil)
+		sysfatal("initkeyboard: %r");
+	tc = chancreate(sizeof(int), 0);
+	if(tc == nil)
+		sysfatal("chancreate: %r");
+	srand(time(nil));
+	init();
+	if(proccreate(tic, nil, 8192) < 0)
+		sysfatal("proccreate tproc: %r");
+
+	enum{ATIC, AMOUSE, ARESIZE, AKEY, AEND};
+	Alt a[AEND+1] = {
+		[ATIC] {tc, nil, CHANRCV},
+		[AMOUSE] {mc->c, &m, CHANRCV},
+		[ARESIZE] {mc->resizec, nil, CHANRCV},
+		[AKEY] {kc->c, &k, CHANRCV},
+		[AEND] {nil, nil, CHANEND}
+	};
+	for(;;)
+		switch(alt(a)){
+		case ATIC:
+			if(done)
+				break;
+			adv();
+			plaster();
+			break;
+		case AMOUSE:
+			if(m.buttons & 4)
+				menu();
+			break;
+		case ARESIZE:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("getwindow: %r");
+			dreset();
+			break;
+		case AKEY:
+			if(check(k))
+				plaster();
+			break;
+		}
+}
--- /dev/null
+++ b/tev.c
@@ -1,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+
+void
+mproc(void)
+{
+	int fd, n, nerr;
+	char buf[1+5*12];
+	Mouse m;
+
+	fd = open("/dev/mouse", ORDWR);
+	if(fd < 0)
+		sysfatal("open /dev/mouse: %r");
+	memset(&m, 0, sizeof m);
+	nerr = 0;
+	for(;;){
+		n = read(fd, buf, sizeof buf);
+		if(n != 1+4*12){
+			if(n < 0 || ++nerr > 10)
+				break;
+			continue;
+		}
+		nerr = 0;
+		switch(*buf){
+		case 'r':
+			print("[r]\n");
+			/* wet floor */
+		case 'm':
+			m.xy.x = atoi(buf+1+0*12);
+			m.xy.y = atoi(buf+1+1*12);
+			m.buttons = atoi(buf+1+2*12);
+			m.msec = atoi(buf+1+3*12);
+			print("[m] %d,%d %d %lud\n", m.xy.x, m.xy.y, m.buttons, m.msec);
+			break;
+		}
+	}
+}
+
+void
+main(int, char **)
+{
+	int n, fd, mpid;
+	char buf[256], *s;
+	Rune r;
+
+	mpid = fork();
+	switch(mpid){
+	case -1:
+		sysfatal("fork: %r");
+	case 0:
+		mproc();
+		exits(nil);
+	}
+	fd = open("/dev/kbd", OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	for(;;){
+		if(buf[0] != 0){
+			n = strlen(buf)+1;
+			memmove(buf, buf+n, sizeof(buf)-n);
+		}
+		if(buf[0] == 0){
+			print("\n");
+			n = read(fd, buf, sizeof(buf)-1);
+			if(n <= 0)
+				break;
+			buf[n-1] = 0;
+			buf[n] = 0;
+		}
+		s = buf+1;
+		switch(buf[0]){
+		case 'c':
+			chartorune(&r, s);
+			print("[c] %C %#ux ", r, r);
+			if(utfrune(buf, Kdel))
+				goto done;
+			break;
+		case 'k':
+		case 'K':
+			print("[%c] ", buf[0]);
+			while(*s){
+				s += chartorune(&r, s);
+				print("%C %#ux ", r, r);
+				if(r == Kdel)
+					goto done;
+			}
+			break;
+		default:
+			print("unknown message %c\n", *buf);
+		}
+	}
+done:
+	postnote(PNPROC, mpid, "shutdown");
+	exits(nil);
+}
--- /dev/null
+++ b/wstat.c
@@ -1,0 +1,31 @@
+#include <u.h>
+#include <libc.h>
+
+#define usage() (sysfatal("usage: no."))
+
+void
+main(int argc, char **argv)
+{
+	int fd;
+	Dir d;
+
+	nulldir(&d);
+	ARGBEGIN{
+	case 'g': d.gid = EARGF(usage()); break;
+	case 'l': d.length = strtoll(EARGF(usage()), nil, 0); break;
+	case 'm': d.mtime = strtoll(EARGF(usage()), nil, 0); break;
+	case 'n': d.name = EARGF(usage()); break;
+	case 'p': d.mode = strtoll(EARGF(usage()), nil, 0); break;
+	case 'u': d.uid = EARGF(usage()); break;
+	default: usage();
+	}ARGEND
+	if(*argv == nil)
+		usage();
+	fd = open(*argv, OWRITE);
+	if(fd < 0)
+		sysfatal("open: %r");
+	if(dirfwstat(fd, &d) < 0)
+		sysfatal("dirfwstat: %r");
+	close(fd);
+	exits(nil);
+}