shithub: puzzles

ref: dd3a0f224d9472f4969c425980218e550d357eb0
dir: /plan9.c/

View raw version
#include <npe.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>

/********************

usage and controls:

n - new game
u - undo
r - redo
s - solve

Everything else is forwarded to the game.

If the game doesn't redraw properly, try pressing the "game"
button.

*********************/

#undef PI
#include "puzzles.h"

#ifdef COMBINED
#error Plan 9 should not be COMBINED
#endif

//#define PROFILE

int dolog = 0;
int logfd = -1;
int logpipe[2];
#define LOG(c) { if (dolog) fprint(logpipe[1], "%s\n", c); }
static void
Log(char *fmt, ...)
{
	va_list arg;
	if (!dolog)
		return;
	va_start(arg, fmt);
	vfprint(logfd, fmt, arg);
	va_end(arg);
}

typedef struct Ft Ft;
struct Ft {
	int type;
	int size;
	Font *font;
};

struct frontend {
	midend *me;
	Image *background;
	Image **colors;
	int ncolors;
	Point ZP;
	Rectangle rect;
	Controlset *cs;
	Channel *c;
	Channel *settingschan;
	int showframe;
	int timeractive;
	Ft *fonts;
	int nfonts;
};

struct blitter {
	Image *blimg;
	Point pos;
};

frontend *fe = nil;

struct proftimes {
	long draw;
} ptimes;

enum {
	GAME = 0,
	SETTINGS = 1,
};

static Font*
findfontfile(int type, int size)
{
	Font *f;
	char buf[128];
	char *fixed = "/lib/font/bit/lucidasans/typeunicode.%d.font";
	char *variable = "/lib/font/bit/lucidasans/unicode.%d.font";
	
	/* find font based on size */
	while (size > 0) {
		snprint(buf, sizeof(buf), type == FONT_FIXED ? fixed : variable, size);
		if (access(buf, OREAD)) {
			f = openfont(display, buf);
			if (f == nil) {
				Log("error opening font file %s\n", buf);
				return font;
			}
			return f;
		}
	}
	Log("Unable to find proper font. Falling back to default font\n");
	return font;
}

static Font*
findfont(frontend *fe, int type, int size)
{
	Ft *n;
	for (int i = 0; i < fe->nfonts; i++) {
		if (fe->fonts[i].type == type && fe->fonts[i].size == size)
			return fe->fonts[i].font;
	}
	fe->nfonts++;
	fe->fonts = realloc(fe->fonts, sizeof(Ft) * fe->nfonts);
	assert(fe->fonts);
	n = &fe->fonts[fe->nfonts-1];
	n->type = type;
	n->size = size;
	n->font = findfontfile(type, size);
	return n->font;
}

void
frontend_default_colour(frontend *, float *output)
{
	output[0] = .9;
	output[1] = .9;
	output[2] = .9;
}

void
get_random_seed(void **randseed, int *randseedsize)
{
	long *t = malloc(sizeof(long));
	assert(t);
	time(t);
	*randseed = (void*)t;
	*randseedsize = sizeof(long);
}

void
deactivate_timer(frontend *fe)
{
	fe->timeractive = 0;
}

void activate_timer(frontend *fe)
{
	fe->timeractive = 1;
}

void fatal(const char *fmt, ...)
{
    va_list ap;

    fprint(2, "fatal error: ");

    va_start(ap, fmt);
    vfprint(2, fmt, ap);
    va_end(ap);

    fprint(2, "\n");
    sysfatal("error");
}

#ifdef DEBUGGING
void
debug_printf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vfprint(1, fmt, ap);
    va_end(ap);
}
#endif

static void
usage(void)
{
	fprint(2, "usage: %s [-hl]\n", argv0);
	exits(nil);
}

static void
p9_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int color, const char *text)
{
	Font *f;
	Point p, size;
	frontend *fe = (frontend*)handle;
	
	f = findfont(fe, fonttype, fontsize);
	
	p.x = x;
	p.y = y;
	size = stringsize(f, text);
	
	if (align & ALIGN_VCENTRE)
		p.y -= size.y / 2;
	else
		p.y -= size.y;
	
	if (align & ALIGN_HCENTRE)
		p.x -= size.x / 2;
	else if (align & ALIGN_HRIGHT)
		p.x -= size.x;
	
	string(screen, addpt(p, fe->ZP), fe->colors[color], ZP, f, text);
}

static void
p9_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
	frontend *fe = (frontend*)handle;
	draw(screen, rectaddpt(Rect(x, y, x+w, y+h), fe->ZP), fe->colors[color], nil, ZP);
}

static void
p9_draw_line(void *handle, int x1, int y1, int x2, int y2, int color)
{
	frontend *fe = (frontend*)handle;
	line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, 0, fe->colors[color], ZP);
}

static void
p9_draw_thick_line(void *handle, float thickness, float x1, float y1, float x2, float y2, int color)
{
	frontend *fe = (frontend*)handle;
	line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, thickness-1, fe->colors[color], ZP);
}

static void
p9_draw_poly(void *handle, const int *coords, int npoints, int fillcolor, int outlinecolor)
{
	Point *points;
	frontend *fe = (frontend*)handle;
	
	points = malloc((npoints+1) * sizeof(Point));
	for (int i = 0; i < npoints; i++) {
		points[i].x = coords[i*2+0] + fe->ZP.x;
		points[i].y = coords[i*2+1] + fe->ZP.y;
	}
	points[npoints] = points[0];
	
	if (fillcolor >= 0)
		fillpoly(screen, points, npoints, 0, fe->colors[fillcolor], ZP);
	if (outlinecolor >= 0)
		poly(screen, points, npoints+1, Endsquare, Endsquare, 0, fe->colors[outlinecolor], ZP);
	
	free(points);
}

static void
p9_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor)
{
	frontend *fe = (frontend*)handle;
	Point c = addpt(Pt(cx, cy), fe->ZP);
	fillellipse(screen, c, radius, radius, fe->colors[fillcolor], ZP);
	ellipse(screen, c, radius, radius, 0, fe->colors[outlinecolor], ZP);
}

static void
p9_draw_update(void *handle, int x, int y, int w, int h)
{
	USED(handle, x, y, w, h);
	//frontend *fe = (frontend*)handle;
}

static void
p9_clip(void *handle, int x, int y, int w, int h)
{
	frontend *fe = (frontend*)handle;
	Rectangle r = rectaddpt(Rect(x, y, x + w, y + h), fe->ZP);
	replclipr(screen, 0, r);
}

static void
p9_unclip(void *handle)
{
	USED(handle);
	replclipr(screen, 0, screen->r);
}

long drawtime;

static void
p9_start_draw(void *handle)
{
	USED(handle);
	LOG("start_draw");
#ifdef PROFILE
	drawtime = times(nil);
#endif
}

static void
p9_end_draw(void *handle)
{
	USED(handle);
	flushimage(display, 1);
	LOG("end_draw");
#ifdef PROFILE
	ptimes.draw = times(nil) - drawtime;
#endif
}

static void
p9_status_bar(void *handle, const char *text)
{
	frontend *fe = (frontend*)handle;
	
	chanprint(fe->cs->ctl, "l_status value %q", text);
}

static blitter*
p9_blitter_new(void *handle, int w, int h)
{
	blitter *bl;
	USED(handle);
	bl = malloc(sizeof(blitter));
	bl->blimg = allocimage(display, Rect(0, 0, w, h), screen->chan, 0, 0);
	return bl;
}

static void
p9_blitter_free(void *handle, blitter *bl)
{
	USED(handle);
	freeimage(bl->blimg);
	free(bl);
}

static void
p9_blitter_save(void *handle, blitter *bl, int x, int y)
{
	USED(handle);
	bl->pos = Pt(x, y);
	draw(bl->blimg, Rect(x, y, x + bl->blimg->r.max.x, y + bl->blimg->r.max.y), screen, nil, Pt(x, y));
}

static void
p9_blitter_load(void *handle, blitter *bl, int x, int y)
{
	USED(handle);
	if (x == BLITTER_FROMSAVED)
		x = bl->pos.x;
	if (y == BLITTER_FROMSAVED)
		y = bl->pos.y;
	draw(screen, Rect(x, y, x + bl->blimg->r.max.x, y + bl->blimg->r.max.y), bl->blimg, nil, Pt(x, y));
}

static const drawing_api p9_drawing = {
	p9_draw_text,
	p9_draw_rect,
	p9_draw_line,
	p9_draw_poly,
	p9_draw_circle,
	p9_draw_update,
	p9_clip,
	p9_unclip,
	p9_start_draw,
	p9_end_draw,
	p9_status_bar,
	p9_blitter_new,
	p9_blitter_free,
	p9_blitter_save,
	p9_blitter_load,
	nil, nil, nil, nil, nil, nil, nil, nil, /* {begin,end}_{doc,page,puzzle}, line_width, line_dotted */
	nil, /* text_fallback */
#ifdef NO_THICK_LINE
	nil,
#else
	p9_draw_thick_line,
#endif
};

static int
rgb2col(int r, int g, int b)
{
	return (r<<24) | (g<<16) | (b<<8) | 0xFF;
}

static frontend*
new_window(void)
{
	frontend *fe;
	
	fe = mallocz(sizeof(frontend), 1);
	if (!fe)
		sysfatal("error: out of memory!");
	
	fe->me = midend_new(fe, &thegame, &p9_drawing, fe);
	
	return fe;
}

static void
initui(Controlset *cs, Channel *c)
{
	Control *b_game, *b_settings;
	Point p;
	
	createrow(cs, "rowmain");
	
	b_game = createtextbutton(cs, "b_game");
	p = stringsize(font, "game");
	chanprint(cs->ctl, "b_game border 1");
	chanprint(cs->ctl, "b_game align center");
	chanprint(cs->ctl, "b_game text game");
	chanprint(cs->ctl, "b_game size %d %d 500 %d", p.x, p.y, p.y);
	controlwire(b_game, "event", c);
	
	b_settings = createtextbutton(cs, "b_settings");
	p = stringsize(font, "settings");
	chanprint(cs->ctl, "b_settings border 1");
	chanprint(cs->ctl, "b_settings align center");
	chanprint(cs->ctl, "b_settings text settings");
	chanprint(cs->ctl, "b_settings size %d %d 500 %d", p.x, p.y, p.y);
	createcolumn(cs, "c_settings");
	chanprint(cs->ctl, "c_settings hide");
	controlwire(b_settings, "event", c);
	
	createlabel(cs, "l_status");
	
	chanprint(cs->ctl, "rowmain add b_game\nrowmain add b_settings");
	
	activate(b_game);
	activate(b_settings);
}

static void
initfe(frontend *fe, Mousectl *mousectl)
{
	float *colors;
	int ncolors;
	int r, g, b;
	float bgcol[3];
	Channel *c, *d;
	
	frontend_default_colour(fe, bgcol);
	fe->background = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(bgcol[0]*255., bgcol[1]*255., bgcol[2]*255.));
	
	colors = midend_colours(fe->me, &ncolors);
	fe->colors = mallocz(ncolors * sizeof(Image*), 1);
	for (int i = 0; i < ncolors; i++) {
		r = colors[i*3+0] * 255.;
		g = colors[i*3+1] * 255.;
		b = colors[i*3+2] * 255.;
		fe->colors[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(r, g, b));
	}
	free(colors);
	
	fe->settingschan = chancreate(sizeof(char*), 0);
	c = chancreate(sizeof(Mouse), 0);
	d = chancreate(sizeof(Rune), 0);
	fe->cs = newcontrolset(screen, d, c, mousectl->resizec);
	fe->c = chancreate(sizeof(char*), 0);
	ctldeletequits = 1;
	namectlimage(display->black, "i_black");
	namectlimage(display->white, "i_white");
	namectlimage(fe->background, "background");
	initui(fe->cs, fe->c);
}

int windowset = 0;

static Point
resize(int *resizenop)
{
	int x, y, fd;
	x = Dx(screen->r);
	y = Dy(screen->r) - 2*font->height;
	
	midend_size(fe->me, &x, &y, 1, 1.);
	
	// to test
	if (0 && !windowset) {
		fd = open("/dev/wctl", OWRITE);
		if (fd >= 0) {
			fprint(fd, "resize -dx %d -dy %d\n", x+2, y + 2*font->height);
			close(fd);
			windowset = 1;
		}
	} else
		windowset = 0;
	
	/* do not resize if we're waiting for the window resize */
	*resizenop = windowset;
	return Pt(x, y);
}

void
resizecontrolset(Controlset *cs)
{
	Rectangle rmenu, rarea, sarea;
	int resizenop;
	Point newsize;
	Point offset;
	
	if (getwindow(display, Refnone) < 0) {
		sysfatal("resize failed: %r");
	}
	
	rmenu = screen->r;
	rmenu.max.y = rmenu.min.y + font->height;
	rarea = screen->r;
	rarea.min.y = rmenu.min.y + font->height;
	rarea.max.y = rarea.max.y - font->height;
	sarea = screen->r;
	sarea.min.y = sarea.max.y - font->height;
	
	newsize = resize(&resizenop);
	if (0 && resizenop)
		return;
	draw(screen, screen->r, fe->background, nil, ZP);
	
	/* calculate offset to place game area in the center */
	offset = Pt(Dx(rarea), Dy(rarea)); // size of total game area
	offset = subpt(divpt(addpt(offset, newsize), 2), newsize);
	fe->rect = rectaddpt(rarea, offset);
	fe->ZP = fe->rect.min;
	
	chanprint(cs->ctl, "rowmain rect %R\nrowmain show", rmenu);
	chanprint(cs->ctl, "l_status rect %R\nl_status show", sarea);
	
	switch (fe->showframe) {
	case GAME:
		chanprint(cs->ctl, "c_settings hide");
		midend_force_redraw(fe->me);
		flushimage(display, 1);
		break;
	case SETTINGS:
		chanprint(cs->ctl, "c_settings rect %R\nc_settings reveal\nc_settings show", rarea);
		break;
	}
	
	LOG("resizecontrolset");
}

typedef struct Option Option;
struct Option {
	char *name;
	Control *kctl;
	Control *vctl;
};

Option options[32];

static Option*
findoption(char *name)
{
	Option *o;
	for (o = options; o->name; o++)
		if (strcmp(o->name, name) == 0)
			return o;
	return nil;
}

static Option*
findoptionname(char *ctl)
{
	Option *o;
	for (o = options; o->name; o++)
		if (strcmp(o->vctl->name, ctl) == 0)
			return o;
	return nil;
}

static void
clearoptions(void)
{
	for (Option *o = options; o->name; o++) {
		if (o->name == nil)
			return;
		free(o->name);
		chanprint(fe->cs->ctl, "c_settings remove %q", o->kctl->name);
		chanprint(fe->cs->ctl, "c_settings remove %q", o->vctl->name);
		deactivate(o->vctl);
		closecontrol(o->kctl);
		closecontrol(o->vctl);
		o->kctl = o->vctl = nil;
		o->name = nil;
	}
}

static void
addoption(config_item *cfg)
{
	char buf[8];
	Control *label, *entry;
	int last = 0;
	Option *o;
	
	for (o = options; o->name; o++) {
		last++;
	}
	
	o->name = strdup(cfg->name);
	
	snprint(buf, sizeof(buf), "l_%d", last);
	label = createlabel(fe->cs, buf);
	o->kctl = label;
	chanprint(fe->cs->ctl, "%q value %q", buf, cfg->name);
	chanprint(fe->cs->ctl, "%q align center", buf);
	chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height*2, font->height*2);
	chanprint(fe->cs->ctl, "c_settings add %q", buf);
	
	snprint(buf, sizeof(buf), "e_%d", last);
	
	entry = nil;
	switch (cfg->type) {
	case C_STRING:
		entry = createentry(fe->cs, buf);
		chanprint(fe->cs->ctl, "%q border 1", buf);
		chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height, font->height);
		chanprint(fe->cs->ctl, "%q value %q", buf, cfg->u.string.sval);
		chanprint(fe->cs->ctl, "%q align center", buf);
		controlwire(entry, "data", fe->settingschan);
		break;
	case C_BOOLEAN:
		if (!entry)
			entry = createtextbutton(fe->cs, buf);
		chanprint(fe->cs->ctl, "%q border 1", buf);
		chanprint(fe->cs->ctl, "%q textcolor black\n%q pressedtextcolor black", buf, buf);
		chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height, font->height);
		chanprint(fe->cs->ctl, "%q value %d", buf, cfg->u.boolean.bval);
		chanprint(fe->cs->ctl, "%q text %s", buf, cfg->u.boolean.bval ? "yes" : "no");
		chanprint(fe->cs->ctl, "%q align center", buf);
		chanprint(fe->cs->ctl, "%q mask transparent", buf);
		controlwire(entry, "event", fe->settingschan);
		break;
	case C_CHOICES:
		// todo
		break;
	}
	o->vctl = entry;
	if (entry) {
		activate(entry);
		chanprint(fe->cs->ctl, "c_settings add %q", buf);
	}
}

int configcats[] = { CFG_SETTINGS, /* CFG_SEED, */ CFG_PREFS };
config_item *configs[] = { nil, /* nil, */ nil };

static void
loadoptions(void)
{
	char *t;
	config_item *cfg;
	Control *c;
	
	/* assumptions: if b_savecfg exists, then l_cfginfo also exists */
	
	c = controlcalled("b_savecfg");
	/* if already set up, remove from widget to add back later */
	if (c) {
		chanprint(fe->cs->ctl, "c_settings remove l_cfginfo");
		chanprint(fe->cs->ctl, "c_settings remove b_savecfg");
	}
	
	clearoptions();
	
	for (int i = 0; i < nelem(configcats); i++) {
		cfg = midend_get_config(fe->me, configcats[i], &t);
		if (configs[i]) {
			//free_cfg(configs[i]);
			configs[i] = nil;
		}
		configs[i] = cfg;
		
		for (; cfg && cfg->type != C_END; cfg++) {
			addoption(cfg);
		}
	}
	
	/* if already set up, early return */
	if (c) {
		goto Addbuttons;
	}
	
	createlabel(fe->cs, "l_cfginfo");
	chanprint(fe->cs->ctl, "l_cfginfo align centerleft");
	chanprint(fe->cs->ctl, "l_cfginfo size 50 %d 500 %d", font->height, font->height);
	
	c = createtextbutton(fe->cs, "b_savecfg");
	chanprint(fe->cs->ctl, "b_savecfg text Save");
	chanprint(fe->cs->ctl, "b_savecfg border 1");
	chanprint(fe->cs->ctl, "b_savecfg align center");
	chanprint(fe->cs->ctl, "b_savecfg size 50 %d 100 %d", font->height, font->height);
	controlwire(c, "event", fe->c);
	activate(c);

Addbuttons:
	chanprint(fe->cs->ctl, "c_settings add l_cfginfo");
	chanprint(fe->cs->ctl, "c_settings add b_savecfg");
}

static void setoption(char *name, char *value, config_item *conf);

static void
readoptions(void)
{
	char *val;
	config_item *cfg;
	Option *o;
	
	for (int i = 0; i < nelem(configcats); i++) {
		for (cfg = configs[i]; cfg && cfg->type != C_END; cfg++) {
			if (cfg->type != C_STRING)
				continue;
			o = findoption(cfg->name);
			if (!o || !o->vctl)
				continue;
			chanprint(fe->cs->ctl, "%q data", o->vctl->name);
			val = recvp(fe->settingschan);
			setoption(cfg->name, val, cfg);
		}
	}
}

static int
saveoptions(void)
{
	char *s;
	int r = 1;
	readoptions();
	for (int i = 0; i < nelem(configcats); i++) {
		s = midend_set_config(fe->me, configcats[i], configs[i]);
		if (s) {
			chanprint(fe->cs->ctl, "l_cfginfo value %q", s);
			r = 0;
		}
		free_cfg(configs[i]);
		configs[i] = nil;
	}
	loadoptions();
	LOG("saved options");
	return r;
}

static void
setoption(char *name, char *value, config_item *conf)
{
	int n;
	config_item *cfg;
	Option *o;
	
	if (conf) {
		cfg = conf;
		goto Found;
	}
	for (int i = 0; i < nelem(configs); i++) {
		for (cfg = configs[i]; cfg && cfg->type != C_END; cfg++) {
			if (strcmp(cfg->name, name) == 0)
				goto Found;
		}
	}
	return;

Found:
	Log("Set %q to %q\n", cfg->name, value);
	switch (cfg->type) {
	case C_STRING:
		cfg->u.string.sval = value;
		break;
	case C_BOOLEAN:
		n = atoi(value) ? 1 : 0;
		cfg->u.boolean.bval = n;
		o = findoption(name);
		chanprint(fe->cs->ctl, "%q value %d", o->vctl->name, n);
		chanprint(fe->cs->ctl, "%q text %q", o->vctl->name, n ? "yes" : "no");
		break;
	case C_CHOICES:
		// todo
		break;
	}
}

static void
showframe(int frame)
{
	fe->showframe = frame;
	resizecontrolset(fe->cs);
	LOG("showframe");
}

static int
keyev(Rune k)
{
	if (fe->showframe != GAME) {
		send(fe->cs->kbdc, &k);
		return 0;
	}
	
	switch (k) {
	case 'q':
	case 127:
		return 1; /* return 1 to quit */
	case 'n':
		midend_process_key(fe->me, 0, 0, UI_NEWGAME);
		break;
	case 'u':
		midend_process_key(fe->me, 0, 0, UI_UNDO);
		break;
	case 'r':
		midend_process_key(fe->me, 0, 0, UI_REDO);
		break;
	case 's':
		midend_process_key(fe->me, 0, 0, UI_SOLVE);
		break;
	default:
		if (midend_process_key(fe->me, 0, 0, k) == PKR_QUIT)
			return 1;
	}
	return 0;
}

static void
tick(float delta)
{
#ifdef PROFILE
	char msg[128];
#endif
	
	if (fe->timeractive) {
		midend_timer(fe->me, delta);
	}
	
#ifdef PROFILE
	snprint(msg, 128, "draw: %ld", ptimes.draw);
	chanprint(fe->cs->ctl, "l_status value %q", msg);
#endif
}

typedef struct Tickinfo Tickinfo;
struct Tickinfo {
	long totaltime;
	long lasttick;
	float delta;
};

static void
timerproc(void *v)
{
	Channel *c;
	Tickinfo ti;
	long newtime;
	
	c = v;
	ti.totaltime = times(nil);
	
	for (;;) {
		sleep(20);
		newtime = times(nil);
		ti.delta = (newtime - ti.totaltime) / 1000.;
		ti.totaltime = newtime;
		send(c, &ti.delta);
	}
}

static void
processmouse(Mouse *m, int *lm)
{
	int x, y, r;
	
	if (fe->showframe != GAME)
		goto Ctrl;
	
	if (!ptinrect(m->xy, fe->rect))
		goto Ctrl;
	
	x = m->xy.x - fe->rect.min.x;
	y = m->xy.y - fe->rect.min.y;
	r = -1;
	
	if ( ((*lm)&1) && !(m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_RELEASE);
	if ( ((*lm)&1) &&  (m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_DRAG);
	if (!((*lm)&1) &&  (m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_BUTTON);
	if ( ((*lm)&2) && !(m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_RELEASE);
	if ( ((*lm)&2) &&  (m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_DRAG);
	if (!((*lm)&2) &&  (m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_BUTTON);
	if ( ((*lm)&4) && !(m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_RELEASE);
	if ( ((*lm)&4) &&  (m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_DRAG);
	if (!((*lm)&4) &&  (m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_BUTTON);
	if (r >= 0) {
	}
	*lm = m->buttons;

	return;

Ctrl:
	send(fe->cs->mousec, m);
}

static void
doexit(void)
{
	if (dolog) {
		close(logfd);
	}
}

void
threadmain(int argc, char **argv)
{
	int lastmouse;
	char *s, *args[6];
	char *wintitle;
	float delta;
	Mousectl *mousectl;
	Mouse m;
	Keyboardctl *keyboardctl;
	Rune rune;
	Alt a[] = {
		{ nil, &s, CHANRCV },
		{ nil, &delta, CHANRCV },
		{ nil, &m, CHANRCV },
		{ nil, &rune, CHANRCV },
		{ nil, &s, CHANRCV },
		{ nil, nil, CHANEND },
	};
	
	fe = new_window();
	
	wintitle = nil;
	midend_get_config(fe->me, CFG_SETTINGS, &wintitle);
	
	ARGBEGIN{
	case 'h':
		usage();
		break;
	case 'l':
		dolog++;
		break;
	}ARGEND;
	
	atexit(doexit);
	
	if (dolog) {
		pipe(logpipe);
		logfd = create("/srv/puzzles", OWRITE|ORCLOSE, 0666);
		if (logfd < 0)
			sysfatal("error opening log file /srv/puzzles: %r");
		fprint(logfd, "%d", logpipe[0]);
		close(logpipe[0]);
		LOG(thegame.name);
	}
	
	if (initdraw(nil, nil, thegame.name) < 0) {
		sysfatal("initdraw failed: %r");
	}
	
	mousectl = initmouse(nil, screen);
	keyboardctl = initkeyboard(nil);
	
	initcontrols();
	
	initfe(fe, mousectl);
	loadoptions();
	midend_new_game(fe->me);
	resizecontrolset(fe->cs);
	
	a[0].c = fe->c;
	a[1].c = chancreate(sizeof(float), 0);
	a[2].c = mousectl->c;
	a[3].c = keyboardctl->c;
	a[4].c = fe->settingschan;
	
	proccreate(timerproc, a[1].c, 4096);
	
	for (;;) {
		switch (alt(a)) {
		case 0: /* libcontrol event channel */
			Log("Control: %s\n", s);
			tokenize(s, args, nelem(args));
			
			if (strcmp(args[0], "b_game:") == 0) {
				showframe(GAME);
				chanprint(fe->cs->ctl, "b_game value 0");
			} else
			if (strcmp(args[0], "b_settings:") == 0) {
				showframe(SETTINGS);
				chanprint(fe->cs->ctl, "b_settings value 0");
			} else
			if (strcmp(args[0], "b_savecfg:") == 0) {
				chanprint(fe->cs->ctl, "b_savecfg value 0");
				if (saveoptions()) {
					midend_new_game(fe->me);
					showframe(GAME);
				}
			} else {
				Log("unknown command: %s\n", args[0]);
			}
			break;
		case 1: /* timer */
			tick(delta);
			break;
		case 2: /* mouse */
			processmouse(&m, &lastmouse);
			break;
		case 3: /* keyboard */
			if (keyev(rune))
				goto Out;
			break;
		case 4: /* settings */
			Log("Setting: %s\n", s);
			tokenize(s, args, nelem(args));
			args[0][strlen(args[0])-1] = 0; /* get rid of sender ":" */
			setoption(findoptionname(args[0])->name, args[2], nil);
			break;
		}
	}
Out:
	threadexitsall(nil);
}