shithub: lola

Download patch

ref: 386ed6ba96647167e1a230033b02398f1d2d7a45
parent: fb7cc13fe8c7e198e6130f973448b13ac8614470
author: aap <aap@papnet.eu>
date: Mon Jul 22 17:58:35 EDT 2024

major additions:
window decorations
virtual desktops
themes

--- /dev/null
+++ b/README
@@ -1,0 +1,41 @@
+# lola
+
+Lola is the attempt at a new window system. Its architecture is based
+on that of rio, and it borrows some of its code, but it is not a
+modified rio.
+
+The first milestone was to reach something like feature parity with rio.
+The goals were:
+
+- use lib9p instead of implementing the 9p server from scratch
+	-> 9p no longer in separate proc, easier synchronization
+- de-couple text handling from windows
+	-> text.c
+- cleaner code, make it more hackable
+
+
+After that had been more or less achieved some new features were implemented:
+
+- window decorations including title bars
+	- various styles:
+	  - flat style (extremely minimal)
+	  - simple style (TODO: maybe rename this...)
+	  - windows 3.x
+	  - windows 95/nt4
+	- (no)title, (no)border wctl messages
+- support for different color schemes
+	- uses named devdraw images
+- virtual desktops
+	- 1-2 chord on desktop opens screen menu
+	- screenoffset wctl message
+	- (no)sticky wctl message
+- select window with mouse by reading from `pick' file
+- misc
+	- 1-3 chord on desktop opens secondary menu
+
+# Building
+
+The window decoration style is set by linking in the relevant file,
+so edit the `mkfile' to try different styles.
+The options are: flat.c, simple.c, win3.c, win95.c
+
--- a/TODO
+++ /dev/null
@@ -1,20 +1,0 @@
-rethink resizing and repainting
-rethink hiding/unhiding
-check for bad rects (what's left here?)
-top/bottom/current seems to work a bit different in rio
-make sure there are no deadlocks
-...
-
-ideas:
-	case-insensitive 'look'
-	virtual screen (like fvwm)
-	cursor movement
-	decorations (at least make them possible)
-	tabbed window
-
-problems:
-	Borderwidth hardcoded in gengetwindow
-	originwindow doesn't work with gengetwindow
-	non-origin screen breaks samterm scrollbars
-	raw mode where?
-	initkeyboard with /dev/kbd support (also fix leaks in old code)
--- a/data.c
+++ b/data.c
@@ -171,23 +171,63 @@
 	return allocimage(display, Rect(0,0,1,1), screen->chan, 1, col);
 }
 
+Image*
+getcolor(char *name, ulong col)
+{
+	Image *img;
+
+	name = smprint("th_%s", name);
+
+	img = namedimage(display, name);
+	if(img == nil){
+		img = mkcolor(col);
+		if(nameimage(img, name, 1) == 0)
+			panic("couldn't name image\n");
+	}
+	free(name);
+	return img;
+}
+
+Image *pal[8];
+
+Image*
+mkicon(char *px, int w, int h)
+{
+	int i, j, idx;
+	Image *img;
+
+	img = allocimage(display, Rect(0,0,w,h), RGBA32, 1, 0x00000000);
+	for(i = 0; i < h; i++)
+		for(j = 0; j < w; j++) {
+			idx = px[i*w + j];
+			if(idx)
+				draw(img, Rect(j,i,j+1,i+1), pal[idx], nil, ZP);
+		}
+	return img;
+}
+
 void
 initdata(void)
 {
-	background = mkcolor(0x777777FF);
-	colors[BACK] = mkcolor(0xFFFFFFFF);
-	colors[HIGH] = mkcolor(0xCCCCCCFF);
-	colors[BORD] = mkcolor(0x999999FF);
-	colors[TEXT] = mkcolor(0x000000FF);
-	colors[HTEXT] = mkcolor(0x000000FF);
+	background = getcolor("background", 0x777777FF);
+	colors[BACK] = getcolor("back", 0xFFFFFFFF);
+	colors[HIGH] = getcolor("high", 0xCCCCCCFF);
+	colors[BORD] = getcolor("bord", 0x999999FF);
+	colors[TEXT] = getcolor("text", 0x000000FF);
+	colors[HTEXT] = getcolor("htext", 0x000000FF);
+	colors[PALETEXT] = getcolor("paletext", 0x666666FF);
+	colors[HOLDTEXT] = getcolor("holdtext", DMedblue);
+	colors[PALEHOLDTEXT] = getcolor("paleholdtext", DGreyblue);
 
-	colors[TITLE] = mkcolor(DGreygreen);
-	colors[LTITLE] = mkcolor(DPalegreygreen);
-	colors[TITLEHOLD] = mkcolor(DMedblue);
-	colors[LTITLEHOLD] = mkcolor(DPalegreyblue);
+	// this is probably dumb.
+	pal[0] = mkcolor(0x00000000);
+	pal[1] = mkcolor(0x000000FF);
+	pal[2] = mkcolor(0xFFFFFFFF);
+	pal[3] = mkcolor(0xC0C0C0FF);
+	pal[4] = mkcolor(0x808080FF);
+	pal[5] = mkcolor(0x0000FFFF);
+	pal[6] = mkcolor(0x87888FFF);
+	pal[7] = mkcolor(0xC0C7C8FF);
 
-	colors[PALETEXT] = mkcolor(0x666666FF);
-	colors[HOLDTEXT] = mkcolor(DMedblue);
-	colors[PALEHOLDTEXT] = mkcolor(DGreyblue);
+	inittheme();
 }
-
--- /dev/null
+++ b/deskmenu.c
@@ -1,0 +1,169 @@
+#include "inc.h"
+
+static	Image	*menutxt;
+static	Image	*back;
+static	Image	*high;
+static	Image	*bord;
+static	Image	*text;
+static	Image	*htext;
+
+enum
+{
+	Border = 2,
+	ItemBorder = 1,
+	Itemwidth = 40,
+	Itemheight = 30
+};
+
+static
+void
+menucolors(void)
+{
+	/* Main tone is greenish, with negative selection */
+	back = getcolor("menuback", 0xEAFFEAFF);
+	high = getcolor("menuhigh", 0x448844FF);	/* dark green */
+	bord = getcolor("menubord", 0x88CC88FF);	/* not as dark green */
+	text = getcolor("menutext", 0x000000FF);
+	htext = getcolor("menuhtext", 0xEAFFEAFF);
+	if(back==nil || high==nil || bord==nil || text==nil || htext==nil)
+		goto Error;
+	return;
+
+    Error:
+	freeimage(back);
+	freeimage(high);
+	freeimage(bord);
+	freeimage(text);
+	freeimage(htext);
+	back = display->white;
+	high = display->black;
+	bord = display->black;
+	text = display->black;
+	htext = display->white;
+}
+
+static Rectangle
+menurect(Rectangle r, int i, int j)
+{
+	if(i < 0 || j < 0)
+		return Rect(0, 0, 0, 0);
+	return rectaddpt(Rect(0, 0, Itemwidth, Itemheight),
+		Pt(r.min.x+i*Itemwidth, r.min.y+j*Itemheight));
+}
+
+static void
+paintitem(Image *m, Rectangle contr, int i, int j, int highlight)
+{
+	Rectangle r;
+
+	if(i < 0 || j < 0)
+		return;
+	r = menurect(contr, i, j);
+	draw(m, r, highlight? high : back, nil, ZP);
+	border(m, r, ItemBorder, bord, ZP);
+}
+
+static Point
+menusel(Rectangle r, Point p)
+{
+	if(!ptinrect(p, r))
+		return Pt(-1,-1);
+	return Pt((p.x-r.min.x)/Itemwidth, (p.y-r.min.y)/Itemheight);
+}
+
+
+static Point
+menuscan(Image *m, int but, Mousectl *mc, Rectangle contr)
+{
+	Point ij, lastij;
+
+	lastij = menusel(contr, mc->xy);
+	paintitem(m, contr, lastij.x, lastij.y, 1);
+	readmouse(mc);
+	while(mc->buttons & (1<<(but-1))){
+		ij = menusel(contr, mc->xy);
+		if(!eqpt(ij, lastij))
+			paintitem(m, contr, lastij.x, lastij.y, 0);
+		if(ij.x == -1 || ij.y == -1)
+			return Pt(-1,-1);
+		lastij = ij;
+		paintitem(m, contr, lastij.x, lastij.y, 1);
+
+		readmouse(mc);
+	}
+	return lastij;
+}
+
+static void
+menupaint(Image *m, Rectangle contr, int nx, int ny)
+{
+	int i, j;
+
+	draw(m, contr, back, nil, ZP);
+	for(i = 0; i < nx; i++)
+	for(j = 0; j < ny; j++)
+		paintitem(m, contr, i, j, 0);
+}
+
+static Point
+clampscreen(Rectangle r)
+{
+	Point pt;
+
+	pt = ZP;
+	if(r.max.x>screen->r.max.x)
+		pt.x = screen->r.max.x-r.max.x;
+	if(r.max.y>screen->r.max.y)
+		pt.y = screen->r.max.y-r.max.y;
+	if(r.min.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.min.y)
+		pt.y = screen->r.min.y-r.min.y;
+	return pt;
+}
+
+Point
+dmenuhit(int but, Mousectl *mc, int nx, int ny, Point last)
+{
+	Rectangle r, menur, contr;
+	Point delta;
+	Point sel;
+
+	if(back == nil)
+		menucolors();
+
+	if(last.x < 0) last.x = 0;
+	if(last.x >= nx) last.x = nx-1;
+	if(last.y < 0) last.y = 0;
+	if(last.y >= ny) last.y = ny-1;
+
+	r = insetrect(Rect(0, 0, nx*Itemwidth, ny*Itemheight), -Border);
+	r = rectsubpt(r, Pt(last.x*Itemwidth+Itemwidth/2, last.y*Itemheight+Itemheight/2));
+	menur = rectaddpt(r, mc->xy);
+	delta = clampscreen(menur);
+	menur = rectaddpt(menur, delta);
+	contr = insetrect(menur, Border);
+
+	Image *b, *backup;
+	{
+		b = screen;
+		backup = allocimage(display, menur, screen->chan, 0, -1);
+		draw(backup, menur, screen, nil, menur.min);
+	}
+	draw(b, menur, back, nil, ZP);
+	border(b, menur, Border, bord, ZP);
+	menupaint(b, contr, nx, ny);
+
+	sel = Pt(-1, -1);
+	while(mc->buttons & (1<<(but-1))){
+		sel = menuscan(b, but, mc, contr);
+	}
+
+	if(backup){
+		draw(screen, menur, backup, nil, menur.min);
+		freeimage(backup);
+	}
+	flushimage(display, 1);
+
+	return sel;
+}
--- /dev/null
+++ b/flat.c
@@ -1,0 +1,77 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 18;
+
+enum {
+	TITLE,
+	LTITLE,
+	TITLEHOLD,
+	LTITLEHOLD,
+	TITLETEXT,
+	LTITLETEXT,
+	TITLEHOLDTEXT,
+	LTITLEHOLDTEXT,
+
+	NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+
+void
+wdecor(Window *w)
+{
+	if(w->frame == nil)
+		return;
+	int c = w->holdmode ?
+		w == focused ? TITLEHOLD : LTITLEHOLD :
+		w == focused ? TITLE : LTITLE;
+
+	Rectangle r = w->frame->r;
+
+	if(!w->noborder){
+		border(w->frame, r, bordersz, wincolors[c], ZP);
+		r = insetrect(r, bordersz);
+	}
+
+	if(!w->notitle){
+		r.max.y = r.min.y + titlesz;
+		draw(w->frame, r, wincolors[c], nil, ZP);
+
+		Point pt = Pt(r.min.x, r.min.y);
+		string(w->frame, pt, wincolors[c+4], pt, font, w->label);
+	}
+}
+
+void
+wtitlectl(Window *w)
+{
+	if(mctl->buttons & 7){
+		wraise(w);
+		wfocus(w);
+		if(mctl->buttons & 1) {
+			if(!w->maximized)
+				grab(w, 1);
+		}
+		if(mctl->buttons & 4)
+			btn3menu();
+	}
+}
+
+void
+inittheme(void)
+{
+	wincolors[TITLE] = getcolor("title", DGreygreen);
+	wincolors[LTITLE] = getcolor("ltitle", DPalegreygreen);
+//	wincolors[TITLE] = getcolor("title", 0x2F78EDFF);
+//	wincolors[LTITLE] = getcolor("ltitle", 0x7C9DE3FF);
+
+	wincolors[TITLEHOLD] = getcolor("titlehold", DMedblue);
+	wincolors[LTITLEHOLD] = getcolor("ltitlehold", DPalegreyblue);
+
+
+	wincolors[TITLETEXT] = getcolor("titletext", 0xFFFFFFFF);
+	wincolors[LTITLETEXT] = getcolor("ltitletext", 0x808080FF);
+	wincolors[TITLEHOLDTEXT] = getcolor("titleholdtext", 0xFFFFFFFF);
+	wincolors[LTITLEHOLDTEXT] = getcolor("ltitleholdtext", 0xC0C0C0FF);
+}
--- a/fs.c
+++ b/fs.c
@@ -7,7 +7,8 @@
 	Qsnarf,
 	Qwctl,
 	Qtap,
-	Qglobal = Qtap,	/* last global one */
+	Qpick,
+	Qglobal = Qpick,	/* last global one */
 
 	/* these need a window */
 	Qcons,
@@ -51,6 +52,7 @@
 	{ Qscreen,	QTFILE, "screen",	0400 },
 	{ Qwindow,	QTFILE, "window",	0400 },
 	{ Qwctl,	QTFILE, "wctl",	0600 },
+	{ Qpick,	QTFILE, "pick",	0400 },
 	{ Qtap,	QTFILE, "kbdtap",	0660 }
 };
 
@@ -269,8 +271,9 @@
 		}
 		if(id = strtol(name, &end, 10), *end == '\0'){
 			w = wfind(id);
-			if(w){
-				incref(w);
+			if(w || id == 0){
+				if(w)
+					incref(w);
 				wrelease(xf->w);
 				xf->w = w;
 				fid->qid = (Qid){QID(ID(w),Qroot), 0, QTDIR};
@@ -341,10 +344,12 @@
 static void
 fsopen(Req *r)
 {
+	Xfid *xf;
 	Window *w;
 	int rd, wr;
 
-	w = XF(r->fid)->w;
+	xf = XF(r->fid);
+	w = xf->w;
 
 	/* TODO: check and sanitize mode */
 
@@ -408,6 +413,22 @@
 		}
 		break;
 
+	case Qpick:
+		if(xf->w){
+			wrelease(xf->w);
+			xf->w = nil;
+		}
+		/* pick window from main thread.
+		 * TODO: this may not be optimal because
+		 *       it might block this thread. */
+		Channel *wc = chancreate(sizeof(Window*), 0);
+		sendp(pickchan, wc);
+		xf->w = w = recvp(wc);
+		if(w)
+			incref(w);
+		chanfree(wc);
+		break;
+
 	case Qtap:
 		if(rd && totap || wr && fromtap){
 			respond(r, Einuse);
@@ -639,17 +660,20 @@
 		respond(r, readimg(r, screen));
 		return;
 	case Qwindow:
-		respond(r, readimg(r, w->img));
+		respond(r, readimg(r, w->frame));
 		return;
 	case Qwctl:
 /* TODO: what's with the Etooshort conditions?? */
 		if(w == nil){
-			if(r->ifcall.count < 6*12){
+			if(r->ifcall.count < 4*12){
 				respond(r, Etooshort);
 				return;
 			}
-			data = smprint("%11d %11d %11d %11d nowindow    nowindow    ",
-				screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y);
+//			data = smprint("%11d %11d %11d %11d %11s %11s ",
+			data = smprint("%11d %11d %11d %11d %11d %11d ",
+				screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y,
+//				"nowindow", "nowindow");
+				screenoff.x, screenoff.y);
 			readstr(r, data);
 			free(data);
 		}else{
@@ -660,6 +684,11 @@
 			respond(r, readblocking(r, w->wctlread));
 			return;
 		}
+		break;
+	case Qpick:
+		data = smprint("%11d ", w ? w->id : -1);
+		readstr(r, data);
+		free(data);
 		break;
 	case Qtap:
 		respond(r, readblocking(r, totap));
--- a/inc.h
+++ b/inc.h
@@ -94,12 +94,7 @@
 enum
 {
 	// NCOL is defined by libframe, add more after it
-	TITLE = NCOL,
-	LTITLE,
-	TITLEHOLD,
-	LTITLEHOLD,
-
-	PALETEXT,
+	PALETEXT = NCOL,
 	HOLDTEXT,
 	PALEHOLDTEXT,
 
@@ -116,22 +111,20 @@
 extern Cursor *corners[9];
 void initdata(void);
 
+extern bool scrolling;
+extern bool notitle;
+extern int ndeskx;
+extern int ndesky;
+
 extern Screen *wscreen;
+extern Image *fakebg;
 extern Mousectl *mctl;
-extern int scrolling;
 extern char *startdir;
-extern int shiftdown;
-extern int gotscreen;
-extern int servekbd;
+extern bool shiftdown, ctldown;
+extern bool gotscreen;
+extern bool servekbd;
 
-extern Cursor *cursor;
-void setcursoroverride(Cursor *c, int ov);
-void setcursornormal(Cursor *c);
 
-Rectangle newrect(void);
-int goodrect(Rectangle r);
-
-
 typedef struct RuneConvBuf RuneConvBuf;
 struct RuneConvBuf
 {
@@ -192,6 +185,9 @@
 	Wakeup
 };
 
+extern int bordersz;
+extern int titlesz;
+
 typedef struct Window Window;
 struct Window
 {
@@ -200,16 +196,23 @@
 	bool hidden;
 	Window *lower;
 	Window *higher;
-	Image *img;
+	Image *frame;
+	Image *content;
 	int id;
 	char name[32];
 	int namecount;
 	char *label;
-	bool noborder;
-	Rectangle contrect;
+	int noborder;
 	int notefd;
 	char *dir;
 
+	bool notitle;
+	bool maximized;
+	bool sticky;
+	Rectangle origrect;
+	Rectangle titlerect;
+	Rectangle contrect;
+
 	Text text;
 	Rectangle scrollr;
 	Rectangle textr;
@@ -248,8 +251,12 @@
 extern Window *windows[MAXWINDOWS];
 extern int nwindows;
 extern Window *focused, *cursorwin;
+extern Point screenoff;
 
+void wtitlectl(Window *w);
 void wdecor(Window *w);
+void wmaximize(Window *w);
+void wrestore(Window *w);
 void wresize(Window *w, Rectangle r);
 Window *wcreate(Rectangle r, bool hidden, bool scrolling);
 void wrelease(Window *w);
@@ -273,6 +280,8 @@
 void wtype(Window *w, Rune r);
 int wincmd(Window *w, int pid, char *dir, char **argv);
 
+void screenoffset(int offx, int offy);
+
 typedef struct Wctlcmd Wctlcmd;
 struct Wctlcmd
 {
@@ -291,10 +300,34 @@
 char *writewctl(Window *w, char *data);
 
 
+extern Cursor *cursor;
+void setcursoroverride(Cursor *c, int ov);
+void setcursornormal(Cursor *c);
+
+Rectangle newrect(void);
+int goodrect(Rectangle r);
+Rectangle centerrect(Rectangle r, Rectangle s);
+void borderTL(Image *img, Rectangle r, Image *c);
+void borderBR(Image *img, Rectangle r, Image *c);
+void winborder(Image *img, Rectangle r, Image *c1, Image *c2);
+
+void refresh(void);
+Point dmenuhit(int but, Mousectl *mc, int nx, int ny, Point last);
+void drainmouse(Mousectl *mc, Window *w);
+void grab(Window *w, int btn);
+void btn3menu(void);
+
+void inittheme(void);
+Image *getcolor(char *name, ulong defcol);
+Image *mkicon(char *px, int w, int h);
+
+
 extern Channel *opentap;	/* open fromtap or totap */
 extern Channel *closetap;	/* close fromtap or totap */
 extern Channel *fromtap;	/* input from kbd tap program to window */
 extern Channel *totap;		/* our keyboard input to tap program */
+
+extern Channel *pickchan;
 
 
 extern Srv fsys;
--- a/main.c
+++ b/main.c
@@ -1,14 +1,19 @@
 #include "inc.h"
 
+bool scrolling;
+bool notitle;
+int ndeskx = 3;
+int ndesky = 3;
+
 RKeyboardctl *kbctl;
 Mousectl *mctl;
-int scrolling = 1;
 char *startdir;
-int shiftdown;
-int gotscreen;
-int servekbd;
+bool shiftdown, ctldown;
+bool gotscreen;
+bool servekbd;
 
 Screen *wscreen;
+Image *fakebg;
 
 void
 killprocs(void)
@@ -305,15 +310,15 @@
 }
 
 void
-grab(Window *w)
+grab(Window *w, int btn)
 {
 	if(w == nil)
-		w = clickwindow(3, mctl);
+		w = clickwindow(btn, mctl);
 	if(w == nil)
 		setcursoroverride(nil, FALSE);
 	else{
-		Rectangle r = dragrect(3, w->img->r, mctl);
-		if(Dx(r) > 0 || Dy(r) > 0){
+		Rectangle r = dragrect(btn, w->frame->r, mctl);
+		if((Dx(r) > 0 || Dy(r) > 0) && !eqrect(r, w->frame->r)){
 			wmove(w, r.min);
 			wfocus(w);
 			flushimage(display, 1);
@@ -341,8 +346,8 @@
 bandresize(Window *w)
 {
 	Rectangle r;
-	r = bandrect(w->img->r, mctl->buttons, mctl);
-	if(!eqrect(r, w->img->r)){
+	r = bandrect(w->frame->r, mctl->buttons, mctl);
+	if(!eqrect(r, w->frame->r)){
 		wresize(w, r);
 		flushimage(display, 1);
 	}
@@ -356,19 +361,19 @@
 	if(!rectclip(&r, screen->r))
 		return 1;
 	for(; t; t = t->higher){
-		if(t->hidden || Dx(t->img->r) == 0 || Dy(t->img->r) == 0 || rectXrect(r, t->img->r) == 0)
+		if(t->hidden || Dx(t->frame->r) == 0 || Dy(t->frame->r) == 0 || rectXrect(r, t->frame->r) == 0)
 			continue;
-		if(r.min.y < t->img->r.min.y)
-			if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->img->r.min.y), t))
+		if(r.min.y < t->frame->r.min.y)
+			if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->frame->r.min.y), t))
 				return 0;
-		if(r.min.x < t->img->r.min.x)
-			if(!obscured(w, Rect(r.min.x, r.min.y, t->img->r.min.x, r.max.y), t))
+		if(r.min.x < t->frame->r.min.x)
+			if(!obscured(w, Rect(r.min.x, r.min.y, t->frame->r.min.x, r.max.y), t))
 				return 0;
-		if(r.max.y > t->img->r.max.y)
-			if(!obscured(w, Rect(r.min.x, t->img->r.max.y, r.max.x, r.max.y), t))
+		if(r.max.y > t->frame->r.max.y)
+			if(!obscured(w, Rect(r.min.x, t->frame->r.max.y, r.max.x, r.max.y), t))
 				return 0;
-		if(r.max.x > t->img->r.max.x)
-			if(!obscured(w, Rect(t->img->r.max.x, r.min.y, r.max.x, r.max.y), t))
+		if(r.max.x > t->frame->r.max.x)
+			if(!obscured(w, Rect(t->frame->r.max.x, r.min.y, r.max.x, r.max.y), t))
 				return 0;
 		return 1;
 	}
@@ -391,13 +396,14 @@
 	 * that includes the border on each side with an extra pixel
 	 * so that the text is still drawn
 	 */
-	if(Dx(r) < 100 || Dy(r) < 2*(Borderwidth+1)+font->height)
+	if(Dx(r) < 100 || Dy(r) < 2*(bordersz+1)+font->height)
 		return 0;
+//TODO(vdesk) this changes
 	/* window must be on screen */
 	if(!rectXrect(screen->r, r))
 		return 0;
 	/* must have some screen and border visible so we can move it out of the way */
-	if(rectinrect(screen->r, insetrect(r, Borderwidth)))
+	if(rectinrect(screen->r, insetrect(r, bordersz)))
 		return 0;
 	return 1;
 }
@@ -409,8 +415,8 @@
 	static int i = 0;
 	int minx, miny, dx, dy;
 
-	dx = min(600, Dx(screen->r) - 2*Borderwidth);
-	dy = min(400, Dy(screen->r) - 2*Borderwidth);
+	dx = min(600, Dx(screen->r) - 2*bordersz);
+	dy = min(400, Dy(screen->r) - 2*bordersz);
 	minx = 32 + 16*i;
 	miny = 32 + 16*i;
 	i++;
@@ -420,50 +426,30 @@
 }
 
 
-enum {
-	Cut,
-	Paste,
-	Snarf,
-	Plumb,
-	Look,
-	Send,
-	Scroll
-};
-char *menu2str[] = {
-	"cut",
-	"paste",
-	"snarf",
-	"plumb",
-	"look",
-	"send",
-	"scroll",
-	nil
-};
-Menu menu2 = { menu2str };
-
-enum {
-	New,
-	Reshape,
-	Move,
-	Delete,
-	Hide,
-	Exit
-};
-int Hidden = Exit+1;
-char *menu3str[7 + MAXWINDOWS] = {
-	"New",
-	"Resize",
-	"Move",
-	"Delete",
-	"Hide",
-	"Exit",
-	nil
-};
-Menu menu3 = { menu3str };
-
 void
 btn2menu(Window *w)
 {
+	enum {
+		Cut,
+		Paste,
+		Snarf,
+		Plumb,
+		Look,
+		Send,
+		Scroll
+	};
+	static char *menu2str[] = {
+		"cut",
+		"paste",
+		"snarf",
+		"plumb",
+		"look",
+		"send",
+		"scroll",
+		nil
+	};
+	static Menu menu2 = { menu2str };
+
 	int sel;
 	Text *x;
 	Cursor *c;
@@ -508,6 +494,24 @@
 void
 btn3menu(void)
 {
+	enum {
+		New,
+		Reshape,
+		Move,
+		Delete,
+		Hide,
+		Hidden
+	};
+	static char *str[Hidden+1 + MAXWINDOWS] = {
+		"New",
+		"Resize",
+		"Move",
+		"Delete",
+		"Hide",
+		nil
+	};
+	static Menu menu = { str };
+
 	static Window *hidden[MAXWINDOWS];
 	int nhidden;
 	Window *w, *t;
@@ -516,15 +520,17 @@
 	nhidden = 0;
 	for(i = 0; i < nwindows; i++){
 		t = windows[i];
-		if(t->hidden || obscured(t, t->img->r, t->higher)){
+		if(!rectXrect(screen->r, t->frame->r))
+			continue;
+		if(t->hidden || obscured(t, t->frame->r, t->higher)){
 			hidden[nhidden] = windows[i];
-			menu3str[nhidden+Hidden] = windows[i]->label;
+			str[nhidden+Hidden] = windows[i]->label;
 			nhidden++;	
 		}
 	}
-	menu3str[nhidden+Hidden] = nil;
+	str[nhidden+Hidden] = nil;
 
-	sel = menuhit(3, mctl, &menu3, wscreen);
+	sel = menuhit(3, mctl, &menu, wscreen);
 	switch(sel){
 	case New:
 		sweep(nil);
@@ -534,7 +540,7 @@
 		if(w) sweep(w);
 		break;
 	case Move:
-		grab(nil);
+		grab(nil, 3);
 		break;
 	case Delete:
 		w = pick();
@@ -544,9 +550,6 @@
 		w = pick();
 		if(w) whide(w);
 		break;
-	case Exit:
-		killprocs();
-		threadexitsall(nil);
 	default:
 		if(sel >= Hidden){
 			w = hidden[sel-Hidden];
@@ -562,57 +565,131 @@
 }
 
 void
+btn13menu(void)
+{
+	enum {
+		RefreshScreen,
+		Exit
+	};
+	static char *str[] = {
+		"Refresh",
+		"Exit",
+		nil
+	};
+	static Menu menu = { str };
+
+	switch(menuhit(3, mctl, &menu, wscreen)){
+	case RefreshScreen:
+		refresh();
+		break;
+	case Exit:
+		killprocs();
+		threadexitsall(nil);
+	}
+}
+
+void
+btn12menu(void)
+{
+	int dx, dy, i, j;
+
+	dx = Dx(screen->r);
+	dy = Dy(screen->r);
+	i = screenoff.x/dx;
+	j = screenoff.y/dy;
+	Point ssel = dmenuhit(2, mctl, ndeskx, ndesky, Pt(i,j));
+	if(ssel.x >= 0 && ssel.y >= 0)
+		screenoffset(ssel.x*dx, ssel.y*dy);
+}
+
+void
 mthread(void*)
 {
 	Window *w;
+	Channel *wc;
 
 	threadsetname("mousethread");
-	while(readmouse(mctl) != -1){
-		w = wpointto(mctl->xy);
-		cursorwin = w;
+	enum { Amouse, Apick, NALT };
+	Alt alts[NALT+1] = {
+		[Amouse]	{.c = mctl->c, .v = &mctl->Mouse, .op = CHANRCV},
+		[Apick]		{.c = pickchan, .v = &wc, .op = CHANRCV},
+		[NALT]		{.op = CHANEND},
+	};
+	for(;;){
+		// normally done in readmouse
+		Display *d = mctl->image->display;
+		if(d->bufp > d->buf)
+			flushimage(d, 1);
+		switch(alt(alts)){
+		case Apick:
+			sendp(wc, pick());
+			break;
+		case Amouse:
+			w = wpointto(mctl->xy);
+			cursorwin = w;
 again:
-		if(w == nil){
-			setcursornormal(nil);
-			if(mctl->buttons & 4)
-				btn3menu();
-		}else if(!ptinrect(mctl->xy, w->contrect)){
-			/* border */
-			setcursornormal(corners[whichcorner(w->img->r, mctl->xy)]);
-			if(mctl->buttons & 7){
-				wraise(w);
-				wfocus(w);
+			if(w == nil){
+				/* background */
+				setcursornormal(nil);
+				while(mctl->buttons & 1){
+					if(mctl->buttons & 2)
+						btn12menu();
+					else if(mctl->buttons & 4)
+						btn13menu();
+					readmouse(mctl);
+				}
 				if(mctl->buttons & 4)
-					grab(w);
-				if(mctl->buttons & 3)
-					bandresize(w);
-			}
-		}else if(w != focused){
-			wsetcursor(w);
-			if(mctl->buttons & 7 ||
-			   mctl->buttons & (8|16) && focused->mouseopen){
-				wraise(w);
-				wfocus(w);
-				if(mctl->buttons & 1)
-					drainmouse(mctl, nil);
-				else
-					goto again;
-			}
-		}else if(!w->mouseopen){
-			wsetcursor(w);
-			if(mctl->buttons && topwin != w)
-				wraise(w);
-			if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->text.scrollr))
+					btn3menu();
+			}else if(!ptinrect(mctl->xy, w->contrect)){
+				/* decoration */
+				if(!w->noborder &&
+				   !ptinrect(mctl->xy, insetrect(w->frame->r, bordersz))){
+					/* border */
+					setcursornormal(corners[whichcorner(w->frame->r, mctl->xy)]);
+					if(mctl->buttons & 7){
+						wraise(w);
+						wfocus(w);
+						if(mctl->buttons & 4)
+							grab(w, 3);
+						if(mctl->buttons & 3)
+							bandresize(w);
+					}
+				}else{
+					/* title bar */
+					setcursornormal(nil);
+					wtitlectl(w);
+				}
+			}else if(w != focused){
+				/* inactive window */
+				wsetcursor(w);
+				if(mctl->buttons & 7 ||
+				   mctl->buttons & (8|16) && focused->mouseopen){
+					wraise(w);
+					wfocus(w);
+					if(mctl->buttons & 1)
+						drainmouse(mctl, nil);
+					else
+						goto again;
+				}
+			}else if(!w->mouseopen){
+				/* active text window */
+				wsetcursor(w);
+				if(mctl->buttons && topwin != w)
+					wraise(w);
+				if(mctl->buttons & (1|8|16) || ptinrect(mctl->xy, w->text.scrollr))
+					drainmouse(mctl, w);
+				if(mctl->buttons & 2){
+					incref(w);
+					btn2menu(w);
+					wrelease(w);
+				}
+				if(mctl->buttons & 4)
+					btn3menu();
+			}else{
+				/* active graphics window */
+				wsetcursor(w);
 				drainmouse(mctl, w);
-			if(mctl->buttons & 2){
-				incref(w);
-				btn2menu(w);
-				wrelease(w);
 			}
-			if(mctl->buttons & 4)
-				btn3menu();
-		}else{
-			wsetcursor(w);
-			drainmouse(mctl, w);
 		}
 	}
 }
@@ -633,18 +710,42 @@
 		nr = screen->clipr;
 
 		freescrtemps();
+		freeimage(fakebg);
 		freescreen(wscreen);
 		wscreen = allocscreen(screen, background, 0);
-		draw(screen, screen->r, background, nil, ZP);
+		fakebg = allocwindow(wscreen, screen->r, Refbackup, DNofill);
+		draw(fakebg, fakebg->r, background, nil, ZP);
 
 		delta = subpt(nr.min, or.min);
-		for(w = bottomwin; w; w = w->higher)
-			wresize(w, rectaddpt(w->img->r, delta));
+		for(w = bottomwin; w; w = w->higher){
+			if(w->maximized){
+				wrestore(w);
+				wresize(w, rectaddpt(w->frame->r, delta));
+				wmaximize(w);
+			}else
+				wresize(w, rectaddpt(w->frame->r, delta));
+		}
 
 		flushimage(display, 1);
 	}
 }
 
+void
+refresh(void)
+{
+	Window *w;
+
+	draw(fakebg, fakebg->r, background, nil, ZP);
+	for(w = bottomwin; w; w = w->higher){
+		if(w->maximized){
+			wrestore(w);
+			wresize(w, w->frame->r);
+			wmaximize(w);
+		}else
+			wresize(w, w->frame->r);
+	}
+}
+
 /*
  *    kbd    -----+-------> to tap
  *                 \
@@ -690,8 +791,10 @@
 		switch(alt(alts)){
 		case Akbd:
 			/* from keyboard to tap or to window */
-			if(*s == 'k' || *s == 'K')
+			if(*s == 'k' || *s == 'K'){
 				shiftdown = utfrune(s+1, Kshift) != nil;
+				ctldown = utfrune(s+1, Kctl) != nil;
+			}
 			prev = cur;
 			cur = focused;
 			if(totap){
@@ -760,13 +863,42 @@
 	}
 }
 
+Channel *pickchan;
+
 void
-threadmain(int, char *[])
+usage(void)
 {
+	fprint(2, "usage: lola [-s] [-t]\n");
+	exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
 	char buf[256];
-//rfork(RFENVG);
-//newwindow("-dx 1280 -dy 800");
+if(strcmp(argv[0]+1, ".out") == 0){
+rfork(RFENVG);
+newwindow("-dx 1280 -dy 800");
+scrolling = TRUE;
+notitle = FALSE;
+}
 
+	ARGBEGIN{
+/*
+	case 'i':
+		initstr = EARGF(usage());
+		break;
+*/
+	case 's':
+		scrolling = TRUE;
+		break;
+	case 't':
+		notitle = TRUE;
+		break;
+	default:
+		usage();
+	}ARGEND
+
 	if(getwd(buf, sizeof(buf)) == nil)
 		startdir = estrdup(".");
 	else
@@ -782,6 +914,8 @@
 	opentap = chancreate(sizeof(Channel*), 0);
 	closetap = chancreate(sizeof(Channel*), 0);
 
+	pickchan = chancreate(sizeof(Channel*), 0);
+
 	servekbd = kbctl->kbdfd >= 0;
 	snarffd = open("/dev/snarf", OREAD|OCEXEC);
 	gotscreen = access("/dev/screen", AEXIST)==0;
@@ -789,7 +923,8 @@
 	initdata();
 
 	wscreen = allocscreen(screen, background, 0);
-	draw(screen, screen->r, background, nil, ZP);
+	fakebg = allocwindow(wscreen, screen->r, Refbackup, DNofill);
+	draw(fakebg, fakebg->r, background, nil, ZP);
 
 	timerinit();
 
--- /dev/null
+++ b/menuhit.c
@@ -1,0 +1,280 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <mouse.h>
+
+enum
+{
+	Margin = 4,		/* outside to text */
+	Border = 2,		/* outside to selection boxes */
+	Blackborder = 2,	/* width of outlining border */
+	Vspacing = 2,		/* extra spacing between lines of text */
+	Maxunscroll = 25,	/* maximum #entries before scrolling turns on */
+	Nscroll = 20,		/* number entries in scrolling part */
+	Scrollwid = 14,		/* width of scroll bar */
+	Gap = 4,			/* between text and scroll bar */
+};
+
+static	Image	*menutxt;
+static	Image	*back;
+static	Image	*high;
+static	Image	*bord;
+static	Image	*text;
+static	Image	*htext;
+
+Image *getcolor(char *name, ulong col);
+
+static
+void
+menucolors(void)
+{
+	/* Main tone is greenish, with negative selection */
+	back = getcolor("menuback", 0xEAFFEAFF);
+	high = getcolor("menuhigh", 0x448844FF);	/* dark green */
+	bord = getcolor("menubord", 0x88CC88FF);	/* not as dark green */
+	text = getcolor("menutext", 0x000000FF);
+	htext = getcolor("menuhtext", 0xEAFFEAFF);
+	if(back==nil || high==nil || bord==nil || text==nil || htext==nil)
+		goto Error;
+	return;
+
+    Error:
+	freeimage(back);
+	freeimage(high);
+	freeimage(bord);
+	freeimage(text);
+	freeimage(htext);
+	back = display->white;
+	high = display->black;
+	bord = display->black;
+	text = display->black;
+	htext = display->white;
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the rectangle, including its black edge, holding element i.
+ */
+static Rectangle
+menurect(Rectangle r, int i)
+{
+	if(i < 0)
+		return Rect(0, 0, 0, 0);
+	r.min.y += (font->height+Vspacing)*i;
+	r.max.y = r.min.y+font->height+Vspacing;
+	return insetrect(r, Border-Margin);
+}
+
+/*
+ * r is a rectangle holding the text elements.
+ * return the element number containing p.
+ */
+static int
+menusel(Rectangle r, Point p)
+{
+	if(!ptinrect(p, r))
+		return -1;
+	return (p.y-r.min.y)/(font->height+Vspacing);
+}
+
+static void
+paintitem(Image *m, Menu *menu, Rectangle textr, int off, int i, int highlight, Image *save, Image *restore)
+{
+	char *item;
+	Rectangle r;
+	Point pt;
+
+	if(i < 0)
+		return;
+	r = menurect(textr, i);
+	if(restore){
+		draw(m, r, restore, nil, restore->r.min);
+		return;
+	}
+	if(save)
+		draw(save, save->r, m, nil, r.min);
+	item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
+	pt.x = (textr.min.x+textr.max.x-stringwidth(font, item))/2;
+	pt.y = textr.min.y+i*(font->height+Vspacing);
+	draw(m, r, highlight? high : back, nil, pt);
+	string(m, pt, highlight? htext : text, pt, font, item);
+}
+
+/*
+ * menur is a rectangle holding all the highlightable text elements.
+ * track mouse while inside the box, return what's selected when button
+ * is raised, -1 as soon as it leaves box.
+ * invariant: nothing is highlighted on entry or exit.
+ */
+static int
+menuscan(Image *m, Menu *menu, int but, Mousectl *mc, Rectangle textr, int off, int lasti, Image *save)
+{
+	int i;
+
+	paintitem(m, menu, textr, off, lasti, 1, save, nil);
+	for(readmouse(mc); mc->buttons & (1<<(but-1)); readmouse(mc)){
+		i = menusel(textr, mc->xy);
+		if(i != -1 && i == lasti)
+			continue;
+		paintitem(m, menu, textr, off, lasti, 0, nil, save);
+		if(i == -1)
+			return i;
+		lasti = i;
+		paintitem(m, menu, textr, off, lasti, 1, save, nil);
+	}
+	return lasti;
+}
+
+static void
+menupaint(Image *m, Menu *menu, Rectangle textr, int off, int nitemdrawn)
+{
+	int i;
+
+	draw(m, insetrect(textr, Border-Margin), back, nil, ZP);
+	for(i = 0; i<nitemdrawn; i++)
+		paintitem(m, menu, textr, off, i, 0, nil, nil);
+}
+
+static void
+menuscrollpaint(Image *m, Rectangle scrollr, int off, int nitem, int nitemdrawn)
+{
+	Rectangle r;
+
+	draw(m, scrollr, back, nil, ZP);
+	r.min.x = scrollr.min.x;
+	r.max.x = scrollr.max.x;
+	r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
+	r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
+	if(r.max.y < r.min.y+2)
+		r.max.y = r.min.y+2;
+	border(m, r, 1, bord, ZP);
+	if(menutxt == 0)
+		menutxt = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkgreen);	/* border color; BUG? */
+	if(menutxt)
+		draw(m, insetrect(r, 1), menutxt, nil, ZP);
+}
+
+int
+menuhit(int but, Mousectl *mc, Menu *menu, Screen *scr)
+{
+	int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
+	int scrolling;
+	Rectangle r, menur, sc, textr, scrollr;
+	Image *b, *save, *backup;
+	Point pt;
+	char *item;
+
+	if(back == nil)
+		menucolors();
+	sc = screen->clipr;
+	replclipr(screen, 0, screen->r);
+	maxwid = 0;
+	for(nitem = 0;
+	    item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
+	    nitem++){
+		i = stringwidth(font, item);
+		if(i > maxwid)
+			maxwid = i;
+	}
+	if(menu->lasthit<0 || menu->lasthit>=nitem)
+		menu->lasthit = 0;
+	screenitem = (Dy(screen->r)-10)/(font->height+Vspacing);
+	if(nitem>Maxunscroll || nitem>screenitem){
+		scrolling = 1;
+		nitemdrawn = Nscroll;
+		if(nitemdrawn > screenitem)
+			nitemdrawn = screenitem;
+		wid = maxwid + Gap + Scrollwid;
+		off = menu->lasthit - nitemdrawn/2;
+		if(off < 0)
+			off = 0;
+		if(off > nitem-nitemdrawn)
+			off = nitem-nitemdrawn;
+		lasti = menu->lasthit-off;
+	}else{
+		scrolling = 0;
+		nitemdrawn = nitem;
+		wid = maxwid;
+		off = 0;
+		lasti = menu->lasthit;
+	}
+	r = insetrect(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
+	r = rectsubpt(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
+	r = rectaddpt(r, mc->xy);
+	pt = ZP;
+	if(r.max.x>screen->r.max.x)
+		pt.x = screen->r.max.x-r.max.x;
+	if(r.max.y>screen->r.max.y)
+		pt.y = screen->r.max.y-r.max.y;
+	if(r.min.x<screen->r.min.x)
+		pt.x = screen->r.min.x-r.min.x;
+	if(r.min.y<screen->r.min.y)
+		pt.y = screen->r.min.y-r.min.y;
+	menur = rectaddpt(r, pt);
+	textr.max.x = menur.max.x-Margin;
+	textr.min.x = textr.max.x-maxwid;
+	textr.min.y = menur.min.y+Margin;
+	textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
+	if(scrolling){
+		scrollr = insetrect(menur, Border);
+		scrollr.max.x = scrollr.min.x+Scrollwid;
+	}else
+		scrollr = Rect(0, 0, 0, 0);
+
+	if(scr){
+		b = allocwindow(scr, menur, Refbackup, DWhite);
+		if(b == nil)
+			b = screen;
+		backup = nil;
+	}else{
+		b = screen;
+		backup = allocimage(display, menur, screen->chan, 0, -1);
+		if(backup)
+			draw(backup, menur, screen, nil, menur.min);
+	}
+	draw(b, menur, back, nil, ZP);
+	border(b, menur, Blackborder, bord, ZP);
+	save = allocimage(display, menurect(textr, 0), screen->chan, 0, -1);
+	r = menurect(textr, lasti);
+	if(pt.x || pt.y)
+		moveto(mc, divpt(addpt(r.min, r.max), 2));
+	menupaint(b, menu, textr, off, nitemdrawn);
+	if(scrolling)
+		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+	while(mc->buttons & (1<<(but-1))){
+		lasti = menuscan(b, menu, but, mc, textr, off, lasti, save);
+		if(lasti >= 0)
+			break;
+		while(!ptinrect(mc->xy, textr) && (mc->buttons & (1<<(but-1)))){
+			if(scrolling && ptinrect(mc->xy, scrollr)){
+				noff = ((mc->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
+				noff -= nitemdrawn/2;
+				if(noff < 0)
+					noff = 0;
+				if(noff > nitem-nitemdrawn)
+					noff = nitem-nitemdrawn;
+				if(noff != off){
+					off = noff;
+					menupaint(b, menu, textr, off, nitemdrawn);
+					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn);
+				}
+			}
+			readmouse(mc);
+		}
+	}
+	if(b != screen)
+		freeimage(b);
+	if(backup){
+		draw(screen, menur, backup, nil, menur.min);
+		freeimage(backup);
+	}
+	freeimage(save);
+	replclipr(screen, 0, sc);
+	flushimage(display, 1);
+	if(lasti >= 0){
+		menu->lasthit = lasti+off;
+		return menu->lasthit;
+	}
+	return -1;
+}
--- a/mkfile
+++ b/mkfile
@@ -10,7 +10,10 @@
 	util.$O \
 	kbd.$O \
 	time.$O \
-	data.$O
+	data.$O \
+	menuhit.$O \
+	deskmenu.$O \
+	simple.$O
 
 HFILES=inc.h
 
--- /dev/null
+++ b/simple.c
@@ -1,0 +1,233 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 17;//19;
+
+enum {
+	TITLE,
+	LTITLE,
+	TITLEHOLD,
+	LTITLEHOLD,
+	TITLETEXT,
+	LTITLETEXT,
+	TITLEHOLDTEXT,
+	LTITLEHOLDTEXT,
+	FRAME,
+	LFRAME,
+
+	NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[4];
+
+void
+btn(Image *img, Rectangle r, Image *col, Image *icon, int down)
+{
+	USED(down);
+
+//	border(img, r, 1, col, ZP);
+	r = centerrect(r, icon->r);
+	draw(img, r, col, icon, ZP);
+}
+
+int
+btnctl(Image *img, Rectangle r, Image *col, Image *icon)
+{
+	int over, prevover;
+
+	prevover = 1;
+	btn(img, r, col, icon, 1);
+	while(mctl->buttons){
+		readmouse(mctl);
+		over = ptinrect(mctl->xy, r);
+		if(over != prevover)
+			btn(img, r, col, icon, over);
+		prevover = over;
+	}
+	if(prevover)
+		btn(img, r, col, icon, 0);
+	return ptinrect(mctl->xy, r);
+}
+
+
+void
+wdecor(Window *w)
+{
+	if(w->frame == nil)
+		return;
+	int c = w->holdmode ?
+		w == focused ? TITLEHOLD : LTITLEHOLD :
+		w == focused ? TITLE : LTITLE;
+	int c2 = w == focused ? FRAME : LFRAME;
+
+	Rectangle r, b1, b2, b3;
+
+	if(!w->noborder){
+		r = w->frame->r;
+		border(w->frame, r, bordersz, wincolors[c], ZP);
+		border(w->frame, r, 1, wincolors[c2], ZP);
+		border(w->frame, insetrect(w->contrect,-1), 1, wincolors[c2], ZP);
+	}
+
+	if(!w->notitle){
+		r = w->titlerect;
+		r.max.y--;
+		draw(w->frame, r, wincolors[c], nil, ZP);
+
+		b1 = r;
+b1.max.x -= bordersz/2;
+		b1.min.x = b1.max.x - titlesz + bordersz;
+		b1.max.y = b1.min.y + Dx(b1);
+		b2 = rectsubpt(b1, Pt(titlesz, 0));
+		b3 = rectsubpt(b2, Pt(titlesz, 0));
+		btn(w->frame, b1, wincolors[c+4], icons[3], 0);
+		btn(w->frame, b2, wincolors[c+4], icons[1+w->maximized], 0);
+		btn(w->frame, b3, wincolors[c+4], icons[0], 0);
+
+r.min.x += bordersz/2;
+r.min.y -= 2;
+		Point pt = Pt(r.min.x, r.min.y);
+		string(w->frame, pt, wincolors[c+4], pt, font, w->label);
+	}
+}
+
+void
+wtitlectl(Window *w)
+{
+	if(mctl->buttons & 7){
+		wraise(w);
+		wfocus(w);
+		if(mctl->buttons & 1) {
+			int c = w->holdmode ?
+				w == focused ? TITLEHOLD : LTITLEHOLD :
+				w == focused ? TITLE : LTITLE;
+
+			Rectangle r = w->titlerect;
+			Rectangle br = Rect(0,0,titlesz-bordersz,titlesz-bordersz);
+r.max.x -= bordersz/2;
+			Rectangle br1 = rectaddpt(br, Pt(r.max.x-titlesz+bordersz, r.min.y));
+			Rectangle br2 = rectsubpt(br1, Pt(titlesz, 0));
+			Rectangle br3 = rectsubpt(br2, Pt(titlesz, 0));
+			// hack...
+			if(w->notitle)
+				br1 = br2 = br3 = Rect(0,0,0,0);
+
+			if(ptinrect(mctl->xy, br1)){
+				if(btnctl(w->frame, br1, wincolors[c+4], icons[3]))
+					wdelete(w);
+			}else if(ptinrect(mctl->xy, br2)){
+				if(btnctl(w->frame, br2, wincolors[c+4], icons[1+w->maximized])){
+					if(w->maximized)
+						wrestore(w);
+					else
+						wmaximize(w);
+				}
+			}else if(ptinrect(mctl->xy, br3)){
+				if(btnctl(w->frame, br3, wincolors[c+4], icons[0]))
+					whide(w);
+			}else if(!w->maximized)
+				grab(w, 1);
+		}
+		if(mctl->buttons & 4)
+			btn3menu();
+	}
+}
+
+
+static char minbtn[] = {
+	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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char maxbtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char rstbtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char closebtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+void
+inittheme(void)
+{
+	freeimage(colors[HOLDTEXT]);
+	freeimage(colors[PALEHOLDTEXT]);
+	colors[HOLDTEXT] = getcolor("holdtext", 0x990000FF);
+	colors[PALEHOLDTEXT] = getcolor("paleholdtext", 0xBB5D00FF);
+
+//	wincolors[TITLE] = getcolor("title", 0x607DA1FF);
+//	wincolors[LTITLE] = getcolor("ltitle", 0xA1A1A1FF);
+
+//	wincolors[TITLE] = getcolor("title", 0x2F78EDFF);
+//	wincolors[LTITLE] = getcolor("ltitle", 0x7C9DE3FF);
+
+	wincolors[TITLE] = getcolor("title", 0x5297F9FF);
+	wincolors[LTITLE] = getcolor("ltitle", 0x2C60B2FF);
+	wincolors[TITLEHOLD] = getcolor("titlehold", 0xED2F2FFF);
+	wincolors[LTITLEHOLD] = getcolor("ltitlehold", 0xE36A6AFF);
+
+	wincolors[FRAME] = getcolor("frame", 0x000000FF);
+	wincolors[LFRAME] = getcolor("lframe", 0x000000FF);
+
+	wincolors[TITLETEXT] = getcolor("titletext", 0xFFFFFFFF);
+	wincolors[LTITLETEXT] = getcolor("ltitletext", 0xFFFFFFFF);
+	wincolors[TITLEHOLDTEXT] = getcolor("titleholdtext", 0xFFFFFFFF);
+	wincolors[LTITLEHOLDTEXT] = getcolor("ltitleholdtext", 0xFFFFFFFF);
+
+	icons[0] = mkicon(minbtn, 16, 13);
+	icons[1] = mkicon(maxbtn, 16, 13);
+	icons[2] = mkicon(rstbtn, 16, 13);
+	icons[3] = mkicon(closebtn, 16, 13);
+}
--- a/util.c
+++ b/util.c
@@ -1,5 +1,44 @@
 #include "inc.h"
 
+/* Center rect s in rect r */
+Rectangle
+centerrect(Rectangle r, Rectangle s)
+{
+	int dx = (Dx(r) - Dx(s))/2;
+	int dy = (Dy(r) - Dy(s))/2;
+	return rectaddpt(Rect(0, 0, Dx(s), Dy(s)), Pt(r.min.x+dx, r.min.y+dy));
+}
+
+void
+borderTL(Image *img, Rectangle r, Image *c)
+{
+	// left
+	draw(img, Rect(r.min.x, r.min.y, r.min.x+1, r.max.y),
+		c, nil, ZP);
+	// top
+	draw(img, Rect(r.min.x, r.min.y, r.max.x, r.min.y+1),
+		c, nil, ZP);
+}
+
+void
+borderBR(Image *img, Rectangle r, Image *c)
+{
+	// bottom
+	draw(img, Rect(r.min.x, r.max.y-1, r.max.x, r.max.y),
+		c, nil, ZP);
+	// right
+	draw(img, Rect(r.max.x-1, r.min.y, r.max.x, r.max.y),
+		c, nil, ZP);
+}
+
+void
+winborder(Image *img, Rectangle r, Image *c1, Image *c2)
+{
+	borderTL(img, r, c1);
+	borderBR(img, r, c2);
+}
+
+
 void
 panic(char *s)
 {
--- a/wctl.c
+++ b/wctl.c
@@ -7,14 +7,22 @@
 char	Ebadwr[]		= "bad rectangle in wctl request";
 char	Ewalloc[]		= "window allocation failed in wctl request";
 
-/* >= Top are disallowed if mouse button is pressed */
+/* >= Top are disallowed if mouse button is pressed.
+ * >= New need a window. */
 enum
 {
+	Screenoffset,
 	New,
 	Resize,
 	Move,
 	Scroll,
 	Noscroll,
+	Border,
+	Noborder,
+	Title,
+	Notitle,
+	Sticky,
+	Nosticky,
 	Set,
 	Top,
 	Bottom,
@@ -25,11 +33,18 @@
 };
 
 static char *cmds[] = {
+	[Screenoffset] = "screenoffset",
 	[New]	= "new",
 	[Resize]	= "resize",
 	[Move]	= "move",
 	[Scroll]	= "scroll",
 	[Noscroll]	= "noscroll",
+	[Border]	= "border",
+	[Noborder]	= "noborder",
+	[Title]	= "title",
+	[Notitle]	= "notitle",
+	[Sticky]	= "sticky",
+	[Nosticky]	= "nosticky",
 	[Set]		= "set",
 	[Top]	= "top",
 	[Bottom]	= "bottom",
@@ -161,8 +176,14 @@
 	cmd.cmd = word(&s, cmds);
 	if(cmd.cmd < 0)
 		goto Lose;
-	if(cmd.cmd == New)
+	switch(cmd.cmd){
+	case Screenoffset:
+		r = ZR;
+		break;
+	case New:
 		r = newrect();
+		break;
+	}
 
 	while((param = word(&s, params)) >= 0){
 		switch(param){	/* special cases */
@@ -247,7 +268,10 @@
 			break;
 		}
 	}
-	cmd.r = rectonscreen(rectaddpt(r, screen->r.min));
+	if(cmd.cmd == Screenoffset)
+		cmd.r = r;
+	else
+		cmd.r = rectonscreen(rectaddpt(r, screen->r.min));
 	while(isspacerune(*s))
 		s++;
 	if(cmd.cmd != New && *s != '\0'){
@@ -266,16 +290,16 @@
 {
 	switch(cmd){
 	case Move:
-		r = rectaddpt(Rect(0,0,Dx(w->img->r),Dy(w->img->r)), r.min);
+		r = rectaddpt(Rect(0,0,Dx(w->frame->r),Dy(w->frame->r)), r.min);
 		if(!goodrect(r))
 			return Ebadwr;
-		if(!eqpt(r.min, w->img->r.min))
+		if(!eqpt(r.min, w->frame->r.min))
 			wmove(w, r.min);
 		break;
 	case Resize:
 		if(!goodrect(r))
 			return Ebadwr;
-		if(!eqrect(r, w->img->r))
+		if(!eqrect(r, w->frame->r))
 			wresize(w, r);
 		break;
 // TODO: these three work somewhat differently in rio
@@ -315,6 +339,31 @@
 		w->scrolling = FALSE;
 		wsendmsg(w, Wakeup);
 		break;
+	case Border:
+		w->noborder &= ~1;
+		wresize(w, w->frame->r);
+		break;
+	case Noborder:
+		w->noborder |= 1;
+		wresize(w, w->frame->r);
+		break;
+	case Title:
+		w->notitle = FALSE;
+		wresize(w, w->frame->r);
+		break;
+	case Notitle:
+		w->notitle = TRUE;
+		wresize(w, w->frame->r);
+		break;
+	case Sticky:
+		w->sticky = TRUE;
+		break;
+	case Nosticky:
+		w->sticky = FALSE;
+		break;
+	case Screenoffset:
+		screenoffset(r.min.x, r.min.y);
+		break;
 	default:
 		return "invalid wctl message";
 	}
@@ -359,7 +408,7 @@
 	if(w == nil)
 		r = ZR;
 	else
-		r = rectsubpt(w->img->r, screen->r.min);
+		r = rectsubpt(w->frame->r, screen->r.min);
 	cmd = parsewctl(data, r);
 	if(cmd.error)
 		return cmd.error;
@@ -370,7 +419,7 @@
 			return "no such window id";
 	}
 
-	if(w == nil && cmd.cmd != New)
+	if(w == nil && cmd.cmd > New)
 		return "command needs to be run within a window";
 
 	switch(cmd.cmd){
--- /dev/null
+++ b/win3.c
@@ -1,0 +1,311 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 19;
+
+enum {
+	ColDefault,
+	ColHilight,
+	ColShadow,
+	ColTitle,
+	ColTitleInact,
+	ColTitleText,
+	ColTitleTextInact,
+
+	ColFrame,
+	ColBorder,
+	ColBorderInact,
+
+	NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[5];
+
+void
+winbtn(Image *img, Rectangle r, Image *icon, int down)
+{
+	draw(img, r, wincolors[ColDefault], nil, ZP);
+	if(down){
+		borderTL(img, r, wincolors[ColShadow]);
+		r = insetrect(r, 1);
+	}else{
+		winborder(img, r, wincolors[ColHilight], wincolors[ColShadow]);
+		r = insetrect(r, 1);
+		borderBR(img, r, wincolors[ColShadow]);
+	}
+
+	r = centerrect(r, icon->r);
+	if(down)
+		r = rectaddpt(r, Pt(1,1));
+	draw(img, r, icon, icon, ZP);
+}
+
+void
+winbtnflat(Image *img, Rectangle r, Image *icon, Image *icondown, int down)
+{
+	if(down){
+		draw(img, r, wincolors[ColShadow], nil, ZP);
+	}else{
+		draw(img, r, wincolors[ColDefault], nil, ZP);
+	}
+
+	r = centerrect(r, icon->r);
+	if(down)
+		icon = icondown;
+	draw(img, r, icon, icon, ZP);
+}
+
+int
+winbtnctl(Image *img, Rectangle r, Image *icon)
+{
+	int over, prevover;
+
+	prevover = 1;
+	winbtn(img, r, icon, 1);
+	while(mctl->buttons){
+		readmouse(mctl);
+		over = ptinrect(mctl->xy, r);
+		if(over != prevover)
+			winbtn(img, r, icon, over);
+		prevover = over;
+	}
+	if(prevover)
+		winbtn(img, r, icon, 0);
+	return ptinrect(mctl->xy, r);
+}
+
+int
+winbtnctlflat(Image *img, Rectangle r, Image *icon, Image *icondown)
+{
+	int over, prevover;
+
+	prevover = 1;
+	winbtnflat(img, r, icon, icondown, 1);
+	while(mctl->buttons){
+		readmouse(mctl);
+		over = ptinrect(mctl->xy, r);
+		if(over != prevover)
+			winbtnflat(img, r, icon, icondown, over);
+		prevover = over;
+	}
+	if(prevover)
+		winbtnflat(img, r, icon, icondown, 0);
+	return ptinrect(mctl->xy, r);
+}
+
+
+
+void
+wdecor(Window *w)
+{
+	if(w->frame == nil)
+		return;
+
+	Rectangle r = w->frame->r;
+
+	if(!w->noborder){
+		border(w->frame, r, bordersz, wincolors[w == focused ? ColBorder : ColBorderInact], ZP);
+		border(w->frame, r, 1, wincolors[ColFrame], ZP);
+		border(w->frame, insetrect(r,3), 1, wincolors[ColFrame], ZP);
+
+		Rectangle br = rectaddpt(Rect(0,0,1,bordersz), r.min);
+		int dx = Dx(r);
+		int dy = Dy(r);
+		int off = bordersz+titlesz-1;
+		draw(w->frame, rectaddpt(br, Pt(off,0)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(off,dy-bordersz)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(dx-1-off,0)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(dx-1-off,dy-bordersz)), wincolors[ColFrame], nil, ZP);
+
+		br = rectaddpt(Rect(0,0,bordersz,1), r.min);
+		draw(w->frame, rectaddpt(br, Pt(0,off)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(dx-bordersz,off)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(0,dy-1-off)), wincolors[ColFrame], nil, ZP);
+		draw(w->frame, rectaddpt(br, Pt(dx-bordersz,dy-1-off)), wincolors[ColFrame], nil, ZP);
+
+		r = insetrect(r, bordersz);
+	}
+
+	if(!w->notitle){
+		r.max.y = r.min.y + titlesz-1;
+		draw(w->frame, r, wincolors[w == focused ? ColTitle : ColTitleInact], nil, ZP);
+		draw(w->frame, Rect(r.min.x,r.max.y,r.max.x,r.max.y+1), wincolors[ColFrame], nil, ZP);
+
+		// menu
+		Rectangle br = Rect(r.min.x,r.min.y,r.min.x+titlesz-1,r.min.y+titlesz-1);
+		winbtnflat(w->frame, br, icons[3], icons[4], 0);
+		border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+		// max/restore
+		br.max.x = r.max.x;
+		br.min.x = br.max.x-titlesz-1;
+		winbtn(w->frame, br, icons[1+w->maximized], 0);
+		border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+		// min
+		br = rectaddpt(br, Pt(-titlesz-2,0));
+		winbtn(w->frame, br, icons[0], 0);
+		border(w->frame, insetrect(br,-1), 1, display->black, ZP);
+
+		int sp = (Dx(r)-stringwidth(font, w->label))/2;
+		Point pt = Pt(r.min.x+sp, r.min.y);
+		string(w->frame, pt, wincolors[w == focused ? ColTitleText : ColTitleTextInact], pt, font, w->label);
+	}
+}
+
+void
+wtitlectl(Window *w)
+{
+	if(mctl->buttons & 7){
+		wraise(w);
+		wfocus(w);
+		if(mctl->buttons & 1) {
+			Rectangle r = w->frame->r;
+			if(!w->noborder)
+				r = insetrect(r, bordersz);
+			Rectangle br = Rect(0,0,titlesz-1,titlesz-1);
+			Rectangle br1 = rectaddpt(br, r.min);
+			Rectangle br2 = rectaddpt(br1, Pt(Dx(r)-titlesz-1, 0));
+			Rectangle br3 = rectaddpt(br2, Pt(-titlesz-2, 0));
+			// hack...
+			if(w->notitle)
+				br1 = br2 = br3 = Rect(0,0,0,0);
+
+			if(ptinrect(mctl->xy, br1)){
+				if(winbtnctlflat(w->frame, br1, icons[3], icons[4]))
+					wdelete(w);
+			}else if(ptinrect(mctl->xy, br2)){
+				if(winbtnctl(w->frame, br2, icons[1+w->maximized])){
+					if(w->maximized)
+						wrestore(w);
+					else
+						wmaximize(w);
+				}
+			}else if(ptinrect(mctl->xy, br3)){
+				if(winbtnctl(w->frame, br3, icons[0]))
+					whide(w);
+			}else if(!w->maximized)
+				grab(w, 1);
+		}
+		if(mctl->buttons & 4)
+			btn3menu();
+	}
+}
+
+
+static char minbtn[] = {
+	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, 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, 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, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 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, 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, 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, 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,
+};
+
+static char maxbtn[] = {
+	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, 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, 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, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char rstbtn[] = {
+	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, 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, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 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, 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, 0, 0, 0, 0,
+};
+
+static char menubtn[] = {
+	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, 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, 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, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+	0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 6, 0,
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 0,
+	0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 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, 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, 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, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+static char menubtninv[] = {
+	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, 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, 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, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+	0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7, 0,
+	0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 0,
+	0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 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, 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, 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, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+void
+inittheme(void)
+{
+	wincolors[ColDefault] = getcolor("button_face", 0xC0C7C8FF);
+	wincolors[ColHilight] = getcolor("button_hilight", 0xFFFFFFFF);
+	wincolors[ColShadow] = getcolor("button_shadow", 0x87888FFF);
+	wincolors[ColTitle] = getcolor("titlebar_active", 0x5787a8FF);
+	wincolors[ColTitleInact] = getcolor("titlebar_inactive", 0xFFFFFFFF);
+	wincolors[ColTitleText] = getcolor("titlebar_text_active", 0xFFFFFFFF);
+	wincolors[ColTitleTextInact] = getcolor("titlebar_text_inactive", 0x000000FF);
+	wincolors[ColFrame] = getcolor("window_frame", 0x000000FF);
+	wincolors[ColBorder] = getcolor("border_active", 0xC0C7C8FF);
+	wincolors[ColBorderInact] = getcolor("border_inactive", 0xFFFFFFFF);
+
+	icons[0] = mkicon(minbtn, 16, 16);
+	icons[1] = mkicon(maxbtn, 16, 16);
+	icons[2] = mkicon(rstbtn, 16, 16);
+	icons[3] = mkicon(menubtn, 16, 16);
+	icons[4] = mkicon(menubtninv, 16, 16);
+}
--- /dev/null
+++ b/win95.c
@@ -1,0 +1,254 @@
+#include "inc.h"
+
+int bordersz = 4;
+int titlesz = 19;
+
+enum {
+	ColDefault,
+	ColLight1,
+	ColLight2,
+	ColDark1,
+	ColDark2,
+	ColTitle,
+	ColTitleInact,
+	ColTitleText,
+	ColTitleTextInact,
+
+	NumWinColors
+};
+
+Image *wincolors[NumWinColors];
+Image *icons[5];
+
+void
+winbtn(Image *img, Rectangle r, Image *icon, int down)
+{
+	if(down){
+		winborder(img, r, wincolors[ColDark2], wincolors[ColLight2]);
+		r = insetrect(r, 1);
+		winborder(img, r, wincolors[ColDark1], wincolors[ColLight1]);
+	}else{
+		winborder(img, r, wincolors[ColLight2], wincolors[ColDark2]);
+		r = insetrect(r, 1);
+		winborder(img, r, wincolors[ColLight1], wincolors[ColDark1]);
+	}
+	r = insetrect(r, 1);
+	draw(img, r, wincolors[ColDefault], nil, ZP);
+
+	r = insetrect(r,-2);
+	if(down)
+		r = rectaddpt(r, Pt(1,1));
+	draw(img, r, icon, icon, ZP);
+}
+
+void
+winframe(Image *img, Rectangle r)
+{
+	winborder(img, r, wincolors[ColLight1], wincolors[ColDark2]);
+	r = insetrect(r, 1);
+	winborder(img, r, wincolors[ColLight2], wincolors[ColDark1]);
+}
+
+int
+winbtnctl(Image *img, Rectangle r, Image *icon)
+{
+	int over, prevover;
+
+	prevover = 1;
+	winbtn(img, r, icon, 1);
+	while(mctl->buttons){
+		readmouse(mctl);
+		over = ptinrect(mctl->xy, r);
+		if(over != prevover)
+			winbtn(img, r, icon, over);
+		prevover = over;
+	}
+	if(prevover)
+		winbtn(img, r, icon, 0);
+	return ptinrect(mctl->xy, r);
+}
+
+void
+wdecor(Window *w)
+{
+	if(w->frame == nil)
+		return;
+
+	Rectangle r = w->frame->r;
+
+	if(!w->noborder){
+		border(w->frame, r, bordersz, wincolors[ColDefault], ZP);
+		winframe(w->frame, r);
+		r = insetrect(r, bordersz);
+	}
+
+	if(!w->notitle){
+		r.max.y = r.min.y + titlesz-1;
+		draw(w->frame, r, wincolors[w == focused ? ColTitle : ColTitleInact], nil, ZP);
+		draw(w->frame, Rect(r.min.x,r.max.y,r.max.x,r.max.y+1), wincolors[ColDefault], nil, ZP);
+
+		// draw buttons
+		Rectangle br = insetrect(r, 2);
+		br.min.x = br.max.x - Dy(br) - 2;
+		winbtn(w->frame, br, icons[3], 0);
+		br = rectaddpt(br, Pt(-Dx(br)-2, 0));
+		winbtn(w->frame, br, icons[1+w->maximized], 0);
+		br = rectaddpt(br, Pt(-Dx(br), 0));
+		winbtn(w->frame, br, icons[0], 0);
+
+		br = rectaddpt(icons[4]->r, insetrect(r,1).min);
+		draw(w->frame, br, icons[4], icons[4], ZP);
+
+		Point pt = Pt(r.min.x + 2 + titlesz-1, r.min.y);
+		string(w->frame, pt, wincolors[w == focused ? ColTitleText : ColTitleTextInact], pt, font, w->label);
+	}
+}
+
+void
+wtitlectl(Window *w)
+{
+	if(mctl->buttons & 7){
+		wraise(w);
+		wfocus(w);
+		if(mctl->buttons & 1) {
+			Rectangle r = w->frame->r;
+			if(!w->noborder)
+				r = insetrect(r, bordersz);
+			r.max.y = r.min.y + titlesz-1;
+			Rectangle br1 = insetrect(r, 2);
+			br1.min.x = br1.max.x - Dy(br1) - 2;
+			Rectangle br2 = rectaddpt(br1, Pt(-Dx(br1)-2, 0));
+			Rectangle br3 = rectaddpt(br1, Pt(-2*Dx(br1)-2, 0));
+			// hack...
+			if(w->notitle)
+				br1 = br2 = br3 = Rect(0,0,0,0);
+
+			if(ptinrect(mctl->xy, br1)){
+				if(winbtnctl(w->frame, br1, icons[3]))
+					wdelete(w);
+			}else if(ptinrect(mctl->xy, br2)){
+				if(winbtnctl(w->frame, br2, icons[1+w->maximized])){
+					if(w->maximized)
+						wrestore(w);
+					else
+						wmaximize(w);
+				}
+			}else if(ptinrect(mctl->xy, br3)){
+				if(winbtnctl(w->frame, br3, icons[0]))
+					whide(w);
+			}else if(!w->maximized)
+				grab(w, 1);
+		}
+		if(mctl->buttons & 4)
+			btn3menu();
+	}
+}
+
+
+
+static char minbtn[] = {
+	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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 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, 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, 0, 0,
+};
+
+static char maxbtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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,
+};
+
+static char rstbtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 1, 1, 1, 1, 1, 1, 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, 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, 0, 0, 0,
+};
+
+static char closebtn[] = {
+	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, 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, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 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, 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,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static char appbtn[] = {
+	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, 0, 0, 0, 0, 0, 0,
+	0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1,
+	0, 0, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1,
+	0, 0, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 1,
+	0, 0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1,
+	0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	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, 0, 0, 0, 0, 0, 0,
+};
+
+void
+inittheme(void)
+{
+	wincolors[ColDefault] = getcolor("3d_face", 0xC0C0C0FF);
+	wincolors[ColLight1] = getcolor("3d_hilight1", 0xDFDFDFFF);
+	wincolors[ColLight2] = getcolor("3d_hilight2", 0xFFFFFFFF);
+	wincolors[ColDark1] = getcolor("3d_shadow1", 0x808080FF);
+	wincolors[ColDark2] = getcolor("3d_shadow2", 0x000000FF);
+	wincolors[ColTitle] = getcolor("titlebar_active", 0x000080FF);
+	wincolors[ColTitleInact] = getcolor("titlebar_inactive", 0x808080FF);
+	wincolors[ColTitleText] = getcolor("titlebar_text_active", 0xFFFFFFFF);
+	wincolors[ColTitleTextInact] = getcolor("titlebar_text_inactive", 0xC0C0C0FF);
+
+	icons[0] = mkicon(minbtn, 16, 14);
+	icons[1] = mkicon(maxbtn, 16, 14);
+	icons[2] = mkicon(rstbtn, 16, 14);
+	icons[3] = mkicon(closebtn, 16, 14);
+	icons[4] = mkicon(appbtn, 16, 16);
+}
--- a/wind.c
+++ b/wind.c
@@ -5,6 +5,8 @@
 int nwindows;
 Window *focused, *cursorwin;
 
+Point screenoff;
+
 static void winthread(void *arg);
 
 static void
@@ -43,31 +45,43 @@
 }
 
 void
-wcalcrects(Window *w)
+wmaximize(Window *w)
 {
-	if(w->noborder)
-		w->contrect = w->img->r;
-	else
-		w->contrect = insetrect(w->img->r, Borderwidth);
-	Rectangle r = insetrect(w->contrect, 1);
-	w->scrollr = r;
-	w->scrollr.max.x = w->scrollr.min.x + 12;
-	w->textr = r;
-	w->textr.min.x = w->scrollr.max.x + 4;
+	w->maximized = 1;
+	w->noborder |= 2;
+	w->origrect = w->frame->r;
+	wresize(w, screen->r);
 }
 
 void
-wdecor(Window *w)
+wrestore(Window *w)
 {
-	if(w->noborder)
-		return;
-	int c = w->holdmode ?
-		w == focused ? TITLEHOLD : LTITLEHOLD :
-		w == focused ? TITLE : LTITLE;
-	border(w->img, w->img->r, Borderwidth, colors[c], ZP);
+	w->maximized = 0;
+	w->noborder &= ~2;
+	wresize(w, w->origrect);
 }
 
 void
+wcalcrects(Window *w, Rectangle r)
+{
+	w->contrect = r;
+	if(!w->noborder)
+		w->contrect = insetrect(w->contrect, bordersz);
+	if(!w->notitle){
+		w->titlerect = w->contrect;
+		w->titlerect.max.y = w->titlerect.min.y + titlesz;
+		w->contrect.min.y += titlesz;
+	}else
+		w->titlerect = ZR;
+
+	r = insetrect(w->contrect, 1);
+	w->scrollr = r;
+	w->scrollr.max.x = w->scrollr.min.x + 12;
+	w->textr = r;
+	w->textr.min.x = w->scrollr.max.x + 4;
+}
+
+void
 wsetcolors(Window *w)
 {
 	int c = w->holdmode ?
@@ -77,21 +91,41 @@
 }
 
 static void
+wfreeimages(Window *w)
+{
+	if(w->frame == nil)
+		return;
+
+	Screen *s = w->content->screen;
+	freeimage(w->content);
+	w->content = nil;
+
+	freescreen(s);
+
+	freeimage(w->frame);
+	w->frame = nil;
+}
+
+static void
 wsetsize(Window *w, Rectangle r)
 {
 	Rectangle hr;
+	Screen *s;
 
-	if(w->img)
-		freeimage(w->img);
+	wfreeimages(w);
 	if(w->hidden){
 		hr = rectaddpt(r, subpt(screen->r.max, r.min));
-		w->img = allocwindow(wscreen, hr, Refbackup, DNofill);
-		originwindow(w->img, r.min, hr.min);
+		w->frame = allocwindow(wscreen, hr, Refbackup, DNofill);
+		originwindow(w->frame, r.min, hr.min);
 	}else
-		w->img = allocwindow(wscreen, r, Refbackup, DNofill);
-	wcalcrects(w);
-	draw(w->img, w->img->r, colors[BACK], nil, ZP);
-	xinit(&w->text, w->textr, w->scrollr, font, w->img, colors);
+		w->frame = allocwindow(wscreen, r, Refbackup, DNofill);
+	s = allocscreen(w->frame, colors[BACK], 0);
+	assert(s);
+	wcalcrects(w, r);
+	w->content = allocwindow(s, w->contrect, Refnone, DNofill);
+	assert(w->content);
+	draw(w->frame, w->frame->r, colors[BACK], nil, ZP);
+	xinit(&w->text, w->textr, w->scrollr, font, w->content, colors);
 }
 
 static int id = 1;
@@ -109,6 +143,7 @@
 	w->dir = estrdup(startdir);
 	w->hidden = hidden;
 	w->scrolling = scrolling;
+	w->notitle = notitle;	// TODO: argument?
 	wsetsize(w, r);
 	wdecor(w);
 	wlistpushfront(w);
@@ -117,7 +152,6 @@
 	windows[nwindows++] = w;
 
 	w->mc.c = chancreate(sizeof(Mouse), 16);
-
 	w->gone = chancreate(sizeof(int), 0);
 	w->kbd = chancreate(sizeof(char*), 16);
 	w->ctl = chancreate(sizeof(int), 0);
@@ -175,12 +209,7 @@
 			memmove(&windows[i], &windows[i+1], (nwindows-i)*sizeof(Window*));
 			break;
 		}
-	if(w->img){
-		/* rio does this, not sure if useful */
-		originwindow(w->img, w->img->r.min, screen->r.max);
-		freeimage(w->img);
-	}
-	w->img = nil;
+	wfreeimages(w);
 	flushimage(display, 1);
 }
 
@@ -231,7 +260,7 @@
 	Window *w;
 
 	for(w = topwin; w; w = w->lower)
-		if(!w->hidden && ptinrect(pt, w->img->r))
+		if(!w->hidden && ptinrect(pt, w->frame->r))
 			return w;
 	return nil;
 }
@@ -248,6 +277,7 @@
 {
 	free(w->label);
 	w->label = estrdup(label);
+	wdecor(w);
 }
 
 void
@@ -256,9 +286,9 @@
 	int i, n;
 	char err[ERRMAX];
 	
-	n = snprint(w->name, sizeof(w->name)-2, "%s.%d.%d", w->noborder ? "noborder" : "window", w->id, w->namecount++);
+	n = snprint(w->name, sizeof(w->name)-2, "%s.%d.%d", "noborder", w->id, w->namecount++);
 	for(i='A'; i<='Z'; i++){
-		if(nameimage(w->img, w->name, 1) > 0)
+		if(nameimage(w->content, w->name, 1) > 0)
 			return;
 		errstr(err, sizeof err);
 		if(strcmp(err, "image name in use") != 0)
@@ -314,7 +344,7 @@
 	Window *w;
 	for(w = bottomwin; w; w = w->higher)
 		if(!w->hidden)
-			topwindow(w->img);
+			topwindow(w->frame);
 }
 
 void
@@ -337,14 +367,14 @@
 	 *
 	 * We don't care if we're handling resizing ourselves though */
 
-	if(w->mouseopen){
-		delta = subpt(pos, w->img->r.min);
-		wresize(w, rectaddpt(w->img->r, delta));
+	if(1 || w->mouseopen){
+		delta = subpt(pos, w->frame->r.min);
+		wresize(w, rectaddpt(w->frame->r, delta));
 	}else{
-		originwindow(w->img, pos, w->hidden ? screen->r.max : pos);
+		originwindow(w->frame, pos, w->hidden ? screen->r.max : pos);
 		if(w != topwin && !w->hidden)
 			worder();
-		wcalcrects(w);
+		wcalcrects(w, w->frame->r);
 		xsetrects(&w->text, w->textr, w->scrollr);
 		wsendmsg(w, Moved);
 	}
@@ -355,7 +385,7 @@
 {
 	wlistremove(w);
 	wlistpushfront(w);
-	topwindow(w->img);
+	topwindow(w->frame);
 	flushimage(display, 1);
 }
 
@@ -364,7 +394,8 @@
 {
 	wlistremove(w);
 	wlistpushback(w);
-	bottomwindow(w->img);
+	bottomwindow(w->frame);
+	bottomwindow(fakebg);
 	flushimage(display, 1);
 }
 
@@ -403,9 +434,13 @@
 	wfocuschanged(focused);
 }
 
+/* Take away focus but also get rid of the window visually.
+ * For hiding/deleting */
 void
 wunfocus(Window *w)
 {
+	if(!w->deleted)
+		originwindow(w->frame, w->frame->r.min, screen->r.max);
 	if(w == focused)
 		wfocus(nil);
 }
@@ -419,7 +454,6 @@
 	wunfocus(w);
 	w->hidden = TRUE;
 	w->wctlready = TRUE;
-	originwindow(w->img, w->img->r.min, screen->r.max);
 	wsendmsg(w, Wakeup);
 	wrelease(w);
 	return 1;
@@ -433,7 +467,7 @@
 	incref(w);
 	w->hidden = FALSE;
 	w->wctlready = TRUE;
-	originwindow(w->img, w->img->r.min, w->img->r.min);
+	originwindow(w->frame, w->frame->r.min, w->frame->r.min);
 	wraise(w);
 	wfocus(w);
 	wrelease(w);
@@ -753,7 +787,7 @@
 		/* take over window again */
 		if(w->deleted)
 			break;
-		draw(w->img, w->img->r, x->cols[BACK], nil, ZP);
+		draw(w->content, w->content->r, x->cols[BACK], nil, ZP);
 		wdecor(w);
 		xfullredraw(&w->text);
 		break;
@@ -948,7 +982,8 @@
 			w->wctlready = FALSE;
 			recv(fsc, &pair);
 			pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %11s %11s ",
-				w->img->r.min.x, w->img->r.min.y, w->img->r.max.x, w->img->r.max.y,
+				w->frame->r.min.x, w->frame->r.min.y,
+				w->frame->r.max.x, w->frame->r.max.y,
 				w == focused ? "current" : "notcurrent",
 				w->hidden ? "hidden" : "visible");
 			send(fsc, &pair);
@@ -959,7 +994,7 @@
 			break;
 
 		case AComplete:
-			if(w->img!=nil){
+			if(w->frame){
 				if(!comp->advance)
 					showcandidates(w, comp);
 				if(comp->advance){
@@ -1064,4 +1099,31 @@
 	}
 
 	return pid;
+}
+
+void
+screenoffset(int offx, int offy)
+{
+	Window *w;
+	Point off, delta;
+
+	off = Pt(offx, offy);
+	delta = subpt(off, screenoff);
+	screenoff = off;
+	for(w = bottomwin; w; w = w->higher){
+		if(w->sticky){
+			/* Don't move but cause resize event because
+			 * program may want some kind of notification */
+			wmove(w, w->frame->r.min);
+			continue;
+		}
+		if(w->maximized){
+			wrestore(w);
+			wmove(w, subpt(w->frame->r.min, delta));
+			wmaximize(w);
+		}else
+			wmove(w, subpt(w->frame->r.min, delta));
+	}
+
+	flushimage(display, 1);
 }