Browse Source

The initial commit of jay desktop (#798)

The goal is to have an easy graphical interface closer to today's conventions.

This is a very early stage.


Signed-off-by: fuchicar <rafita.fernandez@gmail.com>
Rafa 6 years ago
parent
commit
02d00b69f5

+ 171 - 0
sys/src/cmd/jay/config.c

@@ -0,0 +1,171 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+initdefaultconfig(){
+	jayconfig = malloc(sizeof(Jayconfig));
+	jayconfig->taskPanelColor = 0x8C7160FF;
+
+	jayconfig->mainMenuColor = 0x363533FF;
+	jayconfig->mainMenuHooverColor = 0x36A4BFFF;
+
+	jayconfig->windowTitleColor = 0x4D4D4DFF;
+	jayconfig->windowTitleFontColor = DWhite;
+	jayconfig->windowInTopBorder = 0xC4CAC8FF;
+	jayconfig->windowInBottomBorder = DPalegreygreen;
+	jayconfig->windowSelectedColor = 0xCCCCCCFF;
+	jayconfig->windowScrollBarFrontColor = DWhite;//0x999999FF;
+	jayconfig->windowTextCursorColor = DWhite;
+	jayconfig->windowBackgroundColor = DBlack;
+	jayconfig->windowBackTextColor = 0x666666FF;
+	jayconfig->windowFrontTextColor = DWhite;
+
+	jayconfig->backgroundColor = DBlack;
+	jayconfig->backgroundimgpath = "/usr/harvey/lib/background.img";
+
+	jayconfig->menuBackColor = DPalegreyblue;
+	jayconfig->menuHighColor = DGreyblue;
+	jayconfig->menuBorderColor = jayconfig->menuHighColor;
+	jayconfig->menuSelTextColor = DWhite;
+	jayconfig->menuTextColor = DWhite;
+}
+
+char *
+getjayconfig(){
+  char s[1024];
+  sprint(s,"taskPanelColor=0x%08X\n", jayconfig->taskPanelColor);
+  sprint(s, "%smainMenuColor=0x%08X\n", s, jayconfig->mainMenuColor);
+  sprint(s, "%smainMenuHooverColor=0x%08X\n", s, jayconfig->mainMenuHooverColor);
+  sprint(s, "%swindowTitleColor=0x%08X\n", s, jayconfig->windowTitleColor);
+  sprint(s, "%swindowTitleFontColor=0x%08X\n", s, jayconfig->windowTitleFontColor);
+  sprint(s, "%swindowInTopBorder=0x%08X\n", s, jayconfig->windowInTopBorder);
+  sprint(s, "%swindowInBottomBorder=0x%08X\n", s, jayconfig->windowInBottomBorder);
+  sprint(s, "%swindowSelectedColor=0x%08X\n", s, jayconfig->windowSelectedColor);
+  sprint(s, "%swindowScrollBarFrontColor=0x%08X\n", s, jayconfig->windowScrollBarFrontColor);
+  sprint(s, "%swindowTextCursorColor=0x%08X\n", s, jayconfig->windowTextCursorColor);
+  sprint(s, "%swindowBackgroundColor=0x%08X\n", s, jayconfig->windowBackgroundColor);
+  sprint(s, "%swindowBackTextColor=0x%08X\n", s, jayconfig->windowBackTextColor);
+  sprint(s, "%swindowFrontTextColor=0x%08X\n", s, jayconfig->windowFrontTextColor);
+  sprint(s, "%sbackgroundColor=0x%08X\n", s, jayconfig->backgroundColor);
+	sprint(s, "%sbackgroundimgpath=%s\n)", s, jayconfig->backgroundimgpath);
+	sprint(s, "%smenuBackColor=0x%08X\n", s, jayconfig->menuBackColor);
+	sprint(s, "%smenuHighColor=0x%08X\n", s, jayconfig->menuHighColor);
+	sprint(s, "%smenuBorderColor=0x%08X\n", s, jayconfig->menuBorderColor);
+	sprint(s, "%smenuSelTextColor=0x%08X\n", s, jayconfig->menuSelTextColor);
+	sprint(s, "%smenuTextColor=0x%08X\n", s, jayconfig->menuTextColor);
+
+  return estrdup(s);
+}
+
+static uint32_t
+getuint32property(char *p){
+  char *s, aux[11];
+  s = p;
+  while(*s == ' ' && *s != '\0'){
+    s++;
+  }
+  if (*s == '\0')
+    return 0;
+  strncpy(aux, s, 10);
+  aux[11] = '\0';
+  return strtoul(aux, nil, 0);
+}
+
+static char *
+getstringproperty(char *p){
+	char *s, aux[1024];
+	s = p;
+	while(*s == ' ' && *s != '\0'){
+    s++;
+  }
+	if (*s == '\0')
+    return nil;
+	strncpy(aux, s, s - p - 1);
+	aux[s-p]='\0';
+	return strdup(aux);
+}
+
+void
+setconfigproperty(char *p) {
+  char *s, *e, aux[30];
+  int size;
+  s = p;
+  while(*s == ' '&& *s != '\0'){
+    s++;
+  }
+  e = s + 1;
+  while(*e != '=' && *e != ' ' && *e != '\0'){
+    e++;
+  }
+  size = e - s;
+  strncpy(aux, s, size);
+  aux[size] = '\0';
+  e++;
+
+  if (strcmp("taskPanelColor", aux)==0){
+    jayconfig->taskPanelColor = getuint32property(e);
+  } else if (strcmp("mainMenuColor", aux)==0) {
+    jayconfig->mainMenuColor = getuint32property(e);
+  } else if (strcmp("mainMenuHooverColor", aux)==0) {
+    jayconfig->mainMenuHooverColor = getuint32property(e);
+  } else if (strcmp("windowTitleColor", aux)==0) {
+    jayconfig->windowTitleColor = getuint32property(e);
+  } else if (strcmp("windowTitleFontColor", aux)==0) {
+    jayconfig->windowTitleFontColor = getuint32property(e);
+  } else if (strcmp("windowInTopBorder", aux)==0) {
+    jayconfig->windowInTopBorder = getuint32property(e);
+  } else if (strcmp("windowInBottomBorder", aux)==0) {
+    jayconfig->windowInBottomBorder = getuint32property(e);
+  } else if (strcmp("windowSelectedColor", aux)==0) {
+    jayconfig->windowSelectedColor = getuint32property(e);
+  } else if (strcmp("windowScrollBarFrontColor", aux)==0) {
+    jayconfig->windowScrollBarFrontColor = getuint32property(e);
+  } else if (strcmp("windowTextCursorColor", aux)==0) {
+    jayconfig->windowTextCursorColor = getuint32property(e);
+  } else if (strcmp("windowBackgroundColor", aux)==0) {
+    jayconfig->windowBackgroundColor = getuint32property(e);
+  } else if (strcmp("windowBackTextColor", aux)==0) {
+    jayconfig->windowBackTextColor = getuint32property(e);
+  } else if (strcmp("windowFrontTextColor", aux)==0) {
+    jayconfig->windowFrontTextColor = getuint32property(e);
+  } else if (strcmp("backgroundColor", aux)==0) {
+    jayconfig->backgroundColor = getuint32property(e);
+  } else if (strcmp("backgroundimgpath", aux)==0) {
+		jayconfig->backgroundimgpath = getstringproperty(e);
+	} else if (strcmp("menuBackColor", aux)==0) {
+    jayconfig->menuBackColor = getuint32property(e);
+  } else if (strcmp("menuHighColor", aux)==0) {
+    jayconfig->menuHighColor = getuint32property(e);
+  } else if (strcmp("menuBorderColor", aux)==0) {
+    jayconfig->menuBorderColor = getuint32property(e);
+  } else if (strcmp("menuSelTextColor", aux)==0) {
+    jayconfig->menuSelTextColor = getuint32property(e);
+  } else if (strcmp("menuTextColor", aux)==0) {
+    jayconfig->menuTextColor = getuint32property(e);
+  }
+}
+
+void
+setjayconfig(char *conf) {
+  char *s, *e, *aux;
+  int size;
+  for(s = conf; *s != '\0'; s++){
+    for(e = s; *e != '\n' && *e != '\0'; e++){}
+    size = e - s;
+    aux = emalloc(size);
+    strncpy(aux, s, size);
+    *(aux + size - 1) = '\0';
+    setconfigproperty(aux);
+    free(aux);
+    s = e;
+  }
+}

+ 461 - 0
sys/src/cmd/jay/dat.h

@@ -0,0 +1,461 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+enum
+{
+	Qdir,			/* /dev for this window */
+	Qcons,
+	Qconsctl,
+	Qcursor,
+	Qwdir,
+	Qwinid,
+	Qwinname,
+	Qjayctl,
+	Qkbdin,
+	Qlabel,
+	Qmouse,
+	Qnew,
+	Qscreen,
+	Qsnarf,
+	Qtext,
+	Qwctl,
+	Qwindow,
+	Qwsys,		/* directory of window directories */
+	Qwsysdir,		/* window directory, child of wsys */
+
+	QMAX,
+};
+
+enum
+{
+	Kscrolloneup = KF|0x20,
+	Kscrollonedown = KF|0x21,
+};
+
+#define	STACK	8192
+
+typedef	struct	Consreadmesg Consreadmesg;
+typedef	struct	Conswritemesg Conswritemesg;
+typedef	struct	Stringpair Stringpair;
+typedef	struct	Dirtab Dirtab;
+typedef	struct	Fid Fid;
+typedef	struct	Filsys Filsys;
+typedef	struct	Mouseinfo	Mouseinfo;
+typedef	struct	Mousereadmesg Mousereadmesg;
+typedef	struct	Mousestate	Mousestate;
+typedef	struct	Ref Ref;
+typedef	struct	Timer Timer;
+typedef	struct	Wctlmesg Wctlmesg;
+typedef	struct	Window Window;
+typedef	struct	Xfid Xfid;
+typedef	struct TPanel TPanel;
+typedef struct MenuEntry MenuEntry;
+typedef struct StartMenu StartMenu;
+typedef struct Jayconfig Jayconfig;
+
+enum
+{
+	Selborder		= 4,		/* border of selected window */
+	Unselborder	= 1,		/* border of unselected window */
+	Scrollwid 		= 12,		/* width of scroll bar */
+	Scrollgap 		= 4,		/* gap right of scroll bar */
+	BIG			= 3,		/* factor by which window dimension can exceed screen */
+	TRUE		= 1,
+	FALSE		= 0,
+};
+
+#define	QID(w,q)	((w<<8)|(q))
+#define	WIN(q)	((((uint32_t)(q).path)>>8) & 0xFFFFFF)
+#define	FILE(q)	(((uint32_t)(q).path) & 0xFF)
+
+enum	/* control messages */
+{
+	Wakeup,
+	Reshaped,
+	Moved,
+	Refresh,
+	Movemouse,
+	Rawon,
+	Rawoff,
+	Holdon,
+	Holdoff,
+	Deleted,
+	Exited,
+};
+
+enum /* Position */
+{
+	Up,
+	Down,
+	Left,
+	Right,
+};
+
+struct Wctlmesg
+{
+	int		type;
+	Rectangle	r;
+	Image	*image;
+};
+
+struct Conswritemesg
+{
+	Channel	*cw;		/* chan(Stringpair) */
+};
+
+struct Consreadmesg
+{
+	Channel	*c1;		/* chan(tuple(char*, int) == Stringpair) */
+	Channel	*c2;		/* chan(tuple(char*, int) == Stringpair) */
+};
+
+struct Mousereadmesg
+{
+	Channel	*cm;		/* chan(Mouse) */
+};
+
+struct Stringpair	/* rune and nrune or byte and nbyte */
+{
+	void		*s;
+	int		ns;
+};
+
+struct Mousestate
+{
+	Mouse mouse;
+	uint32_t	counter;	/* serial no. of mouse event */
+};
+
+struct Mouseinfo
+{
+	Mousestate	queue[16];
+	int	ri;	/* read index into queue */
+	int	wi;	/* write index */
+	uint32_t	counter;	/* serial no. of last mouse event we received */
+	uint32_t	lastcounter;	/* serial no. of last mouse event sent to client */
+	int	lastb;	/* last button state we received */
+	unsigned char	qfull;	/* filled the queue; no more recording until client comes back */
+};
+
+struct TPanel{
+	Rectangle r; // Total TPanel
+	Image *sb; // StartButton size
+	Image *i;
+	int position;
+};
+
+struct MenuEntry {
+  char *name;
+  char *action;
+  Image *i; //normal
+  Image *ih; //hoover
+  StartMenu *submenu;
+};
+
+struct StartMenu {
+  int numEntries;
+  MenuEntry *entries[50];
+  Image *i;
+};
+
+struct Window
+{
+	Ref Ref;
+	QLock QLock;
+	Frame Frame;
+	Image		*i; //functional part
+	Image		*t; //title part
+	Rectangle r; //i+t
+	Rectangle bc; //button close
+	Rectangle bmax; //button maximize
+	Rectangle bmin; //button minimize
+	Mousectl		mc;
+	Mouseinfo	mouse;
+	Channel		*ck;			/* chan(Rune[10]) */
+	Channel		*cctl;		/* chan(Wctlmesg)[20] */
+	Channel		*conswrite;	/* chan(Conswritemesg) */
+	Channel		*consread;	/* chan(Consreadmesg) */
+	Channel		*mouseread;	/* chan(Mousereadmesg) */
+	Channel		*wctlread;		/* chan(Consreadmesg) */
+	uint			nr;			/* number of runes in window */
+	uint			maxr;		/* number of runes allocated in r */
+	Rune			*run;
+	uint			nraw;
+	Rune			*raw;
+	uint			org;
+	uint			q0;
+	uint			q1;
+	uint			qh;
+	int			id;
+	char			name[32];
+	uint			namecount;
+	Rectangle		scrollr;
+	/*
+	 * Jay once used originwindow, so screenr could be different from i->r.
+	 * Now they're always the same but the code doesn't assume so.
+	*/
+	Rectangle		screenr;	/* screen coordinates of window */
+	int			resized;
+	int			wctlready;
+	Rectangle		lastsr;
+	int			topped;
+	int			notefd;
+	unsigned char		scrolling;
+	Cursor		cursor;
+	Cursor		*cursorp;
+	unsigned char		holding;
+	unsigned char		rawing;
+	unsigned char		ctlopen;
+	unsigned char		wctlopen;
+	unsigned char		deleted;
+	unsigned char		mouseopen;
+	char			*label;
+	int			pid;
+	char			*dir;
+	int		isVisible;
+
+	Rectangle originalr;
+	int maximized;
+};
+
+struct Jayconfig{
+	// Task Panel Config
+	uint32_t taskPanelColor;
+
+	// Main Menu Config
+	uint32_t mainMenuColor;
+	uint32_t mainMenuHooverColor;
+
+	// Window Config
+	uint32_t windowTitleColor;
+	uint32_t windowTitleFontColor;
+	uint32_t windowBackgroundColor;
+	uint32_t windowInTopBorder;
+	uint32_t windowInBottomBorder;
+	uint32_t windowSelectedColor;
+	uint32_t windowScrollBarFrontColor;
+	uint32_t windowTextCursorColor;
+	uint32_t windowBackTextColor;
+	uint32_t windowFrontTextColor;
+
+	//Background
+	uint32_t backgroundColor;
+	char *backgroundimgpath;
+
+	//Menu
+	uint32_t  menuBackColor;
+	uint32_t menuHighColor;
+	uint32_t menuBorderColor;
+	uint32_t menuTextColor;
+	uint32_t menuSelTextColor;
+};
+
+int		winborder(Window*, Point);
+Image *winspace(Window *w);
+void		winctl(void*);
+void		winshell(void*);
+Window*	wlookid(int);
+Window*	wmk(Image*, Mousectl*, Channel*, Channel*, int);
+Window*	wpointto(Point);
+Window*	wtop(Point);
+void		wtopme(Window*);
+void		wbottomme(Window*);
+char*	wcontents(Window*, int*);
+int		wbswidth(Window*, Rune);
+int		wclickmatch(Window*, int, int, int, uint*);
+int		wclose(Window*);
+int		wctlmesg(Window*, int, Rectangle, Image*);
+int		wctlmesg(Window*, int, Rectangle, Image*);
+uint		wbacknl(Window*, uint, uint);
+uint		winsert(Window*, Rune*, int, uint);
+void		waddraw(Window*, Rune*, int);
+void		wborder(Window*, int);
+void		wclosewin(Window*);
+void		wcurrent(Window*);
+void		wcut(Window*);
+void		wdelete(Window*, uint, uint);
+void		wdoubleclick(Window*, uint*, uint*);
+void		wfill(Window*);
+void		wframescroll(Window*, int);
+void		wkeyctl(Window*, Rune);
+void		wmousectl(Window*);
+void		wmovemouse(Window*, Point);
+void		wpaste(Window*);
+void		wplumb(Window*);
+void		wrefresh(Window*, Rectangle);
+void		wrepaint(Window*);
+void		wresize(Window*, Image*, int);
+void		wscrdraw(Window*);
+void		wscroll(Window*, int);
+void		wselect(Window*);
+void		wsendctlmesg(Window*, int, Rectangle, Image*);
+void		wsetcursor(Window*, int);
+void		wsetname(Window*);
+void		wsetorigin(Window*, uint, int);
+void		wsetpid(Window*, int, int);
+void		wsetselect(Window*, uint, uint);
+void		wshow(Window*, uint);
+void		wsnarf(Window*);
+void 		wscrsleep(Window*, uint);
+void		wsetcols(Window*);
+void		wcurrentnext();
+void		whooverbutton(Point p, Window *w);
+Rectangle windowMinusTitle(Rectangle r, Image *t);
+
+struct Dirtab
+{
+	char		*name;
+	unsigned char	type;
+	uint		qid;
+	uint		perm;
+};
+
+struct Fid
+{
+	int		fid;
+	int		busy;
+	int		open;
+	int		mode;
+	Qid		qid;
+	Window	*w;
+	Dirtab	*dir;
+	Fid		*next;
+	int		nrpart;
+	unsigned char	rpart[UTFmax];
+};
+
+struct Xfid
+{
+		Ref Ref;
+		Xfid		*next;
+		Xfid		*free;
+		Fcall Fcall;
+		Channel	*c;	/* chan(void(*)(Xfid*)) */
+		Fid		*f;
+		unsigned char	*buf;
+		Filsys	*fs;
+		QLock	active;
+		int		flushing;	/* another Xfid is trying to flush us */
+		int		flushtag;	/* our tag, so flush can find us */
+		Channel	*flushc;	/* channel(int) to notify us we're being flushed */
+};
+
+Channel*	xfidinit(void);
+void		xfidctl(void*);
+void		xfidflush(Xfid*);
+void		xfidattach(Xfid*);
+void		xfidopen(Xfid*);
+void		xfidclose(Xfid*);
+void		xfidread(Xfid*);
+void		xfidwrite(Xfid*);
+
+enum
+{
+	Nhash	= 16,
+};
+
+struct Filsys
+{
+		int		cfd;
+		int		sfd;
+		int		pid;
+		char		*user;
+		Channel	*cxfidalloc;	/* chan(Xfid*) */
+		Fid		*fids[Nhash];
+};
+
+Filsys*	filsysinit(Channel*);
+int		filsysmount(Filsys*, int);
+Xfid*		filsysrespond(Filsys*, Xfid*, Fcall*, char*);
+void		filsyscancel(Xfid*);
+
+void		wctlproc(void*);
+void		wctlthread(void*);
+
+void		deletetimeoutproc(void*);
+
+struct Timer
+{
+	int		dt;
+	int		cancel;
+	Channel	*c;	/* chan(int) */
+	Timer	*next;
+};
+
+Font		*font;
+Mousectl	*mousectl;
+Mouse	*mouse;
+Keyboardctl	*keyboardctl;
+Display	*display;
+Image	*view;
+Screen	*wscreen;
+Cursor	defcursor;
+Cursor	boxcursor;
+Cursor	crosscursor;
+Cursor	sightcursor;
+Cursor	whitearrow;
+Cursor	query;
+Cursor	*corners[9];
+Image	*background;
+Image	*lightgrey;
+Image	*red;
+Window	**window;
+Window	*wkeyboard;	/* window of simulated keyboard */
+int		nwindow;
+int		snarffd;
+Window	*input;
+QLock	all;			/* BUG */
+Filsys	*filsys;
+Window	*hidden[100];
+int		nhidden;
+int		nsnarf;
+Rune*	snarf;
+int		scrolling;
+int		maxtab;
+Channel*	winclosechan;
+Channel*	deletechan;
+char		*startdir;
+int		sweeping;
+int		wctlfd;
+char		srvpipe[64];
+char		srvwctl[64];
+int		errorshouldabort;
+int		menuing;		/* menu action is pending; waiting for window to be indicated */
+int		snarfversion;	/* updated each time it is written */
+int		messagesize;		/* negotiated in 9P version setup */
+
+// Window parts
+Image *closebutton, *closebuttonhoover;
+Image *minimizebutton, *minimizebuttonhoover;
+Image *maximizebutton, *maximizebuttonhoover;
+
+
+TPanel *taskPanel;
+Rectangle windowspace;
+
+void		initpanel();
+void		redrawpanel();
+void		clickpanel(Point p, TPanel *tp);
+
+StartMenu		*loadStartMenu(TPanel *p);
+void		freeStartMenu(StartMenu *sm);
+void		hoovermenu(StartMenu *sm, Point p);
+void		execmenu(StartMenu *sm, Point p);
+
+//Colors
+Image *blk;
+Image *wht;
+
+//Config
+Jayconfig *jayconfig;
+
+void initdefaultconfig();
+char *getjayconfig();
+void setjayconfig(char *conf);
+void jayredraw(void);

+ 301 - 0
sys/src/cmd/jay/data.c

@@ -0,0 +1,301 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+Cursor crosscursor = {
+	{-7, -7},
+	{0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
+	 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, },
+	{0x01, 0x80, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
+	 0x02, 0x40, 0x02, 0x40, 0x7E, 0x7E, 0x80, 0x01,
+	 0x80, 0x01, 0x7E, 0x7E, 0x02, 0x40, 0x02, 0x40,
+	 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x01, 0x80, },
+};
+
+Cursor boxcursor = {
+	{-7, -7},
+	{0x00, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x21, 0x84, 0x7E, 0x7E,
+	 0x7E, 0x7E, 0x21, 0x84, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x00, },
+	{0x01, 0x80, 0x02, 0x40, 0x04, 0x20, 0x06, 0x60,
+	 0x02, 0x40, 0x32, 0x4C, 0x5E, 0x7A, 0x80, 0x01,
+	 0x80, 0x01, 0x5E, 0x7A, 0x32, 0x4C, 0x02, 0x40,
+	 0x06, 0x60, 0x04, 0x20, 0x02, 0x40, 0x01, 0x80,}
+};
+
+Cursor sightcursor = {
+	{-7, -7},
+	{0x00, 0x00, 0x78, 0x1E, 0x40, 0x02, 0x40, 0x02,
+	 0x41, 0x82, 0x01, 0x80, 0x01, 0x80, 0x0F, 0xF0,
+	 0x0F, 0xF0, 0x01, 0x80, 0x01, 0x80, 0x41, 0x82,
+	 0x40, 0x02, 0x40, 0x02, 0x78, 0x1E, 0x00, 0x00, },
+	{0x78, 0x1E, 0x84, 0x21, 0xB8, 0x1D, 0xA1, 0x85,
+	 0xA2, 0x45, 0x42, 0x42, 0x0E, 0x70, 0x10, 0x08,
+	 0x10, 0x08, 0x0E, 0x70, 0x42, 0x42, 0xA2, 0x45,
+	 0xA1, 0x85, 0xB8, 0x1D, 0x84, 0x21, 0x78, 0x1E, },
+};
+
+Cursor whitearrow = {
+	{-2, 0},
+	{0x20, 0x00, 0x30, 0x00, 0x28, 0x00, 0x24, 0x00,
+	 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40,
+	 0x20, 0x20, 0x20, 0x10, 0x21, 0xF8, 0x22, 0x00,
+	 0x24, 0x00, 0x28, 0x00, 0x30, 0x00, 0x20, 0x00, },
+	{0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00,
+	 0x1C, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x1F, 0x80,
+	 0x1F, 0xC0, 0x1F, 0xE0, 0x1E, 0x00, 0x1C, 0x00,
+	 0x18, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, },
+};
+
+Cursor defcursor = {
+	{-2, 0},
+	{0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x00,
+	 0x1C, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x1F, 0x80,
+	 0x1F, 0xC0, 0x1F, 0xE0, 0x1E, 0x00, 0x1C, 0x00,
+	 0x18, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, },
+	{0x20, 0x00, 0x30, 0x00, 0x28, 0x00, 0x24, 0x00,
+	 0x22, 0x00, 0x21, 0x00, 0x20, 0x80, 0x20, 0x40,
+	 0x20, 0x20, 0x20, 0x10, 0x21, 0xF8, 0x22, 0x00,
+	 0x24, 0x00, 0x28, 0x00, 0x30, 0x00, 0x20, 0x00, },
+};
+
+Cursor query = {
+	{-7,-7},
+	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
+	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
+	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
+	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
+	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
+	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
+	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
+	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor tl = {
+	{-6, -6},
+	{0x00, 0x00, 0x78, 0x00, 0x60, 0x00, 0x50, 0x00,
+	 0x48, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x12,
+	 0x00, 0x0A, 0x00, 0x06, 0x00, 0x1E, 0x00, 0x00, },
+	{0xFC, 0x00, 0x84, 0x00, 0x9C, 0x00, 0xAC, 0x00,
+	 0xB6, 0x00, 0xFB, 0x00, 0x0D, 0x00, 0x07, 0x00,
+	 0x00, 0xE0, 0x00, 0xB0, 0x00, 0xDF, 0x00, 0x6D,
+	 0x00, 0x35, 0x00, 0x39, 0x00, 0x21, 0x00, 0x3F, },
+};
+
+Cursor t = {
+	{-7, -8},
+	{0x00, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00,
+	 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x00, },
+	{0x01, 0x80, 0x02, 0x40, 0x04, 0x20, 0x06, 0x60,
+	 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x01, 0x80,
+	 0x01, 0x80, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
+	 0x06, 0x60, 0x04, 0x20, 0x02, 0x40, 0x01, 0x80, }
+};
+
+Cursor tr = {
+	{-9, -6},
+	{0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x0A,
+	 0x00, 0x12, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00,
+	 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x48, 0x00,
+	 0x50, 0x00, 0x60, 0x00, 0x78, 0x00, 0x00, 0x00, },
+	{0x00, 0x3F, 0x00, 0x21, 0x00, 0x39, 0x00, 0x35,
+	 0x00, 0x6D, 0x00, 0xDF, 0x00, 0xB0, 0x00, 0xE0,
+	 0x07, 0x00, 0x0D, 0x00, 0xFB, 0x00, 0xB6, 0x00,
+	 0xAC, 0x00, 0x9C, 0x00, 0x84, 0x00, 0xFC, 0x00, },
+};
+
+Cursor r = {
+	{-8, -7},
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x7E, 0x7E,
+	 0x7E, 0x7E, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x30, 0x0C, 0x5E, 0x7A, 0x81, 0x81,
+	 0x81, 0x81, 0x5E, 0x7A, 0x30, 0x0C, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor br = {
+	{-9, -9},
+	{0x00, 0x00, 0x78, 0x00, 0x60, 0x00, 0x50, 0x00,
+	 0x48, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x12,
+	 0x00, 0x0A, 0x00, 0x06, 0x00, 0x1E, 0x00, 0x00, },
+	{0xFC, 0x00, 0x84, 0x00, 0x9C, 0x00, 0xAC, 0x00,
+	 0xB6, 0x00, 0xFB, 0x00, 0x0D, 0x00, 0x07, 0x00,
+	 0x00, 0xE0, 0x00, 0xB0, 0x00, 0xDF, 0x00, 0x6D,
+	 0x00, 0x35, 0x00, 0x39, 0x00, 0x21, 0x00, 0x3F, },
+};
+
+Cursor b = {
+	{-7, -7},
+	{0x00, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80,
+	 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00,
+	 0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+	 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x00, },
+	{0x01, 0x80, 0x02, 0x40, 0x04, 0x20, 0x06, 0x60,
+	 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x01, 0x80,
+	 0x01, 0x80, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
+	 0x06, 0x60, 0x04, 0x20, 0x02, 0x40, 0x01, 0x80, }
+};
+
+Cursor bl = {
+	{-6, -9},
+	{0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x0A,
+	 0x00, 0x12, 0x00, 0x20, 0x00, 0x40, 0x00, 0x00,
+	 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x48, 0x00,
+	 0x50, 0x00, 0x60, 0x00, 0x78, 0x00, 0x00, 0x00, },
+	{0x00, 0x3F, 0x00, 0x21, 0x00, 0x39, 0x00, 0x35,
+	 0x00, 0x6D, 0x00, 0xDF, 0x00, 0xB0, 0x00, 0xE0,
+	 0x07, 0x00, 0x0D, 0x00, 0xFB, 0x00, 0xB6, 0x00,
+	 0xAC, 0x00, 0x9C, 0x00, 0x84, 0x00, 0xFC, 0x00, }
+};
+
+Cursor l = {
+	{-7, -7},
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x7E, 0x7E,
+	 0x7E, 0x7E, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	 0x00, 0x00, 0x30, 0x0C, 0x5E, 0x7A, 0x81, 0x81,
+	 0x81, 0x81, 0x5E, 0x7A, 0x30, 0x0C, 0x00, 0x00,
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor *corners[9] = {
+	&tl,	&t,	&tr,
+	&l,	nil,	&r,
+	&bl,	&b,	&br,
+};
+
+int
+gettitleheight(){
+	Point p = stringsize(font, "Á");
+	return 2 * Borderwidth + p.y;
+}
+
+void
+initclosebutton(){
+	int n = gettitleheight() - (2 * Borderwidth);
+	Rectangle r = Rect(0, 0, n, n);
+	//closebutton = allocimage(display, r, CMAP8, 1, DRed);
+	closebutton = allocimage(display, r, CMAP8, 1, jayconfig->windowTitleColor);
+	closebuttonhoover = allocimage(display, r, CMAP8, 1, 0xD8D8D8FF);
+	fillellipse(closebutton, Pt((r.min.x + r.max.x)/2,(r.min.y + r.max.y)/2), Dx(r)/2 - 1, Dx(r)/2 - 1, closebuttonhoover, r.min);
+	//border3d(closebutton, r, 1, wht, blk, r.min);
+	line(closebutton, Pt(r.min.x + Borderwidth, r.min.y + Borderwidth ), Pt(r.max.x - Borderwidth, r.max.y - Borderwidth), 0, 0, 0, blk, r.min);
+	line(closebutton, Pt(r.max.x - Borderwidth, r.min.y + Borderwidth ), Pt(r.min.x + Borderwidth, r.max.y - Borderwidth), 0, 0, 0, blk, r.min);
+	draw(closebuttonhoover, closebutton->r, closebutton, nil, closebutton->r.min);
+	border3d(closebuttonhoover, r, 1, blk, wht, r.min);
+}
+
+void
+initmaximizebutton() {
+	int n = gettitleheight() - (2 * Borderwidth);
+	Rectangle r = Rect(0, 0, n, n);
+	//maximizebutton = allocimage(display, r, CMAP8, 1, DGreen);
+	maximizebutton = allocimage(display, r, CMAP8, 1, jayconfig->windowTitleColor);
+	maximizebuttonhoover = allocimage(display, r, CMAP8, 1, 0xD8D8D8FF);
+	fillellipse(maximizebutton, Pt((r.min.x + r.max.x)/2,(r.min.y + r.max.y)/2), Dx(r)/2 - 1, Dx(r)/2 - 1, maximizebuttonhoover, r.min);
+	ellipse(maximizebutton, Pt((r.min.x + r.max.x)/2,(r.min.y + r.max.y)/2), Dx(r)/2 - 4, Dx(r)/2 - 4, 0, display->black, r.min);
+	//border3d(maximizebutton, r, 1, wht, blk, r.min);
+	/*Point x1 = Pt((r.min.x + r.max.x)/2 -1, r.min.y + 2);
+	Point y1 = Pt((r.min.x + r.max.x)/2 -1, r.max.y - 3);
+	Point x2 = Pt(r.min.x + 2, (r.min.y + r.max.y)/2 - 1);l
+	Point y2 = Pt(r.max.x - 3, (r.min.y + r.max.y)/2 - 1);
+	line(maximizebutton, x1, y1, Endsquare, 0, 1, blk, r.min);
+	line(maximizebutton, x2, y2, Endsquare, 0, 1, blk, r.min);
+	line(maximizebutton, Pt(r.min.x+4, r.min.y + 3), Pt(r.max.x-4, r.min.y + 3),Endsquare, 0, 1, blk, r.min);
+	line(maximizebutton, Pt(r.min.x+4, r.max.y - 3), Pt(r.max.x-4, r.max.y - 3),Endsquare, 0, 1, blk, r.min);
+	line(maximizebutton, Pt(r.min.x+4, r.min.y + 3), Pt(r.min.x+4, r.max.y - 3),Endsquare, 0, 1, blk, r.min);
+	line(maximizebutton, Pt(r.max.x-4, r.min.y + 3), Pt(r.max.x-4, r.max.y - 3),Endsquare, 0, 1, blk, r.min);*/
+	draw(maximizebuttonhoover, maximizebutton->r, maximizebutton, nil, maximizebutton->r.min);
+	border3d(maximizebuttonhoover, r, 1, blk, wht, r.min);
+}
+
+void
+initminimizebutton(){
+	int n = gettitleheight() - (2 * Borderwidth);
+	Rectangle r = Rect(0, 0, n, n);
+	//minimizebutton = allocimage(display, r, CMAP8, 1, DYellow);
+	minimizebutton = allocimage(display, r, CMAP8, 1, jayconfig->windowTitleColor);
+	minimizebuttonhoover = allocimage(display, r, CMAP8, 1, 0xD8D8D8FF);
+	fillellipse(minimizebutton, Pt((r.min.x + r.max.x)/2,(r.min.y + r.max.y)/2), Dx(r)/2 - 1, Dx(r)/2 - 1, minimizebuttonhoover, r.min);
+	//border3d(minimizebutton, r, 1, wht, blk, r.min);
+	int y = r.max.y - Borderwidth;
+	line(minimizebutton, Pt(r.min.x + Borderwidth, y), Pt(r.max.x - Borderwidth, y), 0, 0, 0, blk, r.min);
+	draw(minimizebuttonhoover, minimizebutton->r, minimizebutton, nil, minimizebutton->r.min);
+	border3d(minimizebuttonhoover, r, 1, blk, wht, r.min);
+}
+
+static int
+setbackimg(){
+	if (jayconfig->backgroundimgpath == nil){
+		return 0;
+	}
+	int fd = open(jayconfig->backgroundimgpath, OREAD);
+	if (fd > 0){
+		background = readimage(display, fd, 0);
+		close(fd);
+		return 1;
+	}
+	return 0;
+}
+
+static void
+seticoninit(void){
+	initclosebutton();
+	initmaximizebutton();
+	initminimizebutton();
+	if (!setbackimg()){
+		background = allocimage(display, Rect(0,0,1,1), RGB24, 1, jayconfig->backgroundColor);
+	}
+	setmenucolor(jayconfig->menuBackColor, jayconfig->menuHighColor, jayconfig->menuBorderColor, jayconfig->menuTextColor, jayconfig->menuSelTextColor);
+}
+
+
+void
+iconinit(void)
+{
+	initdefaultconfig();
+	blk = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DBlack);
+	wht = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DWhite);
+	red = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0xDD0000FF);
+	seticoninit();
+}
+
+void
+iconreinit(void) {
+	freeimage(background);
+	freeimage(closebutton);
+	freeimage(closebuttonhoover);
+	freeimage(minimizebutton);
+	freeimage(maximizebutton);
+	freeimage(maximizebuttonhoover);
+	seticoninit();
+}

+ 50 - 0
sys/src/cmd/jay/fns.h

@@ -0,0 +1,50 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+void	keyboardsend(char*, int);
+int	whide(Window*);
+int	wunhide(int);
+void wsettitle(Window *w);
+void	freescrtemps(void);
+int	parsewctl(char**, Rectangle, Rectangle*, int*, int*, int*, int*, char**, char*, char*);
+int	writewctl(Xfid*, char*);
+Window *new(Image*, int, int, int, char*, char*, char**);
+void	jaysetcursor(Cursor*, int);
+int	min(int, int);
+int	max(int, int);
+Rune*	strrune(Rune*, Rune);
+int	isalnum(Rune);
+void	timerstop(Timer*);
+void	timercancel(Timer*);
+Timer*	timerstart(int);
+void	error(char*);
+void	killprocs(void);
+int	shutdown(void*, char*);
+void	iconinit(void);
+void iconreinit(void);
+void	*erealloc(void*, uint);
+void *emalloc(uint);
+char *estrdup(char*);
+void	button3menu(void);
+void	button2menu(Window*);
+void	cvttorunes(char*, int, Rune*, int*, int*, int*);
+/* was (byte*,int)	runetobyte(Rune*, int); */
+char* runetobyte(Rune*, int, int*);
+void	putsnarf(void);
+void	getsnarf(void);
+void	timerinit(void);
+int	goodrect(Rectangle);
+void printPoints(Rectangle r);
+Image* sweep(void);
+Image * wcenter(int sizex, int sizey);
+Rectangle rectsubrect(Rectangle r1, Rectangle r2, int position);
+
+#define	runemalloc(n)		malloc((n)*sizeof(Rune))
+#define	runerealloc(a, n)	realloc(a, (n)*sizeof(Rune))
+#define	runemove(a, b, n)	memmove(a, b, (n)*sizeof(Rune))

+ 706 - 0
sys/src/cmd/jay/fsys.c

@@ -0,0 +1,706 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+char Eperm[] = "permission denied";
+char Eexist[] = "file does not exist";
+char Enotdir[] = "not a directory";
+char	Ebadfcall[] = "bad fcall type";
+char	Eoffset[] = "illegal offset";
+
+int	messagesize = 8192+IOHDRSZ;	/* good start */
+
+enum{
+	DEBUG = 0
+};
+
+Dirtab dirtab[]=
+{
+	{ ".",			QTDIR,	Qdir,			0500|DMDIR },
+	{ "cons",		QTFILE,	Qcons,		0600 },
+	{ "cursor",		QTFILE,	Qcursor,		0600 },
+	{ "consctl",	QTFILE,	Qconsctl,		0200 },
+	{ "winid",		QTFILE,	Qwinid,		0400 },
+	{ "winname",	QTFILE,	Qwinname,	0400 },
+	{ "jayctl",  QTFILE,  Qjayctl, 0600 },
+	{ "kbdin",		QTFILE,	Qkbdin,		0200 },
+	{ "label",		QTFILE,	Qlabel,		0600 },
+	{ "mouse",	QTFILE,	Qmouse,		0600 },
+	{ "screen",		QTFILE,	Qscreen,		0400 },
+	{ "snarf",		QTFILE,	Qsnarf,		0600 },
+	{ "text",		QTFILE,	Qtext,		0400 },
+	{ "wdir",		QTFILE,	Qwdir,		0600 },
+	{ "wctl",		QTFILE,	Qwctl,		0600 },
+	{ "window",	QTFILE,	Qwindow,		0400 },
+	{ "wsys",		QTDIR,	Qwsys,		0500|DMDIR },
+	{ nil, }
+};
+
+static uint		getclock(void);
+static void		filsysproc(void*);
+static Fid*		newfid(Filsys*, int);
+static int		dostat(Filsys*, int, Dirtab*, uint8_t*, int, uint);
+
+int	clockfd;
+int	firstmessage = 1;
+
+static	Xfid*	filsysflush(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysversion(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysauth(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysattach(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsyswalk(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysopen(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsyscreate(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysread(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsyswrite(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysclunk(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysremove(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsysstat(Filsys*, Xfid*, Fid*);
+static	Xfid*	filsyswstat(Filsys*, Xfid*, Fid*);
+
+Xfid* 	(*fcall[Tmax])(Filsys*, Xfid*, Fid*) =
+{
+	[Tflush]	= filsysflush,
+	[Tversion]	= filsysversion,
+	[Tauth]	= filsysauth,
+	[Tattach]	= filsysattach,
+	[Twalk]	= filsyswalk,
+	[Topen]	= filsysopen,
+	[Tcreate]	= filsyscreate,
+	[Tread]	= filsysread,
+	[Twrite]	= filsyswrite,
+	[Tclunk]	= filsysclunk,
+	[Tremove]= filsysremove,
+	[Tstat]	= filsysstat,
+	[Twstat]	= filsyswstat,
+};
+
+void
+post(char *name, char *envname, int srvfd)
+{
+	int fd;
+	char buf[32];
+
+	fd = create(name, OWRITE|ORCLOSE|OCEXEC, 0600);
+	if(fd < 0)
+		error(name);
+	sprint(buf, "%d",srvfd);
+	if(write(fd, buf, strlen(buf)) != strlen(buf))
+		error("srv write");
+	putenv(envname, name);
+}
+
+/*
+ * Build pipe with OCEXEC set on second fd.
+ * Can't put it on both because we want to post one in /srv.
+ */
+int
+cexecpipe(int *p0, int *p1)
+{
+	/* pipe the hard way to get close on exec */
+	if(bind("#|", "/mnt/temp", MREPL) < 0)
+		return -1;
+	*p0 = open("/mnt/temp/data", ORDWR);
+	*p1 = open("/mnt/temp/data1", ORDWR|OCEXEC);
+	unmount(nil, "/mnt/temp");
+	if(*p0<0 || *p1<0)
+		return -1;
+	return 0;
+}
+
+Filsys*
+filsysinit(Channel *cxfidalloc)
+{
+	int n, fd, pid, p0;
+	Filsys *fs;
+	Channel *c;
+	char buf[128];
+
+	fs = emalloc(sizeof(Filsys));
+	if(cexecpipe(&fs->cfd, &fs->sfd) < 0)
+		goto Rescue;
+	fmtinstall('F', fcallfmt);
+	clockfd = open("/dev/time", OREAD|OCEXEC);
+	fd = open("/dev/user", OREAD);
+	strcpy(buf, "Jean-Paul_Belmondo");
+	if(fd >= 0){
+		n = read(fd, buf, sizeof buf-1);
+		if(n > 0)
+			buf[n] = 0;
+		close(fd);
+	}
+	fs->user = estrdup(buf);
+	fs->cxfidalloc = cxfidalloc;
+	pid = getpid();
+
+	/*
+	 * Create and post wctl pipe
+	 */
+	if(cexecpipe(&p0, &wctlfd) < 0)
+		goto Rescue;
+	sprint(srvwctl, "/srv/jaywctl.%s.%d", fs->user, pid);
+	post(srvwctl, "wctl", p0);
+	close(p0);
+
+	/*
+	 * Start server processes
+	 */
+	c = chancreate(sizeof(char*), 0);
+	if(c == nil)
+		error("wctl channel");
+	proccreate(wctlproc, c, 4096);
+	threadcreate(wctlthread, c, 4096);
+	proccreate(filsysproc, fs, 10000);
+
+	/*
+	 * Post srv pipe
+	 */
+	sprint(srvpipe, "/srv/jay.%s.%d", fs->user, pid);
+	post(srvpipe, "wsys", fs->cfd);
+
+	return fs;
+
+Rescue:
+	free(fs);
+	return nil;
+}
+
+static
+void
+filsysproc(void *arg)
+{
+	int n;
+	Xfid *x;
+	Fid *f;
+	Fcall t;
+	uint8_t *buf;
+	Filsys *fs;
+
+	threadsetname("FILSYSPROC");
+	fs = arg;
+	fs->pid = getpid();
+	x = nil;
+	for(;;){
+		buf = emalloc(messagesize+UTFmax);	/* UTFmax for appending partial rune in xfidwrite */
+		n = read9pmsg(fs->sfd, buf, messagesize);
+		if(n <= 0){
+			yield();	/* if threadexitsall'ing, will not return */
+			fprint(2, "jay: %d: read9pmsg: %d %r\n", getpid(), n);
+			errorshouldabort = 0;
+			error("eof or i/o error on server channel");
+		}
+		if(x == nil){
+			send(fs->cxfidalloc, nil);
+			recv(fs->cxfidalloc, &x);
+			x->fs = fs;
+		}
+		x->buf = buf;
+		if(convM2S(buf, n, &x->Fcall) != n)
+			error("convert error in convM2S");
+		if(DEBUG)
+			fprint(2, "jay:<-%F\n", &x->Fcall);
+		if(fcall[x->Fcall.type] == nil)
+			x = filsysrespond(fs, x, &t, Ebadfcall);
+		else{
+			if(x->Fcall.type==Tversion || x->Fcall.type==Tauth)
+				f = nil;
+			else
+				f = newfid(fs, x->Fcall.fid);
+			x->f = f;
+			x  = (*fcall[x->Fcall.type])(fs, x, f);
+		}
+		firstmessage = 0;
+	}
+}
+
+/*
+ * Called only from a different FD group
+ */
+int
+filsysmount(Filsys *fs, int id)
+{
+	char buf[32];
+
+	close(fs->sfd);	/* close server end so mount won't hang if exiting */
+	sprint(buf, "%d", id);
+	if(mount(fs->cfd, -1, "/mnt/wsys", MREPL, buf, 'M') < 0){
+		fprint(2, "mount failed: %r\n");
+		return -1;
+	}
+	if(bind("/mnt/wsys", "/dev", MBEFORE) < 0){
+		fprint(2, "bind failed: %r\n");
+		return -1;
+	}
+	return 0;
+}
+
+Xfid*
+filsysrespond(Filsys *fs, Xfid *x, Fcall *t, char *err)
+{
+	int n;
+
+	if(err){
+		t->type = Rerror;
+		t->ename = err;
+	}else
+		t->type = x->Fcall.type+1;
+	t->fid = x->Fcall.fid;
+	t->tag = x->Fcall.tag;
+	if(x->buf == nil)
+		x->buf = malloc(messagesize);
+	n = convS2M(t, x->buf, messagesize);
+	if(n <= 0)
+		error("convert error in convS2M");
+	if(write(fs->sfd, x->buf, n) != n)
+		error("write error in respond");
+	if(DEBUG)
+		fprint(2, "jay:->%F\n", t);
+	free(x->buf);
+	x->buf = nil;
+	return x;
+}
+
+void
+filsyscancel(Xfid *x)
+{
+	if(x->buf){
+		free(x->buf);
+		x->buf = nil;
+	}
+}
+
+static
+Xfid*
+filsysversion(Filsys *fs, Xfid *x, Fid* f)
+{
+	Fcall t;
+
+	if(!firstmessage)
+		return filsysrespond(x->fs, x, &t, "version request not first message");
+	if(x->Fcall.msize < 256)
+		return filsysrespond(x->fs, x, &t, "version: message size too small");
+	messagesize = x->Fcall.msize;
+	t.msize = messagesize;
+	if(strncmp(x->Fcall.version, "9P2000", 6) != 0)
+		return filsysrespond(x->fs, x, &t, "unrecognized 9P version");
+	t.version = "9P2000";
+	return filsysrespond(fs, x, &t, nil);
+}
+
+static
+Xfid*
+filsysauth(Filsys *fs, Xfid *x, Fid* f)
+{
+	Fcall t;
+
+		return filsysrespond(fs, x, &t, "jay: authentication not required");
+}
+
+static
+Xfid*
+filsysflush(Filsys* fs, Xfid *x, Fid* f)
+{
+	sendp(x->c, xfidflush);
+	return nil;
+}
+
+static
+Xfid*
+filsysattach(Filsys * fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	if(strcmp(x->Fcall.uname, x->fs->user) != 0)
+		return filsysrespond(x->fs, x, &t, Eperm);
+	f->busy = TRUE;
+	f->open = FALSE;
+	f->qid.path = Qdir;
+	f->qid.type = QTDIR;
+	f->qid.vers = 0;
+	f->dir = dirtab;
+	f->nrpart = 0;
+	sendp(x->c, xfidattach);
+	return nil;
+}
+
+static
+int
+numeric(char *s)
+{
+	for(; *s!='\0'; s++)
+		if(*s<'0' || '9'<*s)
+			return 0;
+	return 1;
+}
+
+static
+Xfid*
+filsyswalk(Filsys *fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+	Fid *nf;
+	int i, id;
+	uint8_t type;
+	uint32_t path;
+	Dirtab *d, *dir;
+	Window *w;
+	char *err;
+	Qid qid;
+
+	if(f->open)
+		return filsysrespond(fs, x, &t, "walk of open file");
+	nf = nil;
+	if(x->Fcall.fid  != x->Fcall.newfid){
+		/* BUG: check exists */
+		nf = newfid(fs, x->Fcall.newfid);
+		if(nf->busy)
+			return filsysrespond(fs, x, &t, "clone to busy fid");
+		nf->busy = TRUE;
+		nf->open = FALSE;
+		nf->dir = f->dir;
+		nf->qid = f->qid;
+		nf->w = f->w;
+		incref(&f->w->Ref);
+		nf->nrpart = 0;	/* not open, so must be zero */
+		f = nf;	/* walk f */
+	}
+
+	t.nwqid = 0;
+	err = nil;
+
+	/* update f->qid, f->dir only if walk completes */
+	qid = f->qid;
+	dir = f->dir;
+
+	if(x->Fcall.nwname > 0){
+		for(i=0; i<x->Fcall.nwname; i++){
+			if((qid.type & QTDIR) == 0){
+				err = Enotdir;
+				break;
+			}
+			if(strcmp(x->Fcall.wname[i], "..") == 0){
+				type = QTDIR;
+				path = Qdir;
+				dir = dirtab;
+				if(FILE(qid) == Qwsysdir)
+					path = Qwsys;
+				id = 0;
+    Accept:
+				if(i == MAXWELEM){
+					err = "name too long";
+					break;
+				}
+				qid.type = type;
+				qid.vers = 0;
+				qid.path = QID(id, path);
+				t.wqid[t.nwqid++] = qid;
+				continue;
+			}
+
+			if(qid.path == Qwsys){
+				/* is it a numeric name? */
+				if(!numeric(x->Fcall.wname[i]))
+					break;
+				/* yes: it's a directory */
+				id = atoi(x->Fcall.wname[i]);
+				qlock(&all);
+				w = wlookid(id);
+				if(w == nil){
+					qunlock(&all);
+					break;
+				}
+				path = Qwsysdir;
+				type = QTDIR;
+				qunlock(&all);
+				incref(&w->Ref);
+				sendp(winclosechan, f->w);
+				f->w = w;
+				dir = dirtab;
+				goto Accept;
+			}
+
+			if(snarffd>=0 && strcmp(x->Fcall.wname[i], "snarf")==0)
+				break;	/* don't serve /dev/snarf if it's provided in the environment */
+			id = WIN(f->qid);
+			d = dirtab;
+			d++;	/* skip '.' */
+			for(; d->name; d++)
+				if(strcmp(x->Fcall.wname[i], d->name) == 0){
+					path = d->qid;
+					type = d->type;
+					dir = d;
+					goto Accept;
+				}
+
+			break;	/* file not found */
+		}
+
+		if(i==0 && err==nil)
+			err = Eexist;
+	}
+
+	if(err!=nil || t.nwqid<x->Fcall.nwname){
+		if(nf){
+			if(nf->w)
+				sendp(winclosechan, nf->w);
+			nf->open = FALSE;
+			nf->busy = FALSE;
+		}
+	}else if(t.nwqid == x->Fcall.nwname){
+		f->dir = dir;
+		f->qid = qid;
+	}
+
+	return filsysrespond(fs, x, &t, err);
+}
+
+static
+Xfid*
+filsysopen(Filsys *fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+	int m;
+
+	/* can't truncate anything, so just disregard */
+	x->Fcall.mode &= ~(OTRUNC|OCEXEC);
+	/* can't execute or remove anything */
+	if(x->Fcall.mode==OEXEC || (x->Fcall.mode&ORCLOSE))
+		goto Deny;
+	switch(x->Fcall.mode){
+	default:
+		goto Deny;
+	case OREAD:
+		m = 0400;
+		break;
+	case OWRITE:
+		m = 0200;
+		break;
+	case ORDWR:
+		m = 0600;
+		break;
+	}
+	if(((f->dir->perm&~(DMDIR|DMAPPEND))&m) != m)
+		goto Deny;
+
+	sendp(x->c, xfidopen);
+	return nil;
+
+    Deny:
+	return filsysrespond(fs, x, &t, Eperm);
+}
+
+static
+Xfid*
+filsyscreate(Filsys *fs, Xfid *x, Fid*f)
+{
+	Fcall t;
+
+	return filsysrespond(fs, x, &t, Eperm);
+}
+
+static
+int
+idcmp(const void *a, const void *b)
+{
+	return *(int*)a - *(int*)b;
+}
+
+static
+Xfid*
+filsysread(Filsys *fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+	uint8_t *b;
+	int i, n, o, e, len, j, k, *ids;
+	Dirtab *d, dt;
+	uint clock;
+	char buf[16];
+
+	if((f->qid.type & QTDIR) == 0){
+		sendp(x->c, xfidread);
+		return nil;
+	}
+	o = x->Fcall.offset;
+	e = x->Fcall.offset+x->Fcall.count;
+	clock = getclock();
+	b = malloc(messagesize-IOHDRSZ);	/* avoid memset of emalloc */
+	if(b == nil)
+		return filsysrespond(fs, x, &t, "out of memory");
+	n = 0;
+	switch(FILE(f->qid)){
+	case Qdir:
+	case Qwsysdir:
+		d = dirtab;
+		d++;	/* first entry is '.' */
+		for(i=0; d->name!=nil && i<e; i+=len){
+			len = dostat(fs, WIN(x->f->qid), d, b+n, x->Fcall.count-n, clock);
+			if(len <= BIT16SZ)
+				break;
+			if(i >= o)
+				n += len;
+			d++;
+		}
+		break;
+	case Qwsys:
+		qlock(&all);
+		ids = emalloc(nwindow*sizeof(int));
+		for(j=0; j<nwindow; j++)
+			ids[j] = window[j]->id;
+		qunlock(&all);
+		qsort(ids, nwindow, sizeof ids[0], idcmp);
+		dt.name = buf;
+		for(i=0, j=0; j<nwindow && i<e; i+=len){
+			k = ids[j];
+			sprint(dt.name, "%d", k);
+			dt.qid = QID(k, Qdir);
+			dt.type = QTDIR;
+			dt.perm = DMDIR|0700;
+			len = dostat(fs, k, &dt, b+n, x->Fcall.count-n, clock);
+			if(len == 0)
+				break;
+			if(i >= o)
+				n += len;
+			j++;
+		}
+		free(ids);
+		break;
+	}
+	t.data = (char*)b;
+	t.count = n;
+	filsysrespond(fs, x, &t, nil);
+	free(b);
+	return x;
+}
+
+static
+Xfid*
+filsyswrite(Filsys* fs, Xfid *x, Fid*f)
+{
+	sendp(x->c, xfidwrite);
+	return nil;
+}
+
+static
+Xfid*
+filsysclunk(Filsys *fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	if(f->open){
+		f->busy = FALSE;
+		f->open = FALSE;
+		sendp(x->c, xfidclose);
+		return nil;
+	}
+	if(f->w)
+		sendp(winclosechan, f->w);
+	f->busy = FALSE;
+	f->open = FALSE;
+	return filsysrespond(fs, x, &t, nil);
+}
+
+static
+Xfid*
+filsysremove(Filsys *fs, Xfid *x, Fid*f)
+{
+	Fcall t;
+
+	return filsysrespond(fs, x, &t, Eperm);
+}
+
+static
+Xfid*
+filsysstat(Filsys *fs, Xfid *x, Fid *f)
+{
+	Fcall t;
+
+	t.stat = emalloc(messagesize-IOHDRSZ);
+	t.nstat = dostat(fs, WIN(x->f->qid), f->dir, t.stat, messagesize-IOHDRSZ, getclock());
+	x = filsysrespond(fs, x, &t, nil);
+	free(t.stat);
+	return x;
+}
+
+static
+Xfid*
+filsyswstat(Filsys *fs, Xfid *x, Fid*f)
+{
+	Fcall t;
+
+	return filsysrespond(fs, x, &t, Eperm);
+}
+
+static
+Fid*
+newfid(Filsys *fs, int fid)
+{
+	Fid *f, *ff, **fh;
+
+	ff = nil;
+	fh = &fs->fids[fid&(Nhash-1)];
+	for(f=*fh; f; f=f->next)
+		if(f->fid == fid)
+			return f;
+		else if(ff==nil && f->busy==FALSE)
+			ff = f;
+	if(ff){
+		ff->fid = fid;
+		return ff;
+	}
+	f = emalloc(sizeof *f);
+	f->fid = fid;
+	f->next = *fh;
+	*fh = f;
+	return f;
+}
+
+static
+uint
+getclock(void)
+{
+	char buf[32];
+
+	seek(clockfd, 0, 0);
+	read(clockfd, buf, sizeof buf);
+	return atoi(buf);
+}
+
+static
+int
+dostat(Filsys *fs, int id, Dirtab *dir, uint8_t *buf, int nbuf, uint clock)
+{
+	Dir d;
+
+	d.qid.path = QID(id, dir->qid);
+	if(dir->qid == Qsnarf)
+		d.qid.vers = snarfversion;
+	else
+		d.qid.vers = 0;
+	d.qid.type = dir->type;
+	d.mode = dir->perm;
+	d.length = 0;	/* would be nice to do better */
+	d.name = dir->name;
+	d.uid = fs->user;
+	d.gid = fs->user;
+	d.muid = fs->user;
+	d.atime = clock;
+	d.mtime = clock;
+	return convD2M(&d, buf, nbuf);
+}

+ 1304 - 0
sys/src/cmd/jay/jay.c

@@ -0,0 +1,1304 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+void		resize(void);
+void		move(void);
+void		delete(void);
+void		hide(void);
+void		unhide(int);
+void		newtile(int);
+Image	*sweep(void);
+Image	*bandsize(Window*);
+Image*	drag(Window*, Rectangle*);
+void		refresh(Rectangle);
+void		resized(void);
+Channel	*exitchan;	/* chan(int) */
+Channel	*winclosechan; /* chan(Window*); */
+Rectangle	viewr;
+int		threadrforkflag = 0;	/* should be RFENVG but that hides jay from plumber */
+
+void	mousethread(void*);
+void	keyboardthread(void*);
+void winclosethread(void*);
+void deletethread(void*);
+void	initcmd(void*);
+
+char		*fontname;
+int		mainpid;
+
+enum
+{
+	New,
+	Reshape,
+	Move,
+	Delete,
+	Hide,
+	Exit,
+};
+
+enum
+{
+	Cut,
+	Paste,
+	Snarf,
+	Plumb,
+	Send,
+	Scroll,
+};
+
+char		*menu2str[] = {
+ [Cut] =		"cut",
+ [Paste] =		"paste",
+ [Snarf] =		"copy",
+ [Plumb] =		"plumb",
+ [Send] =		"send",
+ [Scroll] =		"scroll",
+			nil
+};
+
+Menu menu2 =
+{
+	menu2str
+};
+
+int	Hidden = Exit+1;
+
+char		*menu3str[100] = {
+ [New] =		"New",
+ [Reshape] =	"Resize",
+ [Move] =		"Move",
+ [Delete] =		"Delete",
+ [Hide] =		"Hide",
+ [Exit] =		"Exit",
+			nil
+};
+
+Menu menu3 =
+{
+	menu3str
+};
+
+char *rcargv[] = { "rc", "-i", nil };
+char *kbdargv[] = { "rc", "-c", nil, nil };
+
+int errorshouldabort = 0;
+
+void
+derror(Display* d, char *errorstr)
+{
+	error(errorstr);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: jay [-f font] [-i initcmd] [-k kbdcmd] [-s]\n");
+	exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	char *initstr, *kbdin, *s;
+	char buf[256];
+
+	if(strstr(argv[0], ".out") == nil){
+		menu3str[Exit] = nil;
+		Hidden--;
+	}
+	initstr = nil;
+	kbdin = nil;
+	maxtab = 0;
+	ARGBEGIN{
+	case 'f':
+		fontname = ARGF();
+		if(fontname == nil)
+			usage();
+		break;
+	case 'i':
+		initstr = ARGF();
+		if(initstr == nil)
+			usage();
+		break;
+	case 'k':
+		if(kbdin != nil)
+			usage();
+		kbdin = ARGF();
+		if(kbdin == nil)
+			usage();
+		break;
+	case 's':
+		scrolling = TRUE;
+		break;
+	}ARGEND
+
+	mainpid = getpid();
+	if(getwd(buf, sizeof buf) == nil)
+		startdir = estrdup(".");
+	else
+		startdir = estrdup(buf);
+	if(fontname == nil)
+		fontname = getenv("font");
+	if(fontname == nil)
+		fontname = "/lib/font/bit/lucm/unicode.9.font";
+	s = getenv("tabstop");
+	if(s != nil)
+		maxtab = strtol(s, nil, 0);
+	if(maxtab == 0)
+		maxtab = 4;
+	free(s);
+	/* check font before barging ahead */
+	if(access(fontname, 0) < 0){
+		fprint(2, "jay: can't access %s: %r\n", fontname);
+		exits("font open");
+	}
+	putenv("font", fontname);
+
+	snarffd = open("/dev/snarf", OREAD|OCEXEC);
+
+	if(geninitdraw(nil, derror, nil, "jay", nil, Refnone) < 0){
+		fprint(2, "jay: can't open display: %r\n");
+		exits("display open");
+	}
+	iconinit();
+	view = screen;
+	viewr = view->r;
+	windowspace = viewr;
+	mousectl = initmouse(nil, screen);
+	if(mousectl == nil)
+		error("can't find mouse");
+	mouse = (Mouse *)mousectl;
+	jaysetcursor(nil, 1);
+	keyboardctl = initkeyboard(nil);
+	if(keyboardctl == nil)
+		error("can't find keyboard");
+	wscreen = allocscreen(screen, background, 0);
+	if(wscreen == nil)
+		error("can't allocate screen");
+	draw(view, viewr, background, nil, ZP);
+	flushimage(display, 1);
+
+	exitchan = chancreate(sizeof(int), 0);
+	winclosechan = chancreate(sizeof(Window*), 0);
+	deletechan = chancreate(sizeof(char*), 0);
+
+	initpanel();
+	timerinit();
+	threadcreate(keyboardthread, nil, STACK);
+	threadcreate(mousethread, nil, STACK);
+	threadcreate(winclosethread, nil, STACK);
+	threadcreate(deletethread, nil, STACK);
+	filsys = filsysinit(xfidinit());
+
+	if(filsys == nil)
+		fprint(2, "jay: can't create file system server: %r\n");
+	else{
+		errorshouldabort = 1;	/* suicide if there's trouble after this */
+#if 0
+		if(initstr)
+			proccreate(initcmd, initstr, STACK);
+		if(kbdin){
+			kbdargv[2] = kbdin;
+			r = screen->r;
+			r.max.x = r.min.x+300;
+			r.max.y = r.min.y+80;
+			i = allocwindow(wscreen, r, Refbackup, DWhite);
+			wkeyboard = new(i, FALSE, scrolling, 0, nil, "/bin/rc", kbdargv);
+			if(wkeyboard == nil)
+				error("can't create keyboard window");
+		}
+#endif
+		threadnotify(shutdown, 1);
+		recv(exitchan, nil);
+	}
+	killprocs();
+	threadexitsall(nil);
+}
+
+/*
+ * /dev/snarf updates when the file is closed, so we must open our own
+ * fd here rather than use snarffd
+ */
+void
+putsnarf(void)
+{
+	int fd, i, n;
+
+	if(snarffd<0 || nsnarf==0)
+		return;
+	fd = open("/dev/snarf", OWRITE);
+	if(fd < 0)
+		return;
+	/* snarf buffer could be huge, so fprint will truncate; do it in blocks */
+	for(i=0; i<nsnarf; i+=n){
+		n = nsnarf-i;
+		if(n >= 256)
+			n = 256;
+		if(fprint(fd, "%.*S", n, snarf+i) < 0)
+			break;
+	}
+	close(fd);
+}
+
+void
+getsnarf(void)
+{
+	int i, n, nb, nulls;
+	char *sn, buf[1024];
+
+	if(snarffd < 0)
+		return;
+	sn = nil;
+	i = 0;
+	seek(snarffd, 0, 0);
+	while((n = read(snarffd, buf, sizeof buf)) > 0){
+		sn = erealloc(sn, i+n+1);
+		memmove(sn+i, buf, n);
+		i += n;
+		sn[i] = 0;
+	}
+	if(i > 0){
+		snarf = runerealloc(snarf, i+1);
+		cvttorunes(sn, i, snarf, &nb, &nsnarf, &nulls);
+		free(sn);
+	}
+}
+
+void
+initcmd(void *arg)
+{
+	char *cmd;
+
+	cmd = arg;
+	rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
+	procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil);
+	fprint(2, "jay: exec failed: %r\n");
+	exits("exec");
+}
+
+char *oknotes[] =
+{
+	"delete",
+	"hangup",
+	"kill",
+	"exit",
+	nil
+};
+
+int
+shutdown(void * vacio, char *msg)
+{
+	int i;
+	static Lock shutdownlk;
+
+	killprocs();
+	for(i=0; oknotes[i]; i++)
+		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0){
+			lock(&shutdownlk);	/* only one can threadexitsall */
+			threadexitsall(msg);
+		}
+	fprint(2, "jay %d: abort: %s\n", getpid(), msg);
+	abort();
+	exits(msg);
+	return 0;
+}
+
+void
+killprocs(void)
+{
+	int i;
+
+	for(i=0; i<nwindow; i++)
+		postnote(PNGROUP, window[i]->pid, "hangup");
+}
+
+void
+keyboardthread(void* v)
+{
+	Rune buf[2][20], *rp;
+	int n, i;
+
+	threadsetname("keyboardthread");
+	n = 0;
+	for(;;){
+		rp = buf[n];
+		n = 1-n;
+		recv(keyboardctl->c, rp);
+		for(i=1; i<nelem(buf[0])-1; i++)
+			if(nbrecv(keyboardctl->c, rp+i) <= 0)
+				break;
+		rp[i] = L'\0';
+		if(input != nil)
+			sendp(input->ck, rp);
+	}
+}
+
+/*
+ * Used by /dev/kbdin
+ */
+void
+keyboardsend(char *s, int cnt)
+{
+	Rune *r;
+	int i, nb, nr;
+
+	r = runemalloc(cnt);
+	/* BUGlet: partial runes will be converted to error runes */
+	cvttorunes(s, cnt, r, &nb, &nr, nil);
+	for(i=0; i<nr; i++)
+		send(keyboardctl->c, &r[i]);
+	free(r);
+}
+
+int
+portion(int x, int lo, int hi)
+{
+	x -= lo;
+	hi -= lo;
+	if(x < 20)
+		return 0;
+	if(x > hi-20)
+		return 2;
+	return 1;
+}
+
+int
+whichcorner(Window *w, Point p)
+{
+	int i, j;
+
+	i = portion(p.x, w->r.min.x, w->r.max.x);
+	j = portion(p.y, w->r.min.y, w->r.max.y);
+	return 3*j+i;
+}
+
+void
+cornercursor(Window *w, Point p, int force)
+{
+	if(w!=nil && winborder(w, p))
+		jaysetcursor(corners[whichcorner(w, p)], force);
+	else
+		wsetcursor(w, force);
+}
+
+/* thread to allow fsysproc to synchronize window closing with main proc */
+void
+winclosethread(void* v)
+{
+	Window *w;
+
+	threadsetname("winclosethread");
+	for(;;){
+		w = recvp(winclosechan);
+		wclose(w);
+	}
+}
+
+/* thread to make Deleted windows that the client still holds disappear offscreen after an interval */
+void
+deletethread(void* v)
+{
+	char *s, namet[32 + 6]; //32(w->name) + 6(.title)
+	Image *i, *t;
+
+	threadsetname("deletethread");
+	for(;;){
+		s = recvp(deletechan);
+		i = namedimage(display, s);
+		sprint(namet, "%s.title", s);
+		t = namedimage(display, namet);
+		if(i != nil){
+			/* move it off-screen to hide it, since client is slow in letting it go */
+			originwindow(i, i->r.min, view->r.max);
+		}
+		freeimage(i);
+
+		if(t != nil){
+			/* move it off-screen to hide it, since client is slow in letting it go */
+			originwindow(t, t->r.min, view->r.max);
+		}
+		freeimage(t);
+
+		free(s);
+	}
+}
+
+void
+deletetimeoutproc(void *v)
+{
+	char *s;
+
+	s = v;
+	sleep(750);	/* remove window from screen after 3/4 of a second */
+	sendp(deletechan, s);
+}
+
+/*
+ * Button 6 - keyboard toggle - has been pressed.
+ * Send event to keyboard, wait for button up, send that.
+ * Note: there is no coordinate translation done here; this
+ * is just about getting button 6 to the keyboard simulator.
+ */
+void
+keyboardhide(void)
+{
+	send(wkeyboard->mc.c, mouse);
+	do
+		readmouse(mousectl);
+	while(mouse->buttons & (1<<5));
+	send(wkeyboard->mc.c, mouse);
+}
+
+void
+mousethread(void* v)
+{
+	int sending, inside, scrolling, moving, band;
+	Window *oin, *w, *winput;
+	Image *i;
+	Rectangle r;
+	Point xy;
+	Mouse tmp;
+	enum {
+		MReshape,
+		MMouse,
+		NALT
+	};
+	static Alt alts[NALT+1];
+
+	threadsetname("mousethread");
+	sending = FALSE;
+	scrolling = FALSE;
+	moving = FALSE;
+
+	alts[MReshape].c = mousectl->resizec;
+	alts[MReshape].v = nil;
+	alts[MReshape].op = CHANRCV;
+	alts[MMouse].c = mousectl->c;
+	alts[MMouse].v = (Mouse *)mousectl;
+	alts[MMouse].op = CHANRCV;
+	alts[NALT].op = CHANEND;
+
+	for(;;)
+	    switch(alt(alts)){
+		case MReshape:
+			resized();
+			break;
+		case MMouse:
+			if(wkeyboard!=nil && (mouse->buttons & (1<<5))){
+				keyboardhide();
+				break;
+			}
+		Again:
+			winput = input;
+			/* override everything for the keyboard window */
+			if(wkeyboard!=nil && ptinrect(mouse->xy, wkeyboard->screenr)){
+				/* make sure it's on top; this call is free if it is */
+				wtopme(wkeyboard);
+				winput = wkeyboard;
+			}
+
+			if(ptinrect(mouse->xy, taskPanel->r) && mouse->buttons == 1){
+				clickpanel(mouse->xy, taskPanel);
+				goto Drain;
+			}
+
+			if(winput!=nil && winput->i!=nil){
+				/* convert to logical coordinates */
+				xy.x = mouse->xy.x + (winput->i->r.min.x-winput->screenr.min.x);
+				xy.y = mouse->xy.y + (winput->i->r.min.y-winput->screenr.min.y);
+
+				/* the up and down scroll buttons are not subject to the usual rules */
+				if((mouse->buttons&(8|16)) && !winput->mouseopen)
+					goto Sending;
+
+				//inside = ptinrect(mouse->xy, insetrect(winput->screenr, Selborder));
+				inside = ptinrect(mouse->xy, insetrect(winput->r, Selborder));
+				if(winput->mouseopen)
+					scrolling = FALSE;
+				else if(scrolling)
+					scrolling = mouse->buttons;
+				else
+					scrolling = mouse->buttons && ptinrect(xy, winput->scrollr);
+				/* topped will be zero or less if window has been bottomed */
+				if(sending == FALSE && !scrolling && winborder(winput, mouse->xy) && winput->topped>0){
+					moving = TRUE;
+				}else if (sending == FALSE && !scrolling && winput->isVisible && ptinrect(mouse->xy, insetrect(winput->t->r, Selborder)) && winput->topped>0 && mouse->buttons&1) {
+					moving = TRUE;
+				}else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1))){
+					sending = TRUE;
+				}
+			}else
+				sending = FALSE;
+			if(sending){
+			Sending:
+				if(mouse->buttons == 0){
+					cornercursor(winput, mouse->xy, 0);
+					sending = FALSE;
+				}else
+					wsetcursor(winput, 0);
+				tmp = *(Mouse *)mousectl;
+				tmp.xy = xy;
+				send(winput->mc.c, &tmp);
+				continue;
+			}
+			w = wpointto(mouse->xy);
+			/* change cursor if over anyone's border */
+			if(w != nil)
+				cornercursor(w, mouse->xy, 0);
+			else
+				jaysetcursor(nil, 0);
+			if(moving && (mouse->buttons&7)){
+				oin = winput;
+				band = mouse->buttons & 3;
+				sweeping = 1;
+				if(winput->isVisible && ptinrect(mouse->xy, winput->bc)){
+					whooverbutton(mouse->xy, winput);
+					while(mouse->buttons){
+						readmouse(mousectl);
+					}
+					if(ptinrect(mouse->xy, winput->bc)) // only delete if we are still in the red button
+						wsendctlmesg(w, Deleted, ZR, nil);
+					else
+						whooverbutton(Pt(0,0), winput);
+					w=nil;
+					i=nil;
+				}	else if(winput->isVisible && ptinrect(mouse->xy, winput->bmax)) {
+					whooverbutton(mouse->xy, winput);
+					while(mouse->buttons){
+						readmouse(mousectl);
+					}
+					if(ptinrect(mouse->xy, winput->bmax)){
+						// Is the window maximized?
+						if (winput->maximized){
+							winput->maximized=0;
+							//Restore size
+							i = allocwindow(wscreen, windowMinusTitle(winput->originalr, winput->t), Refbackup, DWhite);
+						}else{
+							winput->originalr = winput->r;
+							winput->maximized=1;
+							i = allocwindow(wscreen, windowMinusTitle(windowspace, winput->t), Refbackup, DWhite);
+						}
+					}else {
+						whooverbutton(Pt(0,0), winput);
+						i=nil;
+					}
+				} else if (winput->isVisible && ptinrect(mouse->xy, winput->bmin)) {
+					whooverbutton(mouse->xy, winput);
+					while(mouse->buttons){
+						readmouse(mousectl);
+					}
+					if (ptinrect(mouse->xy, winput->bmin)){
+						whide(winput);
+						w=nil;
+					} else {
+						whooverbutton(Pt(0,0), winput);
+					}
+					i=nil;
+				} else if(winput->isVisible && ptinrect(mouse->xy, insetrect(winput->t->r, Selborder))){ //Moving pressing left button on title bar
+					i = drag(winput, &r);
+					band = 0;
+				}
+				else if(band) //Resize
+					i = bandsize(winput);
+				else //Move
+					i = drag(winput, &r);
+				sweeping = 0;
+				if(i != nil){
+					if(winput == oin){
+						if(band){
+							wsendctlmesg(winput, Reshaped, i->r, i);
+						}
+						else
+							wsendctlmesg(winput, Moved, r, i);
+						cornercursor(winput, mouse->xy, 1);
+					}else
+						freeimage(i);
+				}
+			}
+			if(w != nil)
+				cornercursor(w, mouse->xy, 0);
+			/* we're not sending the event, but if button is down maybe we should */
+			if(mouse->buttons){
+				/* w->topped will be zero or less if window has been bottomed */
+				if(w==nil || (w==winput && w->topped>0)){
+					if(mouse->buttons & 1){
+						;
+					}else if(mouse->buttons & 2){
+						if(winput && !winput->mouseopen)
+							button2menu(winput);
+					}else if(mouse->buttons & 4)
+						button3menu();
+				}else{
+					/* if button 1 event in the window, top the window and wait for button up. */
+					/* otherwise, top the window and pass the event on */
+					if(wtop(mouse->xy) && (mouse->buttons!=1 || winborder(w, mouse->xy)))
+						goto Again;
+					goto Drain;
+				}
+			}
+			moving = FALSE;
+			break;
+
+		Drain:
+			do
+				readmouse(mousectl);
+			while(mousectl->buttons);
+			moving = FALSE;
+			goto Again;	/* recalculate mouse position, cursor */
+		}
+}
+
+void
+resized(void)
+{
+	Image *im;
+	int i, j, ishidden;
+	Rectangle r;
+	Point o, n;
+	Window *w;
+
+	if(getwindow(display, Refnone) < 0)
+		error("failed to re-attach window");
+	freescrtemps();
+	view = screen;
+	freescreen(wscreen);
+	iconreinit();
+	wscreen = allocscreen(screen, background, 0);
+	if(wscreen == nil)
+		error("can't re-allocate screen");
+	draw(view, view->r, background, nil, ZP);
+	redrawpanel();
+	o = subpt(viewr.max, viewr.min);
+	n = subpt(view->clipr.max, view->clipr.min);
+	for(i=0; i<nwindow; i++){
+		w = window[i];
+		if(w->deleted)
+			continue;
+		if(w->maximized){
+			r = windowMinusTitle(windowspace, w->t);
+			w->originalr = rectsubpt(w->originalr, viewr.min);
+			w->originalr.min.x = (w->originalr.min.x*n.x)/o.x;
+			w->originalr.min.y = (w->originalr.min.y*n.y)/o.y;
+			w->originalr.max.x = (w->originalr.max.x*n.x)/o.x;
+			w->originalr.max.y = (w->originalr.max.y*n.y)/o.y;
+			w->originalr = rectaddpt(w->originalr, screen->clipr.min);
+		} else {
+			r = rectsubpt(w->r, viewr.min);
+			r.min.x = (r.min.x*n.x)/o.x;
+			r.min.y = (r.min.y*n.y)/o.y;
+			r.max.x = (r.max.x*n.x)/o.x;
+			r.max.y = (r.max.y*n.y)/o.y;
+			r = rectaddpt(r, screen->clipr.min);
+		}
+		ishidden = 0;
+		for(j=0; j<nhidden; j++)
+			if(w == hidden[j]){
+				ishidden = 1;
+				break;
+			}
+		if(ishidden){
+			im = allocimage(display, r, screen->chan, 0, DWhite);
+			r = ZR;
+		}else
+			im = allocwindow(wscreen, r, Refbackup, DWhite);
+		if(im)
+			wsendctlmesg(w, Reshaped, r, im);
+	}
+	viewr = screen->r;
+	flushimage(display, 1);
+}
+
+void jayredraw(void){
+	resized();
+}
+
+void
+button3menu(void)
+{
+	int i;
+
+	for(i=0; i<nhidden; i++)
+		menu3str[i+Hidden] = hidden[i]->label;
+	menu3str[i+Hidden] = nil;
+
+	sweeping = 1;
+	switch(i = menuhit(3, mousectl, &menu3, wscreen)){
+	case -1:
+		break;
+	case New:
+		new(sweep(), FALSE, scrolling, 0, nil, "/bin/rc", nil);
+		break;
+	case Reshape:
+		resize();
+		break;
+	case Move:
+		move();
+		break;
+	case Delete:
+		delete();
+		break;
+	case Hide:
+		hide();
+		break;
+	case Exit:
+		if(Hidden > Exit){
+			send(exitchan, nil);
+			break;
+		}
+		/* else fall through */
+	default:
+		unhide(i);
+		break;
+	}
+	sweeping = 0;
+}
+
+void
+button2menu(Window *w)
+{
+	if(w->deleted)
+		return;
+	incref(&w->Ref);
+	if(w->scrolling)
+		menu2str[Scroll] = "noscroll";
+	else
+		menu2str[Scroll] = "scroll";
+	switch(menuhit(2, mousectl, &menu2, wscreen)){
+	case Cut:
+		wsnarf(w);
+		wcut(w);
+		wscrdraw(w);
+		break;
+
+	case Snarf:
+		wsnarf(w);
+		break;
+
+	case Paste:
+		getsnarf();
+		wpaste(w);
+		wscrdraw(w);
+		break;
+
+	case Plumb:
+		wplumb(w);
+		break;
+
+	case Send:
+		getsnarf();
+		wsnarf(w);
+		if(nsnarf == 0)
+			break;
+		if(w->rawing){
+			waddraw(w, snarf, nsnarf);
+			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			{
+				Rune newline[] = { '\n' };
+				waddraw(w, newline, 1);
+			}
+		}else{
+			winsert(w, snarf, nsnarf, w->nr);
+			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+			{
+				Rune newline[] = { '\n' };
+				winsert(w, newline, 1, w->nr);
+			}
+		}
+		wsetselect(w, w->nr, w->nr);
+		wshow(w, w->nr);
+		break;
+
+	case Scroll:
+		if(w->scrolling ^= 1)
+			wshow(w, w->nr);
+		break;
+	}
+	wclose(w);
+	wsendctlmesg(w, Wakeup, ZR, nil);
+	flushimage(display, 1);
+}
+
+Point
+onscreen(Point p)
+{
+	p.x = max(screen->clipr.min.x, p.x);
+	p.x = min(screen->clipr.max.x, p.x);
+	p.y = max(screen->clipr.min.y, p.y);
+	p.y = min(screen->clipr.max.y, p.y);
+	return p;
+}
+
+Image*
+sweep(void)
+{
+	Image *i, *oi;
+	Rectangle r;
+	Point p0, p;
+
+	i = nil;
+	menuing = TRUE;
+	jaysetcursor(&crosscursor, 1);
+	while(mouse->buttons == 0)
+		readmouse(mousectl);
+	p0 = onscreen(mouse->xy);
+	p = p0;
+	r.min = p;
+	r.max = p;
+	oi = nil;
+	while(mouse->buttons == 4){
+		readmouse(mousectl);
+		if(mouse->buttons != 4 && mouse->buttons != 0)
+			break;
+		if(!eqpt(mouse->xy, p)){
+			p = onscreen(mouse->xy);
+			r = canonrect(Rpt(p0, p));
+			if(Dx(r)>5 && Dy(r)>5){
+				i = allocwindow(wscreen, r, Refnone, 0xEEEEEEFF); /* grey */
+				freeimage(oi);
+				if(i == nil)
+					goto Rescue;
+				oi = i;
+				//border for click and drag a new window
+				border(i, r, Selborder, red, ZP);
+				flushimage(display, 1);
+			}
+		}
+	}
+	if(mouse->buttons != 0)
+		goto Rescue;
+	if(i==nil || Dx(i->r)<100 || Dy(i->r)<3*font->height)
+		goto Rescue;
+	oi = i;
+	i = allocwindow(wscreen, oi->r, Refbackup, DWhite);
+	freeimage(oi);
+	if(i == nil)
+		goto Rescue;
+	//border(i, r, Selborder, red, ZP);
+	cornercursor(input, mouse->xy, 1);
+	goto Return;
+
+ Rescue:
+	freeimage(i);
+	i = nil;
+	cornercursor(input, mouse->xy, 1);
+	while(mouse->buttons)
+		readmouse(mousectl);
+
+ Return:
+	moveto(mousectl, mouse->xy);	/* force cursor update; ugly */
+	menuing = FALSE;
+	return i;
+}
+
+void
+drawedge(Image **bp, Rectangle r)
+{
+	Image *b = *bp;
+	if(b != nil && Dx(b->r) == Dx(r) && Dy(b->r) == Dy(r))
+		originwindow(b, r.min, r.min);
+	else{
+		freeimage(b);
+		*bp = allocwindow(wscreen, r, Refbackup, DRed);
+	}
+}
+
+void
+drawborder(Rectangle r, int show)
+{
+	static Image *b[4];
+	int i;
+	if(show == 0){
+		for(i = 0; i < 4; i++){
+			freeimage(b[i]);
+			b[i] = nil;
+		}
+	}else{
+		r = canonrect(r);
+		drawedge(&b[0], Rect(r.min.x, r.min.y, r.min.x+Borderwidth, r.max.y));
+		drawedge(&b[1], Rect(r.min.x+Borderwidth, r.min.y, r.max.x-Borderwidth, r.min.y+Borderwidth));
+		drawedge(&b[2], Rect(r.max.x-Borderwidth, r.min.y, r.max.x, r.max.y));
+		drawedge(&b[3], Rect(r.min.x+Borderwidth, r.max.y-Borderwidth, r.max.x-Borderwidth, r.max.y));
+	}
+}
+
+Image*
+drag(Window *w, Rectangle *rp)
+{
+	Image *ni;
+	Point p, op, d, dm, om;
+	Rectangle r;
+
+	menuing = TRUE;
+	om = mouse->xy;
+	jaysetcursor(&boxcursor, 1);
+	//dm = subpt(mouse->xy, w->screenr.min);
+	//d = subpt(i->r.max, i->r.min);
+	dm = subpt(mouse->xy, w->r.min);
+	d = subpt(w->r.max, w->r.min);
+	op = subpt(mouse->xy, dm);
+	drawborder(Rect(op.x, op.y, op.x+d.x, op.y+d.y), 1);
+	flushimage(display, 1);
+	while(mouse->buttons == 4 || mouse->buttons == 1){
+		p = subpt(mouse->xy, dm);
+		r = Rect(p.x, p.y, p.x+d.x, p.y+d.y);
+		//if(!ptinrect(p, taskPanel->r) && !ptinrect(mouse->xy, taskPanel->r))
+		if(ptinrect(p, windowspace) && ptinrect(mouse->xy, windowspace))
+
+		//if(rectinrect(r, windowspace)){
+			if(!eqpt(p, op)){
+				drawborder(r, 1);
+				flushimage(display, 1);
+				op = p;
+			}
+
+		readmouse(mousectl);
+	}
+	r = Rect(op.x, op.y, op.x+d.x, op.y+d.y);
+	drawborder(r, 0);
+	cornercursor(w, mouse->xy, 1);
+	moveto(mousectl, mouse->xy);	/* force cursor update; ugly */
+	menuing = FALSE;
+	flushimage(display, 1);
+	r = windowMinusTitle(r, w->t);
+	if(mouse->buttons!=0 || (ni=allocwindow(wscreen, r, Refbackup, DWhite))==nil){
+		moveto(mousectl, om);
+		while(mouse->buttons)
+			readmouse(mousectl);
+		*rp = Rect(0, 0, 0, 0);
+		return nil;
+	}
+	//i = w->i;
+	//draw(ni, ni->r, i, nil, i->r.min);
+	*rp = r;
+	return ni;
+}
+
+Point
+cornerpt(Rectangle r, Point p, int which)
+{
+	switch(which){
+	case 0:	/* top left */
+		p = Pt(r.min.x, r.min.y);
+		break;
+	case 2:	/* top right */
+		p = Pt(r.max.x,r.min.y);
+		break;
+	case 6:	/* bottom left */
+		p = Pt(r.min.x, r.max.y);
+		break;
+	case 8:	/* bottom right */
+		p = Pt(r.max.x, r.max.y);
+		break;
+	case 1:	/* top edge */
+		p = Pt(p.x,r.min.y);
+		break;
+	case 5:	/* right edge */
+		p = Pt(r.max.x, p.y);
+		break;
+	case 7:	/* bottom edge */
+		p = Pt(p.x, r.max.y);
+		break;
+	case 3:		/* left edge */
+		p = Pt(r.min.x, p.y);
+		break;
+	}
+	return p;
+}
+
+Rectangle
+whichrect(Rectangle r, Point p, int which)
+{
+	switch(which){
+	case 0:	/* top left */
+		r = Rect(p.x, p.y, r.max.x, r.max.y);
+		break;
+	case 2:	/* top right */
+		r = Rect(r.min.x, p.y, p.x, r.max.y);
+		break;
+	case 6:	/* bottom left */
+		r = Rect(p.x, r.min.y, r.max.x, p.y);
+		break;
+	case 8:	/* bottom right */
+		r = Rect(r.min.x, r.min.y, p.x, p.y);
+		break;
+	case 1:	/* top edge */
+		r = Rect(r.min.x, p.y, r.max.x, r.max.y);
+		break;
+	case 5:	/* right edge */
+		r = Rect(r.min.x, r.min.y, p.x, r.max.y);
+		break;
+	case 7:	/* bottom edge */
+		r = Rect(r.min.x, r.min.y, r.max.x, p.y);
+		break;
+	case 3:		/* left edge */
+		r = Rect(p.x, r.min.y, r.max.x, r.max.y);
+		break;
+	}
+	return canonrect(r);
+}
+
+Image*
+bandsize(Window *w)
+{
+	Image *i;
+	Rectangle r, or;
+	Point p, startp;
+	int which, but;
+
+	p = mouse->xy;
+	but = mouse->buttons;
+	which = whichcorner(w, p);
+	p = cornerpt(w->r, p, which);
+	wmovemouse(w, p);
+	readmouse(mousectl);
+	r = whichrect(w->r, p, which);
+	drawborder(r, 1);
+	or = r;
+	startp = p;
+
+	while(mouse->buttons == but){
+		p = onscreen(mouse->xy);
+		r = whichrect(w->r, p, which);
+		if(!eqrect(r, or) && goodrect(r)){
+			drawborder(r, 1);
+			flushimage(display, 1);
+			or = r;
+		}
+		readmouse(mousectl);
+	}
+	p = mouse->xy;
+	drawborder(or, 0);
+	flushimage(display, 1);
+	wsetcursor(w, 1);
+	if(mouse->buttons!=0 || Dx(or)<100 || Dy(or)<3*font->height){
+		while(mouse->buttons)
+			readmouse(mousectl);
+		return nil;
+	}
+	if(abs(p.x-startp.x)+abs(p.y-startp.y) <= 1)
+		return nil;
+	or = windowMinusTitle(or, w->t);
+	i = allocwindow(wscreen, or, Refbackup, DWhite);
+	if(i == nil)
+		return nil;
+	border(i, r, Selborder, red, ZP);
+	return i;
+}
+
+Window*
+pointto(int wait)
+{
+	Window *w;
+
+	menuing = TRUE;
+	jaysetcursor(&sightcursor, 1);
+	while(mouse->buttons == 0)
+		readmouse(mousectl);
+	if(mouse->buttons == 4)
+		w = wpointto(mouse->xy);
+	else
+		w = nil;
+	if(wait){
+		while(mouse->buttons){
+			if(mouse->buttons!=4 && w !=nil){	/* cancel */
+				cornercursor(input, mouse->xy, 0);
+				w = nil;
+			}
+			readmouse(mousectl);
+		}
+		if(w != nil && wpointto(mouse->xy) != w)
+			w = nil;
+	}
+	cornercursor(input, mouse->xy, 0);
+	moveto(mousectl, mouse->xy);	/* force cursor update; ugly */
+	menuing = FALSE;
+	return w;
+}
+
+void
+delete(void)
+{
+	Window *w;
+
+	w = pointto(TRUE);
+	if(w)
+		wsendctlmesg(w, Deleted, ZR, nil);
+}
+
+void
+resize(void)
+{
+	Window *w;
+	Image *i;
+
+	w = pointto(TRUE);
+	if(w == nil)
+		return;
+	i = sweep();
+	if(i)
+		wsendctlmesg(w, Reshaped, i->r, i);
+}
+
+void
+move(void)
+{
+	Window *w;
+	Image *i;
+	Rectangle r;
+
+	w = pointto(FALSE);
+	if(w == nil)
+		return;
+	i = drag(w, &r);
+	if(i)
+		wsendctlmesg(w, Moved, r, i);
+	cornercursor(input, mouse->xy, 1);
+}
+
+int
+whide(Window *w)
+{
+	Image *i;
+	int j;
+
+	for(j=0; j<nhidden; j++)
+		if(hidden[j] == w)	/* already hidden */
+			return -1;
+	i = allocimage(display, w->screenr, w->i->chan, 0, DWhite);
+	if(i){
+		hidden[nhidden++] = w;
+		wsendctlmesg(w, Reshaped, ZR, i);
+		w->isVisible = 0;
+		wcurrentnext();
+		wbottomme(w);
+		return 1;
+	}
+	return 0;
+}
+
+int
+wunhide(int h)
+{
+	Image *i;
+	Window *w;
+
+	w = hidden[h];
+	i = allocwindow(wscreen, w->i->r, Refbackup, DWhite);
+	if(i){
+		--nhidden;
+		memmove(hidden+h, hidden+h+1, (nhidden-h)*sizeof(Window*));
+		wsendctlmesg(w, Reshaped, w->i->r, i);
+		w->isVisible=1;
+		return 1;
+	}
+	return 0;
+}
+
+void
+hide(void)
+{
+	Window *w;
+
+	w = pointto(TRUE);
+	if(w == nil)
+		return;
+	whide(w);
+}
+
+void
+unhide(int h)
+{
+	Window *w;
+
+	h -= Hidden;
+	w = hidden[h];
+	if(w == nil)
+		return;
+	wunhide(h);
+}
+
+Window*
+new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd,
+    char **argv)
+{
+	Window *w;
+	Mousectl *mc;
+	Channel *cm, *ck, *cctl, *cpid;
+	void **arg;
+
+	if(i == nil)
+		return nil;
+	cm = chancreate(sizeof(Mouse), 0);
+	ck = chancreate(sizeof(Rune*), 0);
+	cctl = chancreate(sizeof(Wctlmesg), 4);
+	cpid = chancreate(sizeof(int), 0);
+	if(cm==nil || ck==nil || cctl==nil)
+		error("new: channel alloc failed");
+	mc = emalloc(sizeof(Mousectl));
+	*mc = *mousectl;
+	mc->image = i;
+	mc->c = cm;
+	w = wmk(i, mc, ck, cctl, scrollit);
+	free(mc);	/* wmk copies *mc */
+	window = erealloc(window, ++nwindow*sizeof(Window*));
+	window[nwindow-1] = w;
+	w->isVisible=!hideit;
+	if(hideit){
+		hidden[nhidden++] = w;
+		w->screenr = ZR;
+		w->r = ZR;
+	}
+	threadcreate(winctl, w, 8192);
+	if(!hideit)
+		wcurrent(w);
+	flushimage(display, 1);
+	if(pid == 0){
+		arg = emalloc(5*sizeof(void*));
+		arg[0] = w;
+		arg[1] = cpid;
+		arg[2] = cmd;
+		if(argv == nil)
+			arg[3] = rcargv;
+		else
+			arg[3] = argv;
+		arg[4] = dir;
+		proccreate(winshell, arg, 8192);
+		pid = recvul(cpid);
+		free(arg);
+	}
+	if(pid == 0){
+		/* window creation failed */
+		wsendctlmesg(w, Deleted, ZR, nil);
+		chanfree(cpid);
+		return nil;
+	}
+	wsetpid(w, pid, 1);
+	wsetname(w);
+	if(dir)
+		w->dir = estrdup(dir);
+	chanfree(cpid);
+	Image *wis = winspace(w);
+	if (wis != nil){
+		wresize(w, wis, 1);
+	}
+	return w;
+}

+ 23 - 0
sys/src/cmd/jay/jay.json

@@ -0,0 +1,23 @@
+{
+	"jay": {
+		"Include": [
+			"/sys/src/cmd/cmd.json"
+		],
+		"Install": "/$ARCH/bin/",
+		"Program": "jay",
+		"SourceFiles": [
+			"config.c",
+			"data.c",
+			"fsys.c",
+			"jay.c",
+			"panel.c",
+			"scrl.c",
+			"smenu.c",
+			"time.c",
+			"util.c",
+			"wctl.c",
+			"wind.c",
+			"xfid.c"
+		]
+	}
+}

+ 98 - 0
sys/src/cmd/jay/panel.c

@@ -0,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+drawstartbutton(TPanel *p, int pressed) {
+  if (p->sb == nil){
+    int x = stringwidth(font, "Harvey");
+    Rectangle r = Rect(p->r.min.x + Borderwidth, p->r.min.y + Borderwidth, p->r.min.x + 1.5*x + 2*Borderwidth, p->r.max.y - Borderwidth);
+    Point pt;
+    pt.x = r.min.x;
+  	pt.x += (Dx(r)-x)/2;
+  	pt.y = Borderwidth + r.min.y;
+    p->sb = allocimage(display, r, CMAP8, 1, jayconfig->taskPanelColor);
+    string(p->sb, pt, wht, pt, font, "Harvey");
+  }
+
+  if(pressed){
+    border3d(p->sb, p->sb->r, 1, blk, wht, p->sb->r.min);
+  } else {
+    border3d(p->sb, p->sb->r, 1, wht, blk, p->sb->r.min);
+  }
+  draw(p->i, p->sb->r, p->sb, nil, p->sb->r.min);
+}
+
+void
+freepanel() {
+  freeimage(taskPanel->i);
+  freeimage(taskPanel->sb);
+  free(taskPanel);
+  taskPanel=nil;
+}
+
+void
+redrawpanel() {
+  if (taskPanel != nil){
+    freepanel();
+  }
+  taskPanel = malloc(sizeof(TPanel));
+  taskPanel->position = Up;
+  taskPanel->sb=nil;
+  taskPanel->r = Rect(view->r.min.x, view->r.min.y, view->r.max.x, view->r.min.y + 30);
+  windowspace = rectsubrect(view->r, taskPanel->r, taskPanel->position);
+  taskPanel->i = allocwindow(wscreen, taskPanel->r, Refbackup, jayconfig->taskPanelColor);
+  if (taskPanel->i == nil){
+    error("can't allocate taskPanel");
+  }
+  drawstartbutton(taskPanel, 0);
+  flushimage(display, 1);
+}
+
+void
+initpanel() {
+  redrawpanel();
+}
+
+void
+pressstartbutton(TPanel *p) {
+  int cleaned = 1;
+  drawstartbutton(p, 1);
+  StartMenu *menu = loadStartMenu(p);
+  for(;;){
+    readmouse(mousectl);
+    if(mousectl->buttons && !ptinrect(mouse->xy, menu->i->r)){
+      freeStartMenu(menu);
+      drawstartbutton(p, 0);
+      return;
+    }
+    if(ptinrect(mouse->xy, menu->i->r)){
+      hoovermenu(menu, mouse->xy);
+      cleaned = 0;
+      if(mouse->buttons == 1){
+        execmenu(menu, mouse->xy);
+        freeStartMenu(menu);
+        drawstartbutton(p, 0);
+        return;
+      }
+    } else if(!cleaned){
+      hoovermenu(menu, mouse->xy);
+      cleaned = 1;
+    }
+  }
+}
+
+void
+clickpanel(Point p, TPanel *tp){
+  if (ptinrect(p, tp->sb->r)){
+    pressstartbutton(tp);
+  }
+}

+ 193 - 0
sys/src/cmd/jay/scrl.c

@@ -0,0 +1,193 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+static Image *scrtmp;
+
+static
+void
+scrtemps(void)
+{
+	int h;
+
+	if(scrtmp)
+		return;
+	h = BIG*Dy(screen->r);
+	scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, DWhite);
+	if(scrtmp == nil)
+		error("scrtemps");
+}
+
+void
+freescrtemps(void)
+{
+	freeimage(scrtmp);
+	scrtmp = nil;
+}
+
+static
+Rectangle
+scrpos(Rectangle r, uint p0, uint p1, uint tot)
+{
+	Rectangle q;
+	int h;
+
+	q = r;
+	h = q.max.y-q.min.y;
+	if(tot == 0)
+		return q;
+	if(tot > 1024*1024){
+		tot>>=10;
+		p0>>=10;
+		p1>>=10;
+	}
+	if(p0 > 0)
+		q.min.y += h*p0/tot;
+	if(p1 < tot)
+		q.max.y -= h*(tot-p1)/tot;
+	if(q.max.y < q.min.y+2){
+		if(q.min.y+2 <= r.max.y)
+			q.max.y = q.min.y+2;
+		else
+			q.min.y = q.max.y-2;
+	}
+	return q;
+}
+
+void
+wscrdraw(Window *w)
+{
+	Rectangle r, r1, r2;
+	Image *b;
+
+	scrtemps();
+	if(w->i == nil)
+		error("scrdraw");
+	r = w->scrollr;
+	b = scrtmp;
+	r1 = r;
+	r1.min.x = 0;
+	r1.max.x = Dx(r);
+	r2 = scrpos(r1, w->org, w->org+w->Frame.nchars, w->nr);
+	if(!eqrect(r2, w->lastsr)){
+		w->lastsr = r2;
+		/* move r1, r2 to (0,0) to avoid clipping */
+		r2 = rectsubpt(r2, r1.min);
+		r1 = rectsubpt(r1, r1.min);
+		draw(b, r1, w->Frame.cols[BORD], nil, ZP);
+		draw(b, r2, w->Frame.cols[BACK], nil, ZP);
+		r2.min.x = r2.max.x-1;
+		draw(b, r2, w->Frame.cols[BORD], nil, ZP);
+		draw(w->i, r, b, nil, Pt(0, r1.min.y));
+	}
+}
+
+void
+wscrsleep(Window *w, uint dt)
+{
+	Timer	*timer;
+	int y, b;
+	static Alt alts[3];
+
+	timer = timerstart(dt);
+	y = w->mc.xy.y;
+	b = w->mc.buttons;
+	alts[0].c = timer->c;
+	alts[0].v = nil;
+	alts[0].op = CHANRCV;
+	alts[1].c = w->mc.c;
+	alts[1].v = (Mouse *)&w->mc;
+	alts[1].op = CHANRCV;
+	alts[2].op = CHANEND;
+	for(;;)
+		switch(alt(alts)){
+		case 0:
+			timerstop(timer);
+			return;
+		case 1:
+			if(abs(w->mc.xy.y-y)>2 || w->mc.buttons!=b){
+				timercancel(timer);
+				return;
+			}
+			break;
+		}
+}
+
+void
+wscroll(Window *w, int but)
+{
+	uint p0, oldp0;
+	Rectangle s;
+	int x, y, my, h, first;
+
+	s = insetrect(w->scrollr, 1);
+	h = s.max.y-s.min.y;
+	x = (s.min.x+s.max.x)/2;
+	oldp0 = ~0;
+	first = TRUE;
+	do{
+		flushimage(display, 1);
+		if(w->mc.xy.x<s.min.x || s.max.x<=w->mc.xy.x){
+			readmouse(&w->mc);
+		}else{
+			my = w->mc.xy.y;
+			if(my < s.min.y)
+				my = s.min.y;
+			if(my >= s.max.y)
+				my = s.max.y;
+			if(!eqpt(w->mc.xy, Pt(x, my))){
+				wmovemouse(w, Pt(x, my));
+				readmouse(&w->mc);		/* absorb event generated by moveto() */
+			}
+			if(but == 2){
+				y = my;
+				if(y > s.max.y-2)
+					y = s.max.y-2;
+				if(w->nr > 1024*1024)
+					p0 = ((w->nr>>10)*(y-s.min.y)/h)<<10;
+				else
+					p0 = w->nr*(y-s.min.y)/h;
+				if(oldp0 != p0)
+					wsetorigin(w, p0, FALSE);
+				oldp0 = p0;
+				readmouse(&w->mc);
+				continue;
+			}
+			if(but == 1)
+				p0 = wbacknl(w, w->org, (my-s.min.y)/w->Frame.font->height);
+			else
+				p0 = w->org+frcharofpt(&w->Frame, Pt(s.max.x, my));
+			if(oldp0 != p0)
+				wsetorigin(w, p0, TRUE);
+			oldp0 = p0;
+			/* debounce */
+			if(first){
+				flushimage(display, 1);
+				sleep(200);
+				nbrecv(w->mc.c, (Mouse *)&w->mc);
+				nbrecv(w->mc.c, (Mouse *)&w->mc);
+				first = FALSE;
+			}
+			wscrsleep(w, 100);
+		}
+	}while(w->mc.buttons & (1<<(but-1)));
+	while(w->mc.buttons)
+		readmouse(&w->mc);
+}

+ 251 - 0
sys/src/cmd/jay/smenu.c

@@ -0,0 +1,251 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+static void
+addEntry(StartMenu *sm, char *name, char *action, StartMenu *submenu, Point p){
+  if (name == nil)
+    return;
+  MenuEntry *e = sm->entries[sm->numEntries] = malloc(sizeof(MenuEntry));
+  sm->numEntries++;
+
+  e->name = estrdup(name);
+  if (action != nil)
+    e->action = estrdup(action);
+  else
+    e->action = nil;
+  e->submenu = submenu;
+
+  Point ssize = stringsize(font, e->name);
+
+  Rectangle r = Rect(p.x, p.y, p.x + ssize.x + 2*Borderwidth, p.y + 2*Borderwidth + ssize.y);
+  e->i = allocimage(display, r, CMAP8, 1, jayconfig->mainMenuColor);
+  e->ih = allocimage(display, r, CMAP8, 1, jayconfig->mainMenuHooverColor);
+
+  Point pt = Pt(p.x + Borderwidth, p.y+Borderwidth);
+  string(e->i, pt, wht, pt, font, e->name);
+  string(e->ih, pt, wht, pt, font, e->name);
+}
+
+static void
+resizeImageEntry(MenuEntry *e, int sizex){
+  Image *aux;
+  aux = e->i;
+  e->i = allocimage(display, Rect(aux->r.min.x, aux->r.min.y, aux->r.min.x + sizex, aux->r.max.y), CMAP8, 1, jayconfig->mainMenuColor);
+  draw(e->i, aux->r, aux, nil, e->i->r.min);
+  freeimage(aux);
+
+  aux = e->ih;
+  e->ih = allocimage(display, Rect(aux->r.min.x, aux->r.min.y, aux->r.min.x + sizex, aux->r.max.y), e->ih->chan, 1, jayconfig->mainMenuHooverColor);
+  draw(e->ih, aux->r, aux, nil, e->ih->r.min);
+  freeimage(aux);
+}
+
+static void
+printEntries(StartMenu *sm) {
+  MenuEntry *e;
+  int maxx = 0;
+  for (int i=0; i<sm->numEntries; i++){
+    e = sm->entries[i];
+    if (Dx(e->i->r) > maxx){
+      maxx = Dx(e->i->r);
+    }
+  }
+
+  if (maxx < Dx(sm->i->r)){
+    maxx = Dx(sm->i->r);
+  }
+
+
+  for (int i=0; i<sm->numEntries; i++){
+    e = sm->entries[i];
+    resizeImageEntry(e, maxx);
+    draw(sm->i, e->i->r, e->i, nil, e->i->r.min);
+  }
+}
+
+void
+hoovermenu(StartMenu *sm, Point p) {
+  MenuEntry *e;
+  for(int i=0; i<sm->numEntries; i++){
+    e = sm->entries[i];
+    if(ptinrect(p, e->i->r)){
+      draw(sm->i, e->i->r, e->ih, nil, e->i->r.min);
+    } else {
+      draw(sm->i, e->i->r, e->i, nil, e->i->r.min);
+    }
+  }
+}
+
+void
+execmenu(StartMenu *sm, Point p) {
+  MenuEntry *e;
+  for(int i=0; i<sm->numEntries; i++){
+    e = sm->entries[i];
+    if(ptinrect(p, e->i->r) && e->action != nil){
+      new(wcenter(1.6*400, 400), FALSE, scrolling, 0, nil, e->action, nil);
+    }
+  }
+}
+
+void
+freeStartMenu(StartMenu *sm) {
+  if(sm == nil)
+    return;
+  MenuEntry *e;
+  for (int i=0; i<sm->numEntries; i++){
+    e = sm->entries[i];
+    freeimage(e->i);
+    freeimage(e->ih);
+    free(e->name);
+    free(e->action);
+    freeStartMenu(e->submenu);
+    free(e);
+  }
+  sm->numEntries = 0;
+  freeimage(sm->i);
+  free(sm);
+}
+
+static char *
+getcharproperty(char *start, char *end, char *property){
+  char *s, *aux, *r;
+  int size;
+  s = start;
+  while (s < end && *s!='}' && strncmp(s, property, strlen(property))){
+    if(*s == '['){
+      //ignore submenu
+      int o = 1;
+      s++;
+      while ( s < end && o > 0 ){
+        while (s < end && *s != ']'){
+          if(*s == '['){
+            o++;
+          }
+          s++;
+        }
+        o--;
+      }
+    }
+    s++;
+  }
+  while(s < end && *s!='}' && *s!='"'){
+    s++;
+  }
+  if (s==end)
+    return nil;
+  aux = ++s;
+  while(s < end && *s!='}' && *s!='"'){
+    s++;
+  }
+  if (s==end || *s == '}' || s == aux)
+    return nil;
+  size = s - aux;
+  r = malloc(size + 1);
+  strncpy(r, aux, size);
+  aux=r;
+  aux += size;
+  *aux = '\0';
+  return r;
+}
+
+static StartMenu *
+parsesubmenuproperty(char *start, char *end){
+  //TODO
+  return nil;
+}
+
+static void
+parseentry(StartMenu *menu, char *start, char *end) {
+  char *name, *action;
+  Point p;
+  if (menu->numEntries==0) {
+    p = menu->i->r.min;
+  } else {
+    p = Pt(menu->i->r.min.x, menu->entries[menu->numEntries - 1]->i->r.max.y);
+  }
+  name = getcharproperty(start, end, "name");
+  action = getcharproperty(start, end, "action");
+  addEntry(menu, name, action, parsesubmenuproperty(start, end), p);
+  free(name);
+  free(action);
+}
+
+
+static StartMenu *
+parsemenu(char *start, char *end, Point p, int minSize){
+  StartMenu *sm;
+  char *s, *e;
+  int o;
+  s = start;
+  if (*s != '['){
+    return nil;
+  }
+  sm = malloc(sizeof(StartMenu));
+  sm->numEntries=0;
+  sm->i = allocwindow(wscreen, Rect(p.x +2, p.y + 2, p.x + minSize, p.y + 1.6*minSize), Refnone, jayconfig->mainMenuColor);
+
+  while(s < end){
+    while (s < end && *s !='{'){
+      if(*s == ']')
+        return sm;
+      s++;
+    }
+    if(*s != '{')
+      return nil;
+
+    e = s + 1;
+    o = 1;
+    while (e < end && o > 0){
+      while (e < end && *e !='}'){
+        if (*e == '{')
+          o++;
+        e++;
+      }
+      o--;
+    }
+    if (e == end){
+      freeStartMenu(sm);
+      return nil;
+    }
+    parseentry(sm, s, e);
+    s = ++e;
+  }
+  return sm;
+}
+
+static StartMenu *
+parsemenufile(TPanel *p, const char *path){
+  int fd;
+  Dir *d;
+  char *s;
+
+  fd = open(path, OREAD);
+  if (fd < 0){
+    error("openning menu file");
+    return nil;
+  }
+  d = dirfstat(fd);
+  s = malloc(sizeof(char) * d->length);
+  if (read(fd, s, d->length)>0){
+    close(fd);
+    return parsemenu(s, s + d->length - 1, Pt(p->r.min.x, p->r.max.y), 100);
+  }
+  return nil;
+}
+
+
+StartMenu *
+loadStartMenu(TPanel *p){
+  StartMenu *sm = parsemenufile(p, "/usr/harvey/lib/menu.conf");
+  printEntries(sm);
+  return sm;
+}

+ 133 - 0
sys/src/cmd/jay/time.c

@@ -0,0 +1,133 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+static Channel*	ctimer;	/* chan(Timer*)[100] */
+static Timer *timer;
+
+static
+uint
+msec(void)
+{
+	return nsec()/1000000;
+}
+
+void
+timerstop(Timer *t)
+{
+	t->next = timer;
+	timer = t;
+}
+
+void
+timercancel(Timer *t)
+{
+	t->cancel = TRUE;
+}
+
+static
+void
+timerproc(void* vacio)
+{
+	int i, nt, na, dt, del;
+	Timer **t, *x;
+	uint old, new;
+
+	rfork(RFFDG);
+	threadsetname("TIMERPROC");
+	t = nil;
+	na = 0;
+	nt = 0;
+	old = msec();
+	for(;;){
+		sleep(1);	/* will sleep minimum incr */
+		new = msec();
+		dt = new-old;
+		old = new;
+		if(dt < 0)	/* timer wrapped; go around, losing a tick */
+			continue;
+		for(i=0; i<nt; i++){
+			x = t[i];
+			x->dt -= dt;
+			del = 0;
+			if(x->cancel){
+				timerstop(x);
+				del = 1;
+			}else if(x->dt <= 0){
+				/*
+				 * avoid possible deadlock if client is
+				 * now sending on ctimer
+				 */
+				if(nbsendul(x->c, 0) > 0)
+					del = 1;
+			}
+			if(del){
+				memmove(&t[i], &t[i+1], (nt-i-1)*sizeof t[0]);
+				--nt;
+				--i;
+			}
+		}
+		if(nt == 0){
+			x = recvp(ctimer);
+	gotit:
+			if(nt == na){
+				na += 10;
+				t = realloc(t, na*sizeof(Timer*));
+				if(t == nil)
+					abort();
+			}
+			t[nt++] = x;
+			old = msec();
+		}
+		if(nbrecv(ctimer, &x) > 0)
+			goto gotit;
+	}
+}
+
+void
+timerinit(void)
+{
+	ctimer = chancreate(sizeof(Timer*), 100);
+	proccreate(timerproc, nil, STACK);
+}
+
+/*
+ * timeralloc() and timerfree() don't lock, so can only be
+ * called from the main proc.
+ */
+
+Timer*
+timerstart(int dt)
+{
+	Timer *t;
+
+	t = timer;
+	if(t)
+		timer = timer->next;
+	else{
+		t = emalloc(sizeof(Timer));
+		t->c = chancreate(sizeof(int), 0);
+	}
+	t->next = nil;
+	t->dt = dt;
+	t->cancel = FALSE;
+	sendp(ctimer, t);
+	return t;
+}

+ 190 - 0
sys/src/cmd/jay/util.c

@@ -0,0 +1,190 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
+{
+	uint8_t *q;
+	Rune *s;
+	int j, w;
+
+	/*
+	 * Always guaranteed that n bytes may be interpreted
+	 * without worrying about partial runes.  This may mean
+	 * reading up to UTFmax-1 more bytes than n; the caller
+	 * knows this.  If n is a firm limit, the caller should
+	 * set p[n] = 0.
+	 */
+	q = (uint8_t*)p;
+	s = r;
+	for(j=0; j<n; j+=w){
+		if(*q < Runeself){
+			w = 1;
+			*s = *q++;
+		}else{
+			w = chartorune(s, (char*)q);
+			q += w;
+		}
+		if(*s)
+			s++;
+		else if(nulls)
+				*nulls = TRUE;
+	}
+	*nb = (char*)q-p;
+	*nr = s-r;
+}
+
+void
+error(char *s)
+{
+	fprint(2, "jay: %s: %r\n", s);
+	if(errorshouldabort)
+		abort();
+	threadexitsall("error");
+}
+
+void*
+erealloc(void *p, uint n)
+{
+	p = realloc(p, n);
+	if(p == nil)
+		error("realloc failed");
+	return p;
+}
+
+void*
+emalloc(uint n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil)
+		error("malloc failed");
+	memset(p, 0, n);
+	return p;
+}
+
+char*
+estrdup(char *s)
+{
+	char *p;
+
+	p = malloc(strlen(s)+1);
+	if(p == nil)
+		error("strdup failed");
+	strcpy(p, s);
+	return p;
+}
+
+int
+isalnum(Rune c)
+{
+	/*
+	 * Hard to get absolutely right.  Use what we know about ASCII
+	 * and assume anything above the Latin control characters is
+	 * potentially an alphanumeric.
+	 */
+	if(c <= ' ')
+		return FALSE;
+	if(0x7F<=c && c<=0xA0)
+		return FALSE;
+	if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+		return FALSE;
+	return TRUE;
+}
+
+Rune*
+strrune(Rune *s, Rune c)
+{
+	Rune c1;
+
+	if(c == 0) {
+		while(*s++)
+			;
+		return s-1;
+	}
+
+	while((c1 = *s++) != 0)
+		if(c1 == c)
+			return s-1;
+	return nil;
+}
+
+int
+min(int a, int b)
+{
+	if(a < b)
+		return a;
+	return b;
+}
+
+int
+max(int a, int b)
+{
+	if(a > b)
+		return a;
+	return b;
+}
+
+char*
+runetobyte(Rune *r, int n, int *ip)
+{
+	char *s;
+	int m;
+
+	s = emalloc(n*UTFmax+1);
+	m = snprint(s, n*UTFmax+1, "%.*S", n, r);
+	*ip = m;
+	return s;
+}
+
+Image *
+wcenter(int sizex, int sizey){
+	int x, y;
+	Rectangle r = windowspace;
+	x = (r.max.x - sizex)/2;
+	y = (r.max.y - sizey)/2;
+	return allocwindow(wscreen, Rect(x, y, x + sizex, y + sizey), Refbackup, DWhite);
+}
+
+void
+printPoints(Rectangle r) {
+	print("min <%d> <%d> - max <%d> <%d>", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+Rectangle
+rectsubrect(Rectangle r1, Rectangle r2, int position){
+	switch (position) {
+		case Up:
+			r1.min.y = r2.max.y;
+			break;
+		case Down:
+			r1.max.y = r2.min.y;
+			break;
+		case Left:
+			r1.min.x = r2.max.x;
+			break;
+		case Right:
+			r1.max.x = r2.min.x;
+			break;
+	}
+	return r1;
+}

+ 527 - 0
sys/src/cmd/jay/wctl.c

@@ -0,0 +1,527 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+#include <ctype.h>
+
+char	Ebadwr[]		= "bad rectangle in wctl request";
+char	Ewalloc[]		= "window allocation failed in wctl request";
+
+/* >= Top are disallowed if mouse button is pressed */
+enum
+{
+	New,
+	Resize,
+	Move,
+	Scroll,
+	Noscroll,
+	Set,
+	Top,
+	Bottom,
+	Current,
+	Hide,
+	Unhide,
+	Delete,
+};
+
+static char *cmds[] = {
+	[New]	= "new",
+	[Resize]	= "resize",
+	[Move]	= "move",
+	[Scroll]	= "scroll",
+	[Noscroll]	= "noscroll",
+	[Set]		= "set",
+	[Top]	= "top",
+	[Bottom]	= "bottom",
+	[Current]	= "current",
+	[Hide]	= "hide",
+	[Unhide]	= "unhide",
+	[Delete]	= "delete",
+	nil
+};
+
+enum
+{
+	Cd,
+	Deltax,
+	Deltay,
+	Hidden,
+	Id,
+	Maxx,
+	Maxy,
+	Minx,
+	Miny,
+	PID,
+	R,
+	Scrolling,
+	Noscrolling,
+};
+
+static char *params[] = {
+	[Cd]	 			= "-cd",
+	[Deltax]			= "-dx",
+	[Deltay]			= "-dy",
+	[Hidden]			= "-hide",
+	[Id]				= "-id",
+	[Maxx]			= "-maxx",
+	[Maxy]			= "-maxy",
+	[Minx]			= "-minx",
+	[Miny]			= "-miny",
+	[PID]				= "-pid",
+	[R]				= "-r",
+	[Scrolling]			= "-scroll",
+	[Noscrolling]		= "-noscroll",
+	nil
+};
+
+/*
+ * Check that newly created window will be of manageable size
+ */
+int
+goodrect(Rectangle r)
+{
+	if(!eqrect(canonrect(r), r))
+		return 0;
+	if(Dx(r)<100 || Dy(r)<3*font->height)
+		return 0;
+	/* must have some screen and border visible so we can move it out of the way */
+	if(Dx(r) >= Dx(screen->r) && Dy(r) >= Dy(screen->r))
+		return 0;
+	/* reasonable sizes only please */
+	if(Dx(r) > BIG*Dx(screen->r))
+		return 0;
+	if(Dy(r) > BIG*Dx(screen->r))
+		return 0;
+	return 1;
+}
+
+static
+int
+word(char **sp, char *tab[])
+{
+	char *s, *t;
+	int i;
+
+	s = *sp;
+	while(isspace(*s))
+		s++;
+	t = s;
+	while(*s!='\0' && !isspace(*s))
+		s++;
+	for(i=0; tab[i]!=nil; i++)
+		if(strncmp(tab[i], t, strlen(tab[i])) == 0){
+			*sp = s;
+			return i;
+	}
+	return -1;
+}
+
+int
+set(int sign, int neg, int abs, int pos)
+{
+	if(sign < 0)
+		return neg;
+	if(sign > 0)
+		return pos;
+	return abs;
+}
+
+Rectangle
+newrect(void)
+{
+	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);
+	minx = 32 + 16*i;
+	miny = 32 + 16*i;
+	i++;
+	i %= 10;
+
+	return Rect(minx, miny, minx+dx, miny+dy);
+}
+
+void
+shift(int *minp, int *maxp, int min, int max)
+{
+	if(*minp < min){
+		*maxp += min-*minp;
+		*minp = min;
+	}
+	if(*maxp > max){
+		*minp += max-*maxp;
+		*maxp = max;
+	}
+}
+
+Rectangle
+rectonscreen(Rectangle r)
+{
+	shift(&r.min.x, &r.max.x, screen->r.min.x, screen->r.max.x);
+	shift(&r.min.y, &r.max.y, screen->r.min.y, screen->r.max.y);
+	return r;
+}
+
+/* permit square brackets, in the manner of %R */
+int
+jaystrtol(char *s, char **t)
+{
+	int n;
+
+	while(*s!='\0' && (*s==' ' || *s=='\t' || *s=='['))
+		s++;
+	if(*s == '[')
+		s++;
+	n = strtol(s, t, 10);
+	if(*t != s)
+		while((*t)[0] == ']')
+			(*t)++;
+	return n;
+}
+
+
+int
+parsewctl(char **argp, Rectangle r, Rectangle *rp, int *pidp, int *idp,
+	  int *hiddenp, int *scrollingp, char **cdp, char *s,
+	  char *err)
+{
+	int cmd, param, xy, sign;
+	char *t;
+
+	*pidp = 0;
+	*hiddenp = 0;
+	*scrollingp = scrolling;
+	*cdp = nil;
+	cmd = word(&s, cmds);
+	if(cmd < 0){
+		strcpy(err, "unrecognized wctl command");
+		return -1;
+	}
+	if(cmd == New)
+		r = newrect();
+
+	strcpy(err, "missing or bad wctl parameter");
+	while((param = word(&s, params)) >= 0){
+		switch(param){	/* special cases */
+		case Hidden:
+			*hiddenp = 1;
+			continue;
+		case Scrolling:
+			*scrollingp = 1;
+			continue;
+		case Noscrolling:
+			*scrollingp = 0;
+			continue;
+		case R:
+			r.min.x = jaystrtol(s, &t);
+			if(t == s)
+				return -1;
+			s = t;
+			r.min.y = jaystrtol(s, &t);
+			if(t == s)
+				return -1;
+			s = t;
+			r.max.x = jaystrtol(s, &t);
+			if(t == s)
+				return -1;
+			s = t;
+			r.max.y = jaystrtol(s, &t);
+			if(t == s)
+				return -1;
+			s = t;
+			continue;
+		}
+		while(isspace(*s))
+			s++;
+		if(param == Cd){
+			*cdp = s;
+			while(*s && !isspace(*s))
+				s++;
+			if(*s != '\0')
+				*s++ = '\0';
+			continue;
+		}
+		sign = 0;
+		if(*s == '-'){
+			sign = -1;
+			s++;
+		}else if(*s == '+'){
+			sign = +1;
+			s++;
+		}
+		if(!isdigit(*s))
+			return -1;
+		xy = jaystrtol(s, &s);
+		switch(param){
+		case -1:
+			strcpy(err, "unrecognized wctl parameter");
+			return -1;
+		case Minx:
+			r.min.x = set(sign, r.min.x-xy, xy, r.min.x+xy);
+			break;
+		case Miny:
+			r.min.y = set(sign, r.min.y-xy, xy, r.min.y+xy);
+			break;
+		case Maxx:
+			r.max.x = set(sign, r.max.x-xy, xy, r.max.x+xy);
+			break;
+		case Maxy:
+			r.max.y = set(sign, r.max.y-xy, xy, r.max.y+xy);
+			break;
+		case Deltax:
+			r.max.x = set(sign, r.max.x-xy, r.min.x+xy, r.max.x+xy);
+			break;
+		case Deltay:
+			r.max.y = set(sign, r.max.y-xy, r.min.y+xy, r.max.y+xy);
+			break;
+		case Id:
+			if(idp != nil)
+				*idp = xy;
+			break;
+		case PID:
+			if(pidp != nil)
+				*pidp = xy;
+			break;
+		}
+	}
+
+	*rp = rectonscreen(rectaddpt(r, screen->r.min));
+
+	while(isspace(*s))
+		s++;
+	if(cmd!=New && *s!='\0'){
+		strcpy(err, "extraneous text in wctl message");
+		return -1;
+	}
+
+	if(argp)
+		*argp = s;
+
+	return cmd;
+}
+
+int
+wctlnew(Rectangle rect, char *arg, int pid, int hideit, int scrollit,
+	char *dir, char *err)
+{
+	char **argv;
+	Image *i;
+
+	if(!goodrect(rect)){
+		strcpy(err, Ebadwr);
+		return -1;
+	}
+	argv = emalloc(4*sizeof(char*));
+	argv[0] = "rc";
+	argv[1] = "-c";
+	while(isspace(*arg))
+		arg++;
+	if(*arg == '\0'){
+		argv[1] = "-i";
+		argv[2] = nil;
+	}else{
+		argv[2] = arg;
+		argv[3] = nil;
+	}
+	if(hideit)
+		i = allocimage(display, rect, screen->chan, 0, DWhite);
+	else
+		i = allocwindow(wscreen, rect, Refbackup, DWhite);
+	if(i == nil){
+		strcpy(err, Ewalloc);
+		return -1;
+	}
+	//border(i, rect, Selborder, red, ZP);
+
+	new(i, hideit, scrollit, pid, dir, "/bin/rc", argv);
+
+	free(argv);	/* when new() returns, argv and args have been copied */
+	return 1;
+}
+
+int
+writewctl(Xfid *x, char *err)
+{
+	int cnt, cmd, j, id, hideit, scrollit, pid;
+	Image *i;
+	char *arg, *dir;
+	Rectangle rect;
+	Window *w;
+
+	w = x->f->w;
+	cnt = x->Fcall.count;
+	x->Fcall.data[cnt] = '\0';
+	id = 0;
+
+	rect = rectsubpt(w->screenr, screen->r.min);
+	cmd = parsewctl(&arg, rect, &rect, &pid, &id, &hideit, &scrollit, &dir, x->Fcall.data, err);
+	if(cmd < 0)
+		return -1;
+
+	if(mouse->buttons!=0 && cmd>=Top){
+		strcpy(err, "action disallowed when mouse active");
+		return -1;
+	}
+
+	if(id != 0){
+		for(j=0; j<nwindow; j++)
+			if(window[j]->id == id)
+				break;
+		if(j == nwindow){
+			strcpy(err, "no such window id");
+			return -1;
+		}
+		w = window[j];
+		if(w->deleted || w->i==nil){
+			strcpy(err, "window deleted");
+			return -1;
+		}
+	}
+
+	switch(cmd){
+	case New:
+		return wctlnew(rect, arg, pid, hideit, scrollit, dir, err);
+	case Set:
+		if(pid > 0)
+			wsetpid(w, pid, 0);
+		return 1;
+	case Move:
+		rect = Rect(rect.min.x, rect.min.y, rect.min.x+Dx(w->r), rect.min.y+Dy(w->r));
+		rect = rectonscreen(rect);
+		/* fall through */
+	case Resize:
+		if(!goodrect(rect)){
+			strcpy(err, Ebadwr);
+			return -1;
+		}
+		if(eqrect(rect, w->r))
+			return 1;
+		i = allocwindow(wscreen, rect, Refbackup, DWhite);
+		if(i == nil){
+			strcpy(err, Ewalloc);
+			return -1;
+		}
+		//border(i, rect, Selborder, red, ZP);
+		wsendctlmesg(w, Reshaped, i->r, i);
+		return 1;
+	case Scroll:
+		w->scrolling = 1;
+		wshow(w, w->nr);
+		wsendctlmesg(w, Wakeup, ZR, nil);
+		return 1;
+	case Noscroll:
+		w->scrolling = 0;
+		wsendctlmesg(w, Wakeup, ZR, nil);
+		return 1;
+	case Top:
+		wtopme(w);
+		return 1;
+	case Bottom:
+		wbottomme(w);
+		return 1;
+	case Current:
+		wcurrent(w);
+		return 1;
+	case Hide:
+		switch(whide(w)){
+		case -1:
+			strcpy(err, "window already hidden");
+			return -1;
+		case 0:
+			strcpy(err, "hide failed");
+			return -1;
+		default:
+			break;
+		}
+		return 1;
+	case Unhide:
+		for(j=0; j<nhidden; j++)
+			if(hidden[j] == w)
+				break;
+		if(j == nhidden){
+			strcpy(err, "window not hidden");
+			return -1;
+		}
+		if(wunhide(j) == 0){
+			strcpy(err, "hide failed");
+			return -1;
+		}
+		return 1;
+	case Delete:
+		wsendctlmesg(w, Deleted, ZR, nil);
+		return 1;
+	}
+	strcpy(err, "invalid wctl message");
+	return -1;
+}
+
+void
+wctlthread(void *v)
+{
+	char *buf, *arg, *dir;
+	int cmd, id, pid, hideit, scrollit;
+	Rectangle rect;
+	char err[ERRMAX];
+	Channel *c;
+
+	c = v;
+
+	threadsetname("WCTLTHREAD");
+
+	for(;;){
+		buf = recvp(c);
+		cmd = parsewctl(&arg, ZR, &rect, &pid, &id, &hideit, &scrollit, &dir, buf, err);
+
+		switch(cmd){
+		case New:
+			wctlnew(rect, arg, pid, hideit, scrollit, dir, err);
+		}
+		free(buf);
+	}
+}
+
+void
+wctlproc(void *v)
+{
+	char *buf;
+	int n, eofs;
+	Channel *c;
+
+	threadsetname("WCTLPROC");
+	c = v;
+
+	eofs = 0;
+	for(;;){
+		buf = emalloc(messagesize);
+		n = read(wctlfd, buf, messagesize-1);	/* room for \0 */
+		if(n < 0)
+			break;
+		if(n == 0){
+			if(++eofs > 20)
+				break;
+			continue;
+		}
+		eofs = 0;
+
+		buf[n] = '\0';
+		sendp(c, buf);
+	}
+}

+ 1916 - 0
sys/src/cmd/jay/wind.c

@@ -0,0 +1,1916 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include <complete.h>
+#include "dat.h"
+#include "fns.h"
+
+#define MOVEIT if(0)
+
+enum
+{
+	HiWater	= 640000,	/* max size of history */
+	LoWater	= 400000,	/* min size of history after max'ed */
+	MinWater	= 20000,	/* room to leave available when reallocating */
+};
+
+static	int		topped;
+static	int		id;
+
+static	Image	*cols[NCOL];
+static  Image *fronttext;
+static	Image	*backtext;
+static	Cursor	*lastcursor;
+static	Image	*titlecol;
+static	Image	*lighttitlecol;
+static	Image	*holdcol;
+static	Image	*lightholdcol;
+static	Image	*paleholdcol;
+
+Rectangle
+inserttitlebar(Rectangle r, int n)
+{
+	r.min.y += n;
+	return r;
+}
+
+void
+winsertButtons(Window *w){
+	//closebutton
+	int x = w->t->r.max.x - Borderwidth - Dx(closebutton->r);
+	int y = w->t->r.min.y + Borderwidth;
+	Rectangle r = Rect(x, y, x + Dx(closebutton->r),  y + Dy(closebutton->r));
+	draw(w->t, r, closebutton, nil, closebutton->r.min);
+	w->bc = r;
+
+	//maximizebutton
+	x = x - (2 * Borderwidth) - Dx(maximizebutton->r);
+	r = Rect(x, y, x + Dx(maximizebutton->r),  y + Dy(maximizebutton->r));
+	draw(w->t, r, maximizebutton, nil, maximizebutton->r.min);
+	w->bmax = r;
+
+	//minimizebutton
+	x = x - (2 * Borderwidth) - Dx(minimizebutton->r);
+	r = Rect(x, y, x + Dx(minimizebutton->r),  y + Dy(minimizebutton->r));
+	draw(w->t, r, minimizebutton, nil, minimizebutton->r.min);
+	w->bmin = r;
+}
+
+void
+whooverbutton(Point p, Window *w) {
+	if(ptinrect(p, w->bc)){
+		draw(w->t, w->bc, closebuttonhoover, nil, closebutton->r.min);
+		draw(w->t, w->bmax, maximizebutton, nil, maximizebutton->r.min);
+		draw(w->t, w->bmin, minimizebutton, nil, minimizebutton->r.min);
+		return;
+	} else if(ptinrect(p, w->bmax)){
+		draw(w->t, w->bc, closebutton, nil, closebutton->r.min);
+		draw(w->t, w->bmax, maximizebuttonhoover, nil, maximizebutton->r.min);
+		draw(w->t, w->bmin, minimizebutton, nil, minimizebutton->r.min);
+	} else if(ptinrect(p, w->bmin)) {
+		draw(w->t, w->bc, closebutton, nil, closebutton->r.min);
+		draw(w->t, w->bmax, maximizebutton, nil, maximizebutton->r.min);
+		draw(w->t, w->bmin, minimizebuttonhoover, nil, minimizebutton->r.min);
+	}	else {
+		wsettitle(w);
+	}
+}
+
+void
+wcleantitle(Window *w) {
+	Image *i;
+	if (w->t == nil)
+		return;
+	i = allocimage(display, w->t->r, CMAP8, 1, jayconfig->windowTitleColor);
+	draw(w->t, w->t->r, i, nil, w->t->r.min);
+	freeimage(i);
+}
+
+
+void
+wsettitle(Window *w) {
+	int x;
+	char *s, *e;
+	Point p;
+	Image *i;
+	if (w->t == nil)
+		return;
+	wcleantitle(w);
+	winsertButtons(w);
+	i = allocimage(display, w->t->r, CMAP8, 1, jayconfig->windowTitleFontColor);
+	s = estrdup(w->label);
+	x = stringwidth(font, s);
+	e = s + strlen(s);
+
+	while (x > (Dx(w->t->r)+2*Borderwidth)){
+		e--;
+		*e='\0';
+		x = stringwidth(font, s);
+	}
+	p.x = w->t->r.min.x;
+	p.x += (Dx(w->t->r)-x)/2;
+	p.y = Borderwidth + 2 + w->t->r.min.y;
+
+	string(w->t, p, i, p, font, s);
+	free(s);
+	freeimage(i);
+}
+
+Image *
+getTitleWindow(Image *i){
+	Rectangle w = i->r;
+	Point p = stringsize(font, "Á");
+	Rectangle t = Rect(w.min.x, w.min.y - 2*Borderwidth - p.y, w.max.x,w.min.y);
+	return allocwindow(wscreen, t, Refbackup, jayconfig->windowTitleColor);
+}
+
+Rectangle
+insertOnTop(Rectangle ori, Rectangle top){
+	return Rect(ori.min.x, top.max.y,ori.max.x,ori.max.y);
+}
+
+Rectangle
+windowMinusTitle(Rectangle r, Image *t){
+	if (t == nil)
+		return r;
+	Point p = stringsize(font, "Á");
+	return Rect(r.min.x, r.min.y + 2*Borderwidth + p.y, r.max.x, r.max.y);
+}
+
+
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+	Window *w;
+	Rectangle r;
+
+	if(cols[0] == nil){
+		/* greys are multiples of 0x11111100+0xFF, 14* being palest */
+		fronttext = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowFrontTextColor);
+		backtext = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowBackTextColor);
+		cols[BACK] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowBackgroundColor);
+		cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowSelectedColor);
+		cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowScrollBarFrontColor);
+		cols[TEXT] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowTextCursorColor);
+		cols[HTEXT] = fronttext;
+		titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowInTopBorder);
+		lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowInBottomBorder);
+		holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, jayconfig->windowInTopBorder);
+		lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DBlack);
+		paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DBlack);
+	}
+	w = emalloc(sizeof(Window));
+	w->t = getTitleWindow(i);
+	w->i = i;
+	w->r = Rect(w->t->r.min.x,w->t->r.min.y, w->i->r.max.x,w->i->r.max.y);
+	w->screenr = w->i->r;
+	r = insetrect(w->i->r, Selborder+1);
+	w->mc = *mc;
+	w->ck = ck;
+	w->cctl = cctl;
+	w->cursorp = nil;
+	w->conswrite = chancreate(sizeof(Conswritemesg), 0);
+	w->consread =  chancreate(sizeof(Consreadmesg), 0);
+	w->mouseread =  chancreate(sizeof(Mousereadmesg), 0);
+	w->wctlread =  chancreate(sizeof(Consreadmesg), 0);
+	w->scrollr = r;
+	//w->scrollr.max.x = r.min.x+Scrollwid;
+	w->scrollr.min.x = r.max.x - Scrollwid;
+	w->lastsr = ZR;
+	//r.min.x += Scrollwid+Scrollgap;
+	r.max.x -= Scrollwid+Scrollgap;
+	frinit(&w->Frame, r, font, i, cols);
+	w->Frame.maxtab = maxtab*stringwidth(font, "0");
+	w->topped = ++topped;
+	w->id = ++id;
+	w->notefd = -1;
+	w->scrolling = scrolling;
+	w->dir = estrdup(startdir);
+	w->label = estrdup("<unnamed>");
+	w->maximized = 0;
+	r = insetrect(w->i->r, Selborder);
+	draw(w->i, r, cols[BACK], nil, w->Frame.entire.min);
+	wborder(w, Selborder);
+	wscrdraw(w);
+	incref(&w->Ref);	/* ref will be removed after mounting; avoids delete before ready to be deleted */
+	return w;
+}
+
+void
+wsetname(Window *w)
+{
+	int i, n;
+	char err[ERRMAX];
+	char namet[32 + 6]; // 32(w->name) + 6(.title)
+
+	n = sprint(w->name, "window.%d.%d", w->id, w->namecount++);
+	for(i='A'; i<='Z'; i++){
+		if(nameimage(w->i, w->name, 1) > 0){
+			if (w->t != nil){
+				nameimage(w->t, namet, 1);
+					return;
+			} else
+				return;
+		}
+		errstr(err, sizeof err);
+		if(strcmp(err, "image name in use") != 0)
+			break;
+		w->name[n] = i;
+		w->name[n+1] = 0;
+	}
+	w->name[0] = 0;
+	fprint(2, "jay: setname failed: %s\n", err);
+}
+
+Image *
+winspace(Window *w){
+	int d, m=0;
+	Rectangle r = w->r;
+	if (w->r.min.y < windowspace.min.y){
+		d = windowspace.min.y - w->r.min.y + 1;
+		r.min.y = w->r.min.y + d;
+		r.max.y = w->r.max.y + d;
+		m=1;
+	}
+	if (w->r.min.x < windowspace.min.x){
+		d = windowspace.min.x - w->r.min.x + 1;
+		r.min.x = w->r.min.x + d;
+		r.max.x = w->r.max.x + d;
+		m=1;
+	}
+	if (m){
+		r = windowMinusTitle(r, w->t);
+		if(w->isVisible){
+			return allocwindow(wscreen, r, Refbackup, DWhite);
+		}
+		return allocimage(display, r, w->t->chan, 0, DWhite);
+	}
+	return nil;
+}
+
+void
+wresize(Window *w, Image *i, int move)
+{
+	Rectangle r, or;
+	or = w->i->r;
+	Image *it, *oldI, *wis;
+
+	if(0 == Dx(w->screenr) && 0 == Dy(w->screenr)){
+		it = allocimage(display, w->t->r, w->t->chan, 0, DWhite);
+	} else {
+		it = getTitleWindow(i);
+		oldI= i;
+		i = allocwindow(wscreen, insertOnTop(i->r, it->r), Refbackup, DWhite);
+		freeimage(oldI);
+	}
+	if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r))){
+		draw(i, i->r, w->i, nil, w->i->r.min);
+		draw(it,it->r,w->t,nil, w->t->r.min);
+	}
+	freeimage(w->i);
+	freeimage(w->t);
+	w->i = i;
+	w->t = it;
+	wsettitle(w);
+	w->screenr = i->r;
+	w->r = Rect(w->t->r.min.x,w->t->r.min.y, w->i->r.max.x,w->i->r.max.y);
+	wsetname(w);
+	w->mc.image = i;
+	r = insetrect(i->r, Selborder+1);
+	w->scrollr = r;
+	//w->scrollr.max.x = r.min.x+Scrollwid;
+	w->scrollr.min.x = r.max.x - Scrollwid;
+	w->lastsr = ZR;
+	//r.min.x += Scrollwid+Scrollgap;
+	r.max.x -= Scrollwid+Scrollgap;
+	if(move)
+		frsetrects(&w->Frame, r, w->i);
+	else{
+		frclear(&w->Frame, FALSE);
+		frinit(&w->Frame, r, w->Frame.font, w->i, cols);
+		wsetcols(w);
+		w->Frame.maxtab = maxtab*stringwidth(w->Frame.font, "0");
+		r = insetrect(w->i->r, Selborder);
+		draw(w->i, r, cols[BACK], nil, w->Frame.entire.min);
+		wfill(w);
+		wsetselect(w, w->q0, w->q1);
+		wscrdraw(w);
+	}
+	wborder(w, Selborder);
+	w->topped = ++topped;
+	w->resized = TRUE;
+	w->mouse.counter++;
+	wis = winspace(w);
+	if (wis != nil){
+		wresize(w, wis, 1);
+	}
+}
+
+void
+wrefresh(Window *w, Rectangle rec)
+{
+	/* BUG: rectangle is ignored */
+	if(w == input)
+		wborder(w, Selborder);
+	else
+		wborder(w, Unselborder);
+	if(w->mouseopen)
+		return;
+	draw(w->i, insetrect(w->i->r, Borderwidth), w->Frame.cols[BACK], nil, w->i->r.min);
+	w->Frame.ticked = 0;
+	if(w->Frame.p0 > 0)
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, 0), 0, w->Frame.p0, 0);
+	if(w->Frame.p1 < w->Frame.nchars)
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p1), w->Frame.p1, w->Frame.nchars, 0);
+	frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p0), w->Frame.p0, w->Frame.p1, 1);
+	w->lastsr = ZR;
+	wscrdraw(w);
+}
+
+int
+wclose(Window *w)
+{
+	int i;
+
+	i = decref(&w->Ref);
+	if(i > 0)
+		return 0;
+	if(i < 0)
+		error("negative ref count");
+	if(!w->deleted)
+		wclosewin(w);
+	wsendctlmesg(w, Exited, ZR, nil);
+	return 1;
+}
+
+
+void
+winctl(void *arg)
+{
+	Rune *rp, *bp, *tp, *up, *kbdr;
+	uint qh;
+	int nr, nb, c, wid, i, npart, initial, lastb;
+	char *s, *t, part[3];
+	Window *w;
+	Mousestate *mp, m;
+	enum { WKey, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, NWALT };
+	Alt alts[NWALT+1];
+	Mousereadmesg mrm;
+	Conswritemesg cwm;
+	Consreadmesg crm;
+	Consreadmesg cwrm;
+	Stringpair pair;
+	Wctlmesg wcm;
+	char buf[4*12+1];
+
+	w = arg;
+	snprint(buf, sizeof buf, "winctl-id%d", w->id);
+	threadsetname(buf);
+
+	mrm.cm = chancreate(sizeof(Mouse), 0);
+	cwm.cw = chancreate(sizeof(Stringpair), 0);
+	crm.c1 = chancreate(sizeof(Stringpair), 0);
+	crm.c2 = chancreate(sizeof(Stringpair), 0);
+	cwrm.c1 = chancreate(sizeof(Stringpair), 0);
+	cwrm.c2 = chancreate(sizeof(Stringpair), 0);
+
+
+	alts[WKey].c = w->ck;
+	alts[WKey].v = &kbdr;
+	alts[WKey].op = CHANRCV;
+	alts[WMouse].c = w->mc.c;
+	alts[WMouse].v = (Mouse *)&w->mc;
+	alts[WMouse].op = CHANRCV;
+	alts[WMouseread].c = w->mouseread;
+	alts[WMouseread].v = &mrm;
+	alts[WMouseread].op = CHANSND;
+	alts[WCtl].c = w->cctl;
+	alts[WCtl].v = &wcm;
+	alts[WCtl].op = CHANRCV;
+	alts[WCwrite].c = w->conswrite;
+	alts[WCwrite].v = &cwm;
+	alts[WCwrite].op = CHANSND;
+	alts[WCread].c = w->consread;
+	alts[WCread].v = &crm;
+	alts[WCread].op = CHANSND;
+	alts[WWread].c = w->wctlread;
+	alts[WWread].v = &cwrm;
+	alts[WWread].op = CHANSND;
+	alts[NWALT].op = CHANEND;
+
+	npart = 0;
+	lastb = -1;
+	for(;;){
+		if(w->mouseopen && w->mouse.counter != w->mouse.lastcounter)
+			alts[WMouseread].op = CHANSND;
+		else
+			alts[WMouseread].op = CHANNOP;
+		if(!w->scrolling && !w->mouseopen && w->qh>w->org+w->Frame.nchars)
+			alts[WCwrite].op = CHANNOP;
+		else
+			alts[WCwrite].op = CHANSND;
+		if(w->deleted || !w->wctlready)
+			alts[WWread].op = CHANNOP;
+		else
+			alts[WWread].op = CHANSND;
+		/* this code depends on NL and EOT fitting in a single byte */
+		/* kind of expensive for each loop; worth precomputing? */
+		if(w->holding)
+			alts[WCread].op = CHANNOP;
+		else if(npart || (w->rawing && w->nraw>0))
+			alts[WCread].op = CHANSND;
+		else{
+			alts[WCread].op = CHANNOP;
+			for(i=w->qh; i<w->nr; i++){
+				c = w->run[i];
+				if(c=='\n' || c=='\004'){
+					alts[WCread].op = CHANSND;
+					break;
+				}
+			}
+		}
+		switch(alt(alts)){
+		case WKey:
+			for(i=0; kbdr[i]!=L'\0'; i++)
+				wkeyctl(w, kbdr[i]);
+//			wkeyctl(w, r);
+///			while(nbrecv(w->ck, &r))
+//				wkeyctl(w, r);
+			break;
+		case WMouse:
+			if(w->mouseopen) {
+				w->mouse.counter++;
+
+				/* queue click events */
+				if(!w->mouse.qfull && lastb != w->mc.buttons) {	/* add to ring */
+					mp = &w->mouse.queue[w->mouse.wi];
+					if(++w->mouse.wi == nelem(w->mouse.queue))
+						w->mouse.wi = 0;
+					if(w->mouse.wi == w->mouse.ri)
+						w->mouse.qfull = TRUE;
+					*(Mouse *)mp = *(Mouse *)&w->mc;
+					mp->counter = w->mouse.counter;
+					lastb = w->mc.buttons;
+				}
+			} else
+				wmousectl(w);
+			break;
+		case WMouseread:
+			/* send a queued event or, if the queue is empty, the current state */
+			/* if the queue has filled, we discard all the events it contained. */
+			/* the intent is to discard frantic clicking by the user during long latencies. */
+			w->mouse.qfull = FALSE;
+			if(w->mouse.wi != w->mouse.ri) {
+				m = w->mouse.queue[w->mouse.ri];
+				if(++w->mouse.ri == nelem(w->mouse.queue))
+					w->mouse.ri = 0;
+			} else
+				m = (Mousestate){*(Mouse *)&w->mc, w->mouse.counter};
+
+			w->mouse.lastcounter = m.counter;
+			send(mrm.cm, (Mouse *)&m);
+			continue;
+		case WCtl:
+			if(wctlmesg(w, wcm.type, wcm.r, wcm.image) == Exited){
+				chanfree(crm.c1);
+				chanfree(crm.c2);
+				chanfree(mrm.cm);
+				chanfree(cwm.cw);
+				chanfree(cwrm.c1);
+				chanfree(cwrm.c2);
+				threadexits(nil);
+			}
+			continue;
+		case WCwrite:
+			recv(cwm.cw, &pair);
+			rp = pair.s;
+			nr = pair.ns;
+			bp = rp;
+			for(i=0; i<nr; i++)
+				if(*bp++ == '\b'){
+					--bp;
+					initial = 0;
+					tp = runemalloc(nr);
+					runemove(tp, rp, i);
+					up = tp+i;
+					for(; i<nr; i++){
+						*up = *bp++;
+						if(*up == '\b')
+							if(up == tp)
+								initial++;
+							else
+								--up;
+						else
+							up++;
+					}
+					if(initial){
+						if(initial > w->qh)
+							initial = w->qh;
+						qh = w->qh-initial;
+						wdelete(w, qh, qh+initial);
+						w->qh = qh;
+					}
+					free(rp);
+					rp = tp;
+					nr = up-tp;
+					rp[nr] = 0;
+					break;
+				}
+			w->qh = winsert(w, rp, nr, w->qh)+nr;
+			if(w->scrolling || w->mouseopen)
+				wshow(w, w->qh);
+			wsetselect(w, w->q0, w->q1);
+			wscrdraw(w);
+			free(rp);
+			break;
+		case WCread:
+			recv(crm.c1, &pair);
+			t = pair.s;
+			nb = pair.ns;
+			i = npart;
+			npart = 0;
+			if(i)
+				memmove(t, part, i);
+			while(i<nb && (w->qh<w->nr || w->nraw>0)){
+				if(w->qh == w->nr){
+					wid = runetochar(t+i, &w->raw[0]);
+					w->nraw--;
+					runemove(w->raw, w->raw+1, w->nraw);
+				}else
+					wid = runetochar(t+i, &w->run[w->qh++]);
+				c = t[i];	/* knows break characters fit in a byte */
+				i += wid;
+				if(!w->rawing && (c == '\n' || c=='\004')){
+					if(c == '\004')
+						i--;
+					break;
+				}
+			}
+			if(i==nb && w->qh<w->nr && w->run[w->qh]=='\004')
+				w->qh++;
+			if(i > nb){
+				npart = i-nb;
+				memmove(part, t+nb, npart);
+				i = nb;
+			}
+			pair.s = t;
+			pair.ns = i;
+			send(crm.c2, &pair);
+			continue;
+		case WWread:
+			w->wctlready = 0;
+			recv(cwrm.c1, &pair);
+			if(w->deleted || w->i==nil)
+				pair.ns = sprint(pair.s, "");
+			else{
+				s = "visible";
+				for(i=0; i<nhidden; i++)
+					if(hidden[i] == w){
+						s = "hidden";
+						break;
+					}
+				t = "notcurrent";
+				if(w == input)
+					t = "current";
+				pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %s %s ",
+					w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+			}
+			send(cwrm.c2, &pair);
+			continue;
+		}
+		if(!w->deleted)
+			flushimage(display, 1);
+	}
+}
+
+void
+waddraw(Window *w, Rune *r, int nr)
+{
+	w->raw = runerealloc(w->raw, w->nraw+nr);
+	runemove(w->raw+w->nraw, r, nr);
+	w->nraw += nr;
+}
+
+/*
+ * Need to do this in a separate proc because if process we're interrupting
+ * is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
+ */
+void
+interruptproc(void *v)
+{
+	int *notefd;
+
+	notefd = v;
+	write(*notefd, "interrupt", 9);
+	free(notefd);
+}
+
+int
+windfilewidth(Window *w, uint q0, int oneelement)
+{
+	uint q;
+	Rune r;
+
+	q = q0;
+	while(q > 0){
+		r = w->run[q-1];
+		if(r<=' ')
+			break;
+		if(oneelement && r=='/')
+			break;
+		--q;
+	}
+	return q0-q;
+}
+
+void
+showcandidates(Window *w, Completion *c)
+{
+	int i;
+	Fmt f;
+	Rune *rp;
+	uint nr, qline, q0;
+	char *s;
+
+	runefmtstrinit(&f);
+	if (c->nmatch == 0)
+		s = "[no matches in ";
+	else
+		s = "[";
+	if(c->nfile > 32)
+		fmtprint(&f, "%s%d files]\n", s, c->nfile);
+	else{
+		fmtprint(&f, "%s", s);
+		for(i=0; i<c->nfile; i++){
+			if(i > 0)
+				fmtprint(&f, " ");
+			fmtprint(&f, "%s", c->filename[i]);
+		}
+		fmtprint(&f, "]\n");
+	}
+	/* place text at beginning of line before host point */
+	qline = w->qh;
+	while(qline>0 && w->run[qline-1] != '\n')
+		qline--;
+
+	rp = runefmtstrflush(&f);
+	nr = runestrlen(rp);
+
+	q0 = w->q0;
+	q0 += winsert(w, rp, runestrlen(rp), qline) - qline;
+	free(rp);
+	wsetselect(w, q0+nr, q0+nr);
+}
+
+Rune*
+namecomplete(Window *w)
+{
+	int nstr, npath;
+	Rune *rp, *path, *str;
+	Completion *c;
+	char *s, *dir, *root;
+
+	/* control-f: filename completion; works back to white space or / */
+	if(w->q0<w->nr && w->run[w->q0]>' ')	/* must be at end of word */
+		return nil;
+	nstr = windfilewidth(w, w->q0, TRUE);
+	str = runemalloc(nstr);
+	runemove(str, w->run+(w->q0-nstr), nstr);
+	npath = windfilewidth(w, w->q0-nstr, FALSE);
+	path = runemalloc(npath);
+	runemove(path, w->run+(w->q0-nstr-npath), npath);
+	rp = nil;
+
+	/* is path rooted? if not, we need to make it relative to window path */
+	if(npath>0 && path[0]=='/'){
+		dir = malloc(UTFmax*npath+1);
+		sprint(dir, "%.*S", npath, path);
+	}else{
+		if(strcmp(w->dir, "") == 0)
+			root = ".";
+		else
+			root = w->dir;
+		dir = malloc(strlen(root)+1+UTFmax*npath+1);
+		sprint(dir, "%s/%.*S", root, npath, path);
+	}
+	dir = cleanname(dir);
+
+	s = smprint("%.*S", nstr, str);
+	c = complete(dir, s);
+	free(s);
+	if(c == nil)
+		goto Return;
+
+	if(!c->advance)
+		showcandidates(w, c);
+
+	if(c->advance)
+		rp = runesmprint("%s", c->string);
+
+  Return:
+	freecompletion(c);
+	free(dir);
+	free(path);
+	free(str);
+	return rp;
+}
+
+void
+wkeyctl(Window *w, Rune r)
+{
+	uint q0 ,q1;
+	int n, nb, nr;
+	Rune *rp;
+	int *notefd;
+
+	if(r == 0)
+		return;
+	if(w->deleted)
+		return;
+	/* navigation keys work only when mouse is not open */
+	if(!w->mouseopen)
+		switch(r){
+		case Kdown:
+			n = w->Frame.maxlines/3;
+			goto case_Down;
+		case Kscrollonedown:
+			n = mousescrollsize(w->Frame.maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Down;
+		case Kpgdown:
+			n = 2*w->Frame.maxlines/3;
+		case_Down:
+			q0 = w->org+frcharofpt(&w->Frame, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->Frame.font->height));
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kup:
+			n = w->Frame.maxlines/3;
+			goto case_Up;
+		case Kscrolloneup:
+			n = mousescrollsize(w->Frame.maxlines);
+			if(n <= 0)
+				n = 1;
+			goto case_Up;
+		case Kpgup:
+			n = 2*w->Frame.maxlines/3;
+		case_Up:
+			q0 = wbacknl(w, w->org, n);
+			wsetorigin(w, q0, TRUE);
+			return;
+		case Kleft:
+			if(w->q0 > 0){
+				q0 = w->q0-1;
+				wsetselect(w, q0, q0);
+				wshow(w, q0);
+			}
+			return;
+		case Kright:
+			if(w->q1 < w->nr){
+				q1 = w->q1+1;
+				wsetselect(w, q1, q1);
+				wshow(w, q1);
+			}
+			return;
+		case Khome:
+			wshow(w, 0);
+			return;
+		case Kend:
+			wshow(w, w->nr);
+			return;
+		case 0x01:	/* ^A: beginning of line */
+			if(w->q0==0 || w->q0==w->qh || w->run[w->q0-1]=='\n')
+				return;
+			nb = wbswidth(w, 0x15 /* ^U */);
+			wsetselect(w, w->q0-nb, w->q0-nb);
+			wshow(w, w->q0);
+			return;
+		case 0x05:	/* ^E: end of line */
+			q0 = w->q0;
+			while(q0 < w->nr && w->run[q0]!='\n')
+				q0++;
+			wsetselect(w, q0, q0);
+			wshow(w, w->q0);
+			return;
+		}
+	if(w->rawing && (w->q0==w->nr || w->mouseopen)){
+		waddraw(w, &r, 1);
+		return;
+	}
+	if(r==0x1B || (w->holding && r==0x7F)){	/* toggle hold */
+		if(w->holding)
+			--w->holding;
+		else
+			w->holding++;
+		wrepaint(w);
+		if(r == 0x1B)
+			return;
+	}
+	if(r != 0x7F){
+		wsnarf(w);
+		wcut(w);
+	}
+	switch(r){
+	case 0x7F:		/* send interrupt */
+		w->qh = w->nr;
+		wshow(w, w->qh);
+		notefd = emalloc(sizeof(int));
+		*notefd = w->notefd;
+		proccreate(interruptproc, notefd, 4096);
+		return;
+	case 0x06:	/* ^F: file name completion */
+	case Kins:		/* Insert: file name completion */
+		rp = namecomplete(w);
+		if(rp == nil)
+			return;
+		nr = runestrlen(rp);
+		q0 = w->q0;
+		q0 = winsert(w, rp, nr, q0);
+		wshow(w, q0+nr);
+		free(rp);
+		return;
+	case 0x08:	/* ^H: erase character */
+	case 0x15:	/* ^U: erase line */
+	case 0x17:	/* ^W: erase word */
+		if(w->q0==0 || w->q0==w->qh)
+			return;
+		nb = wbswidth(w, r);
+		q1 = w->q0;
+		q0 = q1-nb;
+		if(q0 < w->org){
+			q0 = w->org;
+			nb = q1-q0;
+		}
+		if(nb > 0){
+			wdelete(w, q0, q0+nb);
+			wsetselect(w, q0, q0);
+		}
+		return;
+	}
+	/* otherwise ordinary character; just insert */
+	q0 = w->q0;
+	q0 = winsert(w, &r, 1, q0);
+	wshow(w, q0+1);
+}
+
+void
+wsetcols(Window *w)
+{
+	if(w->holding)
+		if(w == input)
+			w->Frame.cols[TEXT] = w->Frame.cols[HTEXT] = holdcol;
+		else
+			w->Frame.cols[TEXT] = w->Frame.cols[HTEXT] = lightholdcol;
+	else
+		if(w == input)
+			w->Frame.cols[TEXT] = w->Frame.cols[HTEXT] = fronttext;
+		else
+			w->Frame.cols[TEXT] = w->Frame.cols[HTEXT] = backtext;
+}
+
+void
+wrepaint(Window *w)
+{
+	wsetcols(w);
+	if(!w->mouseopen)
+		frredraw(&w->Frame);
+	if(w == input){
+		wborder(w, Selborder);
+		wsetcursor(w, 0);
+	}else
+		wborder(w, Unselborder);
+}
+
+int
+wbswidth(Window *w, Rune c)
+{
+	uint q, eq, stop;
+	Rune r;
+	int skipping;
+
+	/* there is known to be at least one character to erase */
+	if(c == 0x08)	/* ^H: erase character */
+		return 1;
+	q = w->q0;
+	stop = 0;
+	if(q > w->qh)
+		stop = w->qh;
+	skipping = TRUE;
+	while(q > stop){
+		r = w->run[q-1];
+		if(r == '\n'){		/* eat at most one more character */
+			if(q == w->q0)	/* eat the newline */
+				--q;
+			break;
+		}
+		if(c == 0x17){
+			eq = isalnum(r);
+			if(eq && skipping)	/* found one; stop skipping */
+				skipping = FALSE;
+			else if(!eq && !skipping)
+				break;
+		}
+		--q;
+	}
+	return w->q0-q;
+}
+
+void
+wsnarf(Window *w)
+{
+	if(w->q1 == w->q0)
+		return;
+	nsnarf = w->q1-w->q0;
+	snarf = runerealloc(snarf, nsnarf);
+	snarfversion++;	/* maybe modified by parent */
+	runemove(snarf, w->run+w->q0, nsnarf);
+	putsnarf();
+}
+
+void
+wcut(Window *w)
+{
+	if(w->q1 == w->q0)
+		return;
+	wdelete(w, w->q0, w->q1);
+	wsetselect(w, w->q0, w->q0);
+}
+
+void
+wpaste(Window *w)
+{
+	uint q0;
+
+	if(nsnarf == 0)
+		return;
+	wcut(w);
+	q0 = w->q0;
+	if(w->rawing && q0==w->nr){
+		waddraw(w, snarf, nsnarf);
+		wsetselect(w, q0, q0);
+	}else{
+		q0 = winsert(w, snarf, nsnarf, w->q0);
+		wsetselect(w, q0, q0+nsnarf);
+	}
+}
+
+void
+wplumb(Window *w)
+{
+	Plumbmsg *m;
+	static int fd = -2;
+	char buf[32];
+	uint p0, p1;
+	Cursor *c;
+
+	if(fd == -2)
+		fd = plumbopen("send", OWRITE|OCEXEC);
+	if(fd < 0)
+		return;
+	m = emalloc(sizeof(Plumbmsg));
+	m->src = estrdup("jay");
+	m->dst = nil;
+	m->wdir = estrdup(w->dir);
+	m->type = estrdup("text");
+	p0 = w->q0;
+	p1 = w->q1;
+	if(w->q1 > w->q0)
+		m->attr = nil;
+	else{
+		while(p0>0 && w->run[p0-1]!=' ' && w->run[p0-1]!='\t' && w->run[p0-1]!='\n')
+			p0--;
+		while(p1<w->nr && w->run[p1]!=' ' && w->run[p1]!='\t' && w->run[p1]!='\n')
+			p1++;
+		sprint(buf, "click=%d", w->q0-p0);
+		m->attr = plumbunpackattr(buf);
+	}
+	if(p1-p0 > messagesize-1024){
+		plumbfree(m);
+		return;	/* too large for 9P */
+	}
+	m->data = runetobyte(w->run+p0, p1-p0, &m->ndata);
+	if(plumbsend(fd, m) < 0){
+		c = lastcursor;
+		jaysetcursor(&query, 1);
+		sleep(300);
+		jaysetcursor(c, 1);
+	}
+	plumbfree(m);
+}
+
+int
+winborder(Window *w, Point xy)
+{
+	return w->isVisible && ptinrect(xy, w->r) && !ptinrect(xy, insetrect(w->r, Selborder));
+}
+
+void
+wmousectl(Window *w)
+{
+	int but;
+
+	if(w->mc.buttons == 1)
+		but = 1;
+	else if(w->mc.buttons == 2)
+		but = 2;
+	else if(w->mc.buttons == 4)
+		but = 3;
+	else{
+		if(w->mc.buttons == 8)
+			wkeyctl(w, Kscrolloneup);
+		if(w->mc.buttons == 16)
+			wkeyctl(w, Kscrollonedown);
+		return;
+	}
+
+	incref(&w->Ref);		/* hold up window while we track */
+	if(w->deleted)
+		goto Return;
+	if(ptinrect(w->mc.xy, w->scrollr)){
+		if(but)
+			wscroll(w, but);
+		goto Return;
+	}
+	if(but == 1)
+		wselect(w);
+	/* else all is handled by main process */
+   Return:
+	wclose(w);
+}
+
+void
+wdelete(Window *w, uint q0, uint q1)
+{
+	uint n, p0, p1;
+
+	n = q1-q0;
+	if(n == 0)
+		return;
+	runemove(w->run+q0, w->run+q1, w->nr-q1);
+	w->nr -= n;
+	if(q0 < w->q0)
+		w->q0 -= min(n, w->q0-q0);
+	if(q0 < w->q1)
+		w->q1 -= min(n, w->q1-q0);
+	if(q1 < w->qh)
+		w->qh -= n;
+	else if(q0 < w->qh)
+		w->qh = q0;
+	if(q1 <= w->org)
+		w->org -= n;
+	else if(q0 < w->org+w->Frame.nchars){
+		p1 = q1 - w->org;
+		if(p1 > w->Frame.nchars)
+			p1 = w->Frame.nchars;
+		if(q0 < w->org){
+			w->org = q0;
+			p0 = 0;
+		}else
+			p0 = q0 - w->org;
+		frdelete(&w->Frame, p0, p1);
+		wfill(w);
+	}
+}
+
+
+static Window	*clickwin;
+static uint	clickmsec;
+static Window	*selectwin;
+static uint	selectq;
+
+/*
+ * called from frame library
+ */
+void
+framescroll(Frame *f, int dl)
+{
+	if(f != &selectwin->Frame)
+		error("frameselect not right frame");
+	wframescroll(selectwin, dl);
+}
+
+void
+wframescroll(Window *w, int dl)
+{
+	uint q0;
+
+	if(dl == 0){
+		wscrsleep(w, 100);
+		return;
+	}
+	if(dl < 0){
+		q0 = wbacknl(w, w->org, -dl);
+		if(selectq > w->org+w->Frame.p0)
+			wsetselect(w, w->org+w->Frame.p0, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->Frame.p0);
+	}else{
+		if(w->org+w->Frame.nchars == w->nr)
+			return;
+		q0 = w->org+frcharofpt(&w->Frame, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->Frame.font->height));
+		if(selectq >= w->org+w->Frame.p1)
+			wsetselect(w, w->org+w->Frame.p1, selectq);
+		else
+			wsetselect(w, selectq, w->org+w->Frame.p1);
+	}
+	wsetorigin(w, q0, TRUE);
+}
+
+void
+wselect(Window *w)
+{
+	uint q0, q1;
+	int b, x, y, first;
+
+	first = 1;
+	selectwin = w;
+	/*
+	 * Double-click immediately if it might make sense.
+	 */
+	b = w->mc.buttons;
+	q0 = w->q0;
+	q1 = w->q1;
+	selectq = w->org+frcharofpt(&w->Frame, w->mc.xy);
+	if(clickwin==w && w->mc.msec-clickmsec<500)
+	if(q0==q1 && selectq==w->q0){
+		wdoubleclick(w, &q0, &q1);
+		wsetselect(w, q0, q1);
+		flushimage(display, 1);
+		x = w->mc.xy.x;
+		y = w->mc.xy.y;
+		/* stay here until something interesting happens */
+		do
+			readmouse(&w->mc);
+		while(w->mc.buttons==b && abs(w->mc.xy.x-x)<3 && abs(w->mc.xy.y-y)<3);
+		w->mc.xy.x = x;	/* in case we're calling frselect */
+		w->mc.xy.y = y;
+		q0 = w->q0;	/* may have changed */
+		q1 = w->q1;
+		selectq = q0;
+	}
+	if(w->mc.buttons == b){
+		w->Frame.scroll = framescroll;
+		frselect(&w->Frame, &w->mc);
+		/* horrible botch: while asleep, may have lost selection altogether */
+		if(selectq > w->nr)
+			selectq = w->org + w->Frame.p0;
+		w->Frame.scroll = nil;
+		if(selectq < w->org)
+			q0 = selectq;
+		else
+			q0 = w->org + w->Frame.p0;
+		if(selectq > w->org+w->Frame.nchars)
+			q1 = selectq;
+		else
+			q1 = w->org+w->Frame.p1;
+	}
+	if(q0 == q1){
+		if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500){
+			wdoubleclick(w, &q0, &q1);
+			clickwin = nil;
+		}else{
+			clickwin = w;
+			clickmsec = w->mc.msec;
+		}
+	}else
+		clickwin = nil;
+	wsetselect(w, q0, q1);
+	flushimage(display, 1);
+	while(w->mc.buttons){
+		w->mc.msec = 0;
+		b = w->mc.buttons;
+		if(b & 6){
+			if(b & 2){
+				wsnarf(w);
+				wcut(w);
+			}else{
+				if(first){
+					first = 0;
+					getsnarf();
+				}
+				wpaste(w);
+			}
+		}
+		wscrdraw(w);
+		flushimage(display, 1);
+		while(w->mc.buttons == b)
+			readmouse(&w->mc);
+		clickwin = nil;
+	}
+}
+
+void
+wsendctlmesg(Window *w, int type, Rectangle r, Image *image)
+{
+	Wctlmesg wcm;
+
+	wcm.type = type;
+	wcm.r = r;
+	wcm.image = image;
+	send(w->cctl, &wcm);
+}
+
+int
+wctlmesg(Window *w, int m, Rectangle r, Image *i)
+{
+	char buf[64];
+
+	switch(m){
+	default:
+		error("unknown control message");
+		break;
+	case Wakeup:
+		break;
+	case Moved:
+	case Reshaped:
+		if(w->deleted){
+			freeimage(i);
+			break;
+		}
+		w->screenr = r;
+		strcpy(buf, w->name);
+		wresize(w, i, m==Moved);
+		r = i->r;
+		w->wctlready = 1;
+		proccreate(deletetimeoutproc, estrdup(buf), 4096);
+		if(Dx(r) > 0){
+			if(w != input)
+				wcurrent(w);
+		}else if(w == input)
+			wcurrent(nil);
+		flushimage(display, 1);
+		break;
+	case Refresh:
+		if(w->deleted || Dx(w->r)<=0 || !rectclip(&r, w->r))
+			break;
+		if(!w->mouseopen)
+			wrefresh(w, r);
+		flushimage(display, 1);
+		break;
+	case Movemouse:
+		if(sweeping || !ptinrect(r.min, w->i->r))
+			break;
+		wmovemouse(w, r.min);
+	case Rawon:
+		break;
+	case Rawoff:
+		if(w->deleted)
+			break;
+		while(w->nraw > 0){
+			wkeyctl(w, w->raw[0]);
+			--w->nraw;
+			runemove(w->raw, w->raw+1, w->nraw);
+		}
+		break;
+	case Holdon:
+	case Holdoff:
+		if(w->deleted)
+			break;
+		wrepaint(w);
+		flushimage(display, 1);
+		break;
+	case Deleted:
+		if(w->deleted)
+			break;
+		write(w->notefd, "hangup", 6);
+		proccreate(deletetimeoutproc, estrdup(w->name), 4096);
+		wclosewin(w);
+		break;
+	case Exited:
+		frclear(&w->Frame, TRUE);
+		close(w->notefd);
+		chanfree(w->mc.c);
+		chanfree(w->ck);
+		chanfree(w->cctl);
+		chanfree(w->conswrite);
+		chanfree(w->consread);
+		chanfree(w->mouseread);
+		chanfree(w->wctlread);
+		free(w->raw);
+		free(w->run);
+		free(w->dir);
+		free(w->label);
+		free(w);
+		break;
+	}
+	return m;
+}
+
+/*
+ * Convert back to physical coordinates
+ */
+void
+wmovemouse(Window *w, Point p)
+{
+	p.x += w->r.min.x-w->t->r.min.x;
+	p.y += w->r.min.y-w->t->r.min.y;
+	moveto(mousectl, p);
+}
+
+void
+wborder(Window *w, int type)
+{
+	Image *col;
+
+	if(w->i == nil)
+		return;
+	if(w->holding){
+		if(type == Selborder)
+			col = holdcol;
+		else
+			col = paleholdcol;
+	}else{
+		if(type == Selborder)
+			col = titlecol;
+		else
+			col = lighttitlecol;
+	}
+
+	border(w->i, w->i->r, Selborder, col, ZP);
+	if(w->t != nil){
+		//border(w->t, w->t->r,Selborder,col, ZP);
+	}
+}
+
+Window*
+wpointto(Point pt)
+{
+	int i;
+	Window *v, *w;
+
+	w = nil;
+	for(i=0; i<nwindow; i++){
+		v = window[i];
+		if(v->isVisible)
+		if(ptinrect(pt, v->r))
+		if(!v->deleted)
+		if(w==nil || v->topped>w->topped)
+			w = v;
+	}
+	return w;
+}
+
+void
+wcurrentnext(){
+	Window *w;
+	for(int i=0; i<nwindow; i++){
+		w = window[i];
+		if(w->isVisible){
+			wcurrent(w);
+			wtopme(w);
+			return;
+		}
+	}
+}
+
+void
+wcurrent(Window *w)
+{
+	Window *oi;
+
+	if(w !=nil && !w->isVisible){
+		wcurrentnext();
+		return;
+	}
+	if(wkeyboard!=nil && w==wkeyboard)
+		return;
+	oi = input;
+	input = w;
+	if(oi!=w && oi!=nil)
+		wrepaint(oi);
+	if(w !=nil){
+		wrepaint(w);
+		wsetcursor(w, 0);
+	}
+	if(w != oi){
+		if(oi){
+			oi->wctlready = 1;
+			wsendctlmesg(oi, Wakeup, ZR, nil);
+		}
+		if(w){
+			w->wctlready = 1;
+			wsendctlmesg(w, Wakeup, ZR, nil);
+		}
+	}
+}
+
+void
+wsetcursor(Window *w, int force)
+{
+	Cursor *p;
+
+	if(w==nil || /*w!=input || */ w->i==nil || Dx(w->r)<=0)
+		p = nil;
+	else if(wpointto(mouse->xy) == w){
+		p = w->cursorp;
+		if(p==nil && w->holding)
+			p = &whitearrow;
+	}else
+		p = nil;
+	if(!menuing)
+		jaysetcursor(p, force && !menuing);
+}
+
+void
+jaysetcursor(Cursor *p, int force)
+{
+	if(!force && p==lastcursor)
+		return;
+	if (p==nil)
+		p = &defcursor;
+	setcursor(mousectl, p);
+	lastcursor = p;
+}
+
+Window*
+wtop(Point pt)
+{
+	Window *w;
+
+	w = wpointto(pt);
+	if(w){
+		if(w->topped == topped)
+			return nil;
+		topwindow(w->i);
+		if(w->t != nil)
+			topwindow(w->t);
+		wcurrent(w);
+		flushimage(display, 1);
+		w->topped = ++topped;
+	}
+	return w;
+}
+
+void
+wtopme(Window *w)
+{
+	if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
+		topwindow(w->i);
+		if(w->t != nil)
+			topwindow(w->t);
+		flushimage(display, 1);
+		w->topped = ++ topped;
+	}
+}
+
+void
+wbottomme(Window *w)
+{
+	if(w!=nil && w->i!=nil && !w->deleted){
+		bottomwindow(w->i);
+		if(w->t != nil)
+			bottomwindow(w->t);
+		flushimage(display, 1);
+		w->topped = - ++topped;
+	}
+}
+
+Window*
+wlookid(int id)
+{
+	int i;
+
+	for(i=0; i<nwindow; i++)
+		if(window[i]->id == id)
+			return window[i];
+	return nil;
+}
+
+void
+wclosewin(Window *w)
+{
+	Rectangle r;
+	int i;
+
+	w->deleted = TRUE;
+	if(w == input){
+		input = nil;
+		wsetcursor(w, 0);
+	}
+	if(w == wkeyboard)
+		wkeyboard = nil;
+	for(i=0; i<nhidden; i++)
+		if(hidden[i] == w){
+			--nhidden;
+			memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
+			hidden[nhidden] = nil;
+			break;
+		}
+	for(i=0; i<nwindow; i++)
+		if(window[i] == w){
+			--nwindow;
+			memmove(window+i, window+i+1, (nwindow-i)*sizeof(Window*));
+			w->deleted = TRUE;
+			r = w->i->r;
+			/* move it off-screen to hide it, in case client is slow in letting it go */
+			MOVEIT originwindow(w->i, r.min, view->r.max);
+			freeimage(w->i);
+			if (w->t != nil){
+				r = w->t->r;
+				/* move it off-screen to hide it, in case client is slow in letting it go */
+				MOVEIT originwindow(w->t, r.min, view->r.max);
+				freeimage(w->t);
+				w->t=nil;
+			}
+			w->i = nil;
+			return;
+		}
+	error("unknown window in closewin");
+}
+
+void
+wsetpid(Window *w, int pid, int dolabel)
+{
+	char buf[128];
+	int fd;
+
+	w->pid = pid;
+	if(dolabel){
+		sprint(buf, "rc %d", pid);
+		free(w->label);
+		w->label = estrdup(buf);
+		wsettitle(w);
+	}
+	sprint(buf, "/proc/%d/notepg", pid);
+	fd = open(buf, OWRITE|OCEXEC);
+	if(w->notefd > 0)
+		close(w->notefd);
+	w->notefd = fd;
+}
+
+void
+winshell(void *args)
+{
+	Window *w;
+	Channel *pidc;
+	void **arg;
+	char *cmd, *dir;
+	char **argv;
+
+	arg = args;
+	w = arg[0];
+	pidc = arg[1];
+	cmd = arg[2];
+	argv = arg[3];
+	dir = arg[4];
+	rfork(RFNAMEG|RFFDG|RFENVG);
+	if(filsysmount(filsys, w->id) < 0){
+		fprint(2, "mount failed: %r\n");
+		sendul(pidc, 0);
+		threadexits("mount failed");
+	}
+	close(0);
+	if(open("/dev/cons", OREAD) < 0){
+		fprint(2, "can't open /dev/cons: %r\n");
+		sendul(pidc, 0);
+		threadexits("/dev/cons");
+	}
+	close(1);
+	if(open("/dev/cons", OWRITE) < 0){
+		fprint(2, "can't open /dev/cons: %r\n");
+		sendul(pidc, 0);
+		threadexits("open");	/* BUG? was terminate() */
+	}
+	if(wclose(w) == 0){	/* remove extra ref hanging from creation */
+		notify(nil);
+		dup(1, 2);
+		if(dir)
+			chdir(dir);
+		procexec(pidc, cmd, argv);
+		_exits("exec failed");
+	}
+}
+
+static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
+static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
+static Rune left2[] =  { L'\n', 0 };
+static Rune left3[] =  { L'\'', L'"', L'`', 0 };
+
+Rune *left[] = {
+	left1,
+	left2,
+	left3,
+	nil
+};
+Rune *right[] = {
+	right1,
+	left2,
+	left3,
+	nil
+};
+
+void
+wdoubleclick(Window *w, uint *q0, uint *q1)
+{
+	int c, i;
+	Rune *r, *l, *p;
+	uint q;
+
+	for(i=0; left[i]!=nil; i++){
+		q = *q0;
+		l = left[i];
+		r = right[i];
+		/* try matching character to left, looking right */
+		if(q == 0)
+			c = '\n';
+		else
+			c = w->run[q-1];
+		p = strrune(l, c);
+		if(p != nil){
+			if(wclickmatch(w, c, r[p-l], 1, &q))
+				*q1 = q-(c!='\n');
+			return;
+		}
+		/* try matching character to right, looking left */
+		if(q == w->nr)
+			c = '\n';
+		else
+			c = w->run[q];
+		p = strrune(r, c);
+		if(p != nil){
+			if(wclickmatch(w, c, l[p-r], -1, &q)){
+				*q1 = *q0+(*q0<w->nr && c=='\n');
+				*q0 = q;
+				if(c!='\n' || q!=0 || w->run[0]=='\n')
+					(*q0)++;
+			}
+			return;
+		}
+	}
+	/* try filling out word to right */
+	while(*q1<w->nr && isalnum(w->run[*q1]))
+		(*q1)++;
+	/* try filling out word to left */
+	while(*q0>0 && isalnum(w->run[*q0-1]))
+		(*q0)--;
+}
+
+int
+wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
+{
+	Rune c;
+	int nest;
+
+	nest = 1;
+	for(;;){
+		if(dir > 0){
+			if(*q == w->nr)
+				break;
+			c = w->run[*q];
+			(*q)++;
+		}else{
+			if(*q == 0)
+				break;
+			(*q)--;
+			c = w->run[*q];
+		}
+		if(c == cr){
+			if(--nest==0)
+				return 1;
+		}else if(c == cl)
+			nest++;
+	}
+	return cl=='\n' && nest==1;
+}
+
+
+uint
+wbacknl(Window *w, uint p, uint n)
+{
+	int i, j;
+
+	/* look for start of this line if n==0 */
+	if(n==0 && p>0 && w->run[p-1]!='\n')
+		n = 1;
+	i = n;
+	while(i-->0 && p>0){
+		--p;	/* it's at a newline now; back over it */
+		if(p == 0)
+			break;
+		/* at 128 chars, call it a line anyway */
+		for(j=128; --j>0 && p>0; p--)
+			if(w->run[p-1]=='\n')
+				break;
+	}
+	return p;
+}
+
+void
+wshow(Window *w, uint q0)
+{
+	int qe;
+	int nl;
+	uint q;
+
+	qe = w->org+w->Frame.nchars;
+	if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+		wscrdraw(w);
+	else{
+		nl = 4*w->Frame.maxlines/5;
+		q = wbacknl(w, q0, nl);
+		/* avoid going backwards if trying to go forwards - long lines! */
+		if(!(q0>w->org && q<w->org))
+			wsetorigin(w, q, TRUE);
+		while(q0 > w->org+w->Frame.nchars)
+			wsetorigin(w, w->org+1, FALSE);
+	}
+}
+
+void
+wsetorigin(Window *w, uint org, int exact)
+{
+	int i, a, fixup;
+	Rune *r;
+	uint n;
+
+	if(org>0 && !exact){
+		/* org is an estimate of the char posn; find a newline */
+		/* don't try harder than 256 chars */
+		for(i=0; i<256 && org<w->nr; i++){
+			if(w->run[org] == '\n'){
+				org++;
+				break;
+			}
+			org++;
+		}
+	}
+	a = org-w->org;
+	fixup = 0;
+	if(a>=0 && a<w->Frame.nchars){
+		frdelete(&w->Frame, 0, a);
+		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
+	}else if(a<0 && -a<w->Frame.nchars){
+		n = w->org - org;
+		r = runemalloc(n);
+		runemove(r, w->run+org, n);
+		frinsert(&w->Frame, r, r+n, 0);
+		free(r);
+	}else
+		frdelete(&w->Frame, 0, w->Frame.nchars);
+	w->org = org;
+	wfill(w);
+	wscrdraw(w);
+	wsetselect(w, w->q0, w->q1);
+	if(fixup && w->Frame.p1 > w->Frame.p0)
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p1-1), w->Frame.p1-1, w->Frame.p1, 1);
+}
+
+void
+wsetselect(Window *w, uint q0, uint q1)
+{
+	int p0, p1;
+
+	/* w->Frame.p0 and w->Frame.p1 are always right; w->q0 and w->q1 may be off */
+	w->q0 = q0;
+	w->q1 = q1;
+	/* compute desired p0,p1 from q0,q1 */
+	p0 = q0-w->org;
+	p1 = q1-w->org;
+	if(p0 < 0)
+		p0 = 0;
+	if(p1 < 0)
+		p1 = 0;
+	if(p0 > w->Frame.nchars)
+		p0 = w->Frame.nchars;
+	if(p1 > w->Frame.nchars)
+		p1 = w->Frame.nchars;
+	if(p0==w->Frame.p0 && p1==w->Frame.p1)
+		return;
+	/* screen disagrees with desired selection */
+	if(w->Frame.p1<=p0 || p1<=w->Frame.p0 || p0==p1 || w->Frame.p1==w->Frame.p0){
+		/* no overlap or too easy to bother trying */
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p0), w->Frame.p0, w->Frame.p1, 0);
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, p0), p0, p1, 1);
+		goto Return;
+	}
+	/* overlap; avoid unnecessary painting */
+	if(p0 < w->Frame.p0){
+		/* extend selection backwards */
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, p0), p0, w->Frame.p0, 1);
+	}else if(p0 > w->Frame.p0){
+		/* trim first part of selection */
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p0), w->Frame.p0, p0, 0);
+	}
+	if(p1 > w->Frame.p1){
+		/* extend selection forwards */
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, w->Frame.p1), w->Frame.p1, p1, 1);
+	}else if(p1 < w->Frame.p1){
+		/* trim last part of selection */
+		frdrawsel(&w->Frame, frptofchar(&w->Frame, p1), p1, w->Frame.p1, 0);
+	}
+
+    Return:
+	w->Frame.p0 = p0;
+	w->Frame.p1 = p1;
+}
+
+uint
+winsert(Window *w, Rune *r, int n, uint q0)
+{
+	uint m;
+
+	if(n == 0)
+		return q0;
+	if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
+		m = min(HiWater-LoWater, min(w->org, w->qh));
+		w->org -= m;
+		w->qh -= m;
+		if(w->q0 > m)
+			w->q0 -= m;
+		else
+			w->q0 = 0;
+		if(w->q1 > m)
+			w->q1 -= m;
+		else
+			w->q1 = 0;
+		w->nr -= m;
+		runemove(w->run, w->run+m, w->nr);
+		q0 -= m;
+	}
+	if(w->nr+n > w->maxr){
+		/*
+		 * Minimize realloc breakage:
+		 *	Allocate at least MinWater
+		 * 	Double allocation size each time
+		 *	But don't go much above HiWater
+		 */
+		m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
+		if(m > HiWater)
+			m = max(HiWater+MinWater, w->nr+n);
+		if(m > w->maxr){
+			w->run = runerealloc(w->run, m);
+			w->maxr = m;
+		}
+	}
+	runemove(w->run+q0+n, w->run+q0, w->nr-q0);
+	runemove(w->run+q0, r, n);
+	w->nr += n;
+	/* if output touches, advance selection, not qh; works best for keyboard and output */
+	if(q0 <= w->q1)
+		w->q1 += n;
+	if(q0 <= w->q0)
+		w->q0 += n;
+	if(q0 < w->qh)
+		w->qh += n;
+	if(q0 < w->org)
+		w->org += n;
+	else if(q0 <= w->org+w->Frame.nchars)
+		frinsert(&w->Frame, r, r+n, q0-w->org);
+	return q0;
+}
+
+void
+wfill(Window *w)
+{
+	Rune *rp;
+	int i, n, m, nl;
+
+	if(w->Frame.lastlinefull)
+		return;
+	rp = malloc(messagesize);
+	do{
+		n = w->nr-(w->org+w->Frame.nchars);
+		if(n == 0)
+			break;
+		if(n > 2000)	/* educated guess at reasonable amount */
+			n = 2000;
+		runemove(rp, w->run+(w->org+w->Frame.nchars), n);
+		/*
+		 * it's expensive to frinsert more than we need, so
+		 * count newlines.
+		 */
+		nl = w->Frame.maxlines-w->Frame.nlines;
+		m = 0;
+		for(i=0; i<n; ){
+			if(rp[i++] == '\n'){
+				m++;
+				if(m >= nl)
+					break;
+			}
+		}
+		frinsert(&w->Frame, rp, rp+i, w->Frame.nchars);
+	}while(w->Frame.lastlinefull == FALSE);
+	free(rp);
+}
+
+char*
+wcontents(Window *w, int *ip)
+{
+	return runetobyte(w->run, w->nr, ip);
+}

+ 890 - 0
sys/src/cmd/jay/xfid.c

@@ -0,0 +1,890 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <thread.h>
+#include <cursor.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <fcall.h>
+#include <plumb.h>
+#include "dat.h"
+#include "fns.h"
+
+#define	MAXSNARF	100*1024
+
+char Einuse[] =		"file in use";
+char Edeleted[] =	"window deleted";
+char Ebadreq[] =	"bad graphics request";
+char Etooshort[] =	"buffer too small";
+char Ebadtile[] =	"unknown tile";
+char Eshort[] =		"short i/o request";
+char Elong[] = 		"snarf buffer too long";
+char Eunkid[] = 	"unknown id in attach";
+char Ebadrect[] = 	"bad rectangle in attach";
+char Ewindow[] = 	"cannot make window";
+char Enowindow[] = 	"window has no image";
+char Ebadmouse[] = 	"bad format on /dev/mouse";
+char Ebadwrect[] = 	"rectangle outside screen";
+char Ebadoffset[] = 	"window read not on scan line boundary";
+extern char Eperm[];
+
+static	Xfid	*xfidfree;
+static	Xfid	*xfid;
+static	Channel	*cxfidalloc;	/* chan(Xfid*) */
+static	Channel	*cxfidfree;	/* chan(Xfid*) */
+
+static	char	*tsnarf;
+static	int	ntsnarf;
+
+void
+xfidallocthread(void* vacio)
+{
+	Xfid *x;
+	enum { Alloc, Free, N };
+	static Alt alts[N+1];
+
+	alts[Alloc].c = cxfidalloc;
+	alts[Alloc].v = nil;
+	alts[Alloc].op = CHANRCV;
+	alts[Free].c = cxfidfree;
+	alts[Free].v = &x;
+	alts[Free].op = CHANRCV;
+	alts[N].op = CHANEND;
+	for(;;){
+		switch(alt(alts)){
+		case Alloc:
+			x = xfidfree;
+			if(x)
+				xfidfree = x->free;
+			else{
+				x = emalloc(sizeof(Xfid));
+				x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
+				x->flushc = chancreate(sizeof(int), 0);	/* notification only; no data */
+				x->flushtag = -1;
+				x->next = xfid;
+				xfid = x;
+				threadcreate(xfidctl, x, 16384);
+			}
+			if(x->Ref.ref != 0){
+				fprint(2, "%p incref %ld\n", x, x->Ref.ref);
+				error("incref");
+			}
+			if(x->flushtag != -1)
+				error("flushtag in allocate");
+			incref(&x->Ref);
+			sendp(cxfidalloc, x);
+			break;
+		case Free:
+			if(x->Ref.ref != 0){
+				fprint(2, "%p decref %ld\n", x, x->Ref.ref);
+				error("decref");
+			}
+			if(x->flushtag != -1)
+				error("flushtag in free");
+			x->free = xfidfree;
+			xfidfree = x;
+			break;
+		}
+	}
+}
+
+Channel*
+xfidinit(void)
+{
+	cxfidalloc = chancreate(sizeof(Xfid*), 0);
+	cxfidfree = chancreate(sizeof(Xfid*), 0);
+	threadcreate(xfidallocthread, nil, STACK);
+	return cxfidalloc;
+}
+
+void
+xfidctl(void *arg)
+{
+	Xfid *x;
+	void (*f)(Xfid*);
+	char buf[64];
+
+	x = arg;
+	snprint(buf, sizeof buf, "xfid.%p", x);
+	threadsetname(buf);
+	for(;;){
+		f = recvp(x->c);
+		(*f)(x);
+		if(decref(&x->Ref) == 0)
+			sendp(cxfidfree, x);
+	}
+}
+
+void
+xfidflush(Xfid *x)
+{
+	Fcall t;
+	Xfid *xf;
+
+	for(xf=xfid; xf; xf=xf->next)
+		if(xf->flushtag == x->Fcall.oldtag){
+			xf->flushtag = -1;
+			xf->flushing = TRUE;
+			incref(&xf->Ref);	/* to hold data structures up at tail of synchronization */
+			if(xf->Ref.ref == 1)
+				error("ref 1 in flush");
+			if(canqlock(&xf->active)){
+				qunlock(&xf->active);
+				sendul(xf->flushc, 0);
+			}else{
+				qlock(&xf->active);	/* wait for him to finish */
+				qunlock(&xf->active);
+			}
+			xf->flushing = FALSE;
+			if(decref(&xf->Ref) == 0)
+				sendp(cxfidfree, xf);
+			break;
+		}
+	filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidattach(Xfid *x)
+{
+	Fcall t;
+	int id, hideit, scrollit;
+	Window *w;
+	char *err, *n, *dir, errbuf[ERRMAX];
+	int pid, newlymade;
+	Rectangle r;
+	Image *i;
+
+	t.qid = x->f->qid;
+	qlock(&all);
+	w = nil;
+	err = Eunkid;
+	newlymade = FALSE;
+	hideit = 0;
+
+	if(x->Fcall.aname[0] == 'N'){	/* N 100,100, 200, 200 - old syntax */
+		n = x->Fcall.aname+1;
+		pid = strtoul(n, &n, 0);
+		if(*n == ',')
+			n++;
+		r.min.x = strtoul(n, &n, 0);
+		if(*n == ',')
+			n++;
+		r.min.y = strtoul(n, &n, 0);
+		if(*n == ',')
+			n++;
+		r.max.x = strtoul(n, &n, 0);
+		if(*n == ',')
+			n++;
+		r.max.y = strtoul(n, &n, 0);
+  Allocate:
+		if(!goodrect(r))
+			err = Ebadrect;
+		else{
+			if(hideit)
+				i = allocimage(display, r, screen->chan, 0, DWhite);
+			else
+				i = allocwindow(wscreen, r, Refbackup, DWhite);
+			if(i){
+				border(i, r, Selborder, display->black, ZP);
+				if(pid == 0)
+					pid = -1;	/* make sure we don't pop a shell! - UGH */
+				w = new(i, hideit, scrolling, pid, nil, nil, nil);
+				flushimage(display, 1);
+				newlymade = TRUE;
+			}else
+				err = Ewindow;
+		}
+	}else if(strncmp(x->Fcall.aname, "new", 3) == 0){	/* new -dx -dy - new syntax, as in wctl */
+		pid = 0;
+		if(parsewctl(nil, ZR, &r, &pid, nil, &hideit, &scrollit, &dir, x->Fcall.aname, errbuf) < 0)
+			err = errbuf;
+		else
+			goto Allocate;
+	}else{
+		id = atoi(x->Fcall.aname);
+		w = wlookid(id);
+	}
+	x->f->w = w;
+	if(w == nil){
+		qunlock(&all);
+		x->f->busy = FALSE;
+		filsysrespond(x->fs, x, &t, err);
+		return;
+	}
+	if(!newlymade)	/* counteract dec() in winshell() */
+		incref(&w->Ref);
+	qunlock(&all);
+	filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidopen(Xfid *x)
+{
+	Fcall t;
+	Window *w;
+
+	w = x->f->w;
+	if(w->deleted){
+		filsysrespond(x->fs, x, &t, Edeleted);
+		return;
+	}
+	switch(FILE(x->f->qid)){
+	case Qconsctl:
+		if(w->ctlopen){
+			filsysrespond(x->fs, x, &t, Einuse);
+			return;
+		}
+		w->ctlopen = TRUE;
+		break;
+	case Qkbdin:
+		if(w !=  wkeyboard){
+			filsysrespond(x->fs, x, &t, Eperm);
+			return;
+		}
+		break;
+	case Qmouse:
+		if(w->mouseopen){
+			filsysrespond(x->fs, x, &t, Einuse);
+			return;
+		}
+		/*
+		 * Reshaped: there's a race if the appl. opens the
+		 * window, is resized, and then opens the mouse,
+		 * but that's rare.  The alternative is to generate
+		 * a resized event every time a new program starts
+		 * up in a window that has been resized since the
+		 * dawn of time.  We choose the lesser evil.
+		 */
+		w->resized = FALSE;
+		w->mouseopen = TRUE;
+		break;
+	case Qsnarf:
+		if(x->Fcall.mode==ORDWR || x->Fcall.mode==OWRITE){
+			if(tsnarf)
+				free(tsnarf);	/* collision, but OK */
+			ntsnarf = 0;
+			tsnarf = malloc(1);
+		}
+		break;
+	case Qwctl:
+		if(x->Fcall.mode==OREAD || x->Fcall.mode==ORDWR){
+			/*
+			 * It would be much nicer to implement fan-out for wctl reads,
+			 * so multiple people can see the resizings, but jay just isn't
+			 * structured for that.  It's structured for /dev/cons, which gives
+			 * alternate data to alternate readers.  So to keep things sane for
+			 * wctl, we compromise and give an error if two people try to
+			 * open it.  Apologies.
+			 */
+			if(w->wctlopen){
+				filsysrespond(x->fs, x, &t, Einuse);
+				return;
+			}
+			w->wctlopen = TRUE;
+			w->wctlready = 1;
+			wsendctlmesg(w, Wakeup, ZR, nil);
+		}
+		break;
+	}
+	t.qid = x->f->qid;
+	t.iounit = messagesize-IOHDRSZ;
+	x->f->open = TRUE;
+	x->f->mode = x->Fcall.mode;
+	filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidclose(Xfid *x)
+{
+	Fcall t;
+	Window *w;
+	int nb, nulls;
+
+	w = x->f->w;
+	switch(FILE(x->f->qid)){
+	case Qconsctl:
+		if(w->rawing){
+			w->rawing = FALSE;
+			wsendctlmesg(w, Rawoff, ZR, nil);
+		}
+		if(w->holding){
+			w->holding = FALSE;
+			wsendctlmesg(w, Holdoff, ZR, nil);
+		}
+		w->ctlopen = FALSE;
+		break;
+	case Qcursor:
+		w->cursorp = nil;
+		wsetcursor(w, FALSE);
+		break;
+	case Qmouse:
+		w->resized = FALSE;
+		w->mouseopen = FALSE;
+		if(w->i != nil)
+			wsendctlmesg(w, Refresh, w->i->r, nil);
+		break;
+	/* odd behavior but really ok: replace snarf buffer when /dev/snarf is closed */
+	case Qsnarf:
+		if(x->f->mode==ORDWR || x->f->mode==OWRITE){
+			snarf = runerealloc(snarf, ntsnarf+1);
+			cvttorunes(tsnarf, ntsnarf, snarf, &nb, &nsnarf, &nulls);
+			free(tsnarf);
+			tsnarf = nil;
+			ntsnarf = 0;
+		}
+		break;
+	case Qwctl:
+		if(x->f->mode==OREAD || x->f->mode==ORDWR)
+			w->wctlopen = FALSE;
+		break;
+	}
+	wclose(w);
+	filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidwrite(Xfid *x)
+{
+	Fcall fc;
+	int c, cnt, qid, nb, off, nr;
+	char buf[256], *p, *conf;
+	Point pt;
+	Window *w;
+	Rune *r;
+	Conswritemesg cwm;
+	Stringpair pair;
+	enum { CWdata, CWflush, NCW };
+	Alt alts[NCW+1];
+
+	w = x->f->w;
+	if(w->deleted){
+		filsysrespond(x->fs, x, &fc, Edeleted);
+		return;
+	}
+	qid = FILE(x->f->qid);
+	cnt = x->Fcall.count;
+	off = x->Fcall.offset;
+	x->Fcall.data[cnt] = 0;
+	switch(qid){
+	case Qcons:
+		nr = x->f->nrpart;
+		if(nr > 0){
+			memmove(x->Fcall.data+nr, x->Fcall.data, cnt);	/* there's room: see malloc in filsysproc */
+			memmove(x->Fcall.data, x->f->rpart, nr);
+			cnt += nr;
+			x->f->nrpart = 0;
+		}
+		r = runemalloc(cnt);
+		cvttorunes(x->Fcall.data, cnt-UTFmax, r, &nb, &nr, nil);
+		/* approach end of buffer */
+		while(fullrune(x->Fcall.data+nb, cnt-nb)){
+			c = nb;
+			nb += chartorune(&r[nr], x->Fcall.data+c);
+			if(r[nr])
+				nr++;
+		}
+		if(nb < cnt){
+			memmove(x->f->rpart, x->Fcall.data+nb, cnt-nb);
+			x->f->nrpart = cnt-nb;
+		}
+		x->flushtag = x->Fcall.tag;
+
+		alts[CWdata].c = w->conswrite;
+		alts[CWdata].v = &cwm;
+		alts[CWdata].op = CHANRCV;
+		alts[CWflush].c = x->flushc;
+		alts[CWflush].v = nil;
+		alts[CWflush].op = CHANRCV;
+		alts[NCW].op = CHANEND;
+
+		switch(alt(alts)){
+		case CWdata:
+			break;
+		case CWflush:
+			filsyscancel(x);
+			return;
+		}
+
+		/* received data */
+		x->flushtag = -1;
+		if(x->flushing){
+			recv(x->flushc, nil);	/* wake up flushing xfid */
+			pair.s = runemalloc(1);
+			pair.ns = 0;
+			send(cwm.cw, &pair);		/* wake up window with empty data */
+			filsyscancel(x);
+			return;
+		}
+		qlock(&x->active);
+		pair.s = r;
+		pair.ns = nr;
+		send(cwm.cw, &pair);
+		fc.count = x->Fcall.count;
+		filsysrespond(x->fs, x, &fc, nil);
+		qunlock(&x->active);
+		return;
+
+	case Qconsctl:
+		if(strncmp(x->Fcall.data, "holdon", 6)==0){
+			if(w->holding++ == 0)
+				wsendctlmesg(w, Holdon, ZR, nil);
+			break;
+		}
+		if(strncmp(x->Fcall.data, "holdoff", 7)==0 && w->holding){
+			if(--w->holding == FALSE)
+				wsendctlmesg(w, Holdoff, ZR, nil);
+			break;
+		}
+		if(strncmp(x->Fcall.data, "rawon", 5)==0){
+			if(w->holding){
+				w->holding = FALSE;
+				wsendctlmesg(w, Holdoff, ZR, nil);
+			}
+			if(w->rawing++ == 0)
+				wsendctlmesg(w, Rawon, ZR, nil);
+			break;
+		}
+		if(strncmp(x->Fcall.data, "rawoff", 6)==0 && w->rawing){
+			if(--w->rawing == 0)
+				wsendctlmesg(w, Rawoff, ZR, nil);
+			break;
+		}
+		filsysrespond(x->fs, x, &fc, "unknown control message");
+		return;
+
+	case Qcursor:
+		if(cnt < 2*4+2*2*16)
+			w->cursorp = nil;
+		else{
+			w->cursor.offset.x = BGLONG(x->Fcall.data+0*4);
+			w->cursor.offset.y = BGLONG(x->Fcall.data+1*4);
+			memmove(w->cursor.clr, x->Fcall.data+2*4, 2*2*16);
+			w->cursorp = &w->cursor;
+		}
+		wsetcursor(w, !sweeping);
+		break;
+
+	case Qlabel:
+		if(off != 0){
+			filsysrespond(x->fs, x, &fc, "non-zero offset writing label");
+			return;
+		}
+		free(w->label);
+		w->label = emalloc(cnt+1);
+		memmove(w->label, x->Fcall.data, cnt);
+		w->label[cnt] = 0;
+		wsettitle(w);
+		break;
+
+	case Qjayctl:
+		conf = emalloc(cnt+1);
+		memmove(conf, x->Fcall.data, cnt);
+		*(conf + cnt) = '\0';
+		setjayconfig(conf);
+		free(conf);
+		jayredraw();
+		break;
+
+	case Qmouse:
+		if(w!=input || Dx(w->screenr)<=0)
+			break;
+		if(x->Fcall.data[0] != 'm'){
+			filsysrespond(x->fs, x, &fc, Ebadmouse);
+			return;
+		}
+		p = nil;
+		pt.x = strtoul(x->Fcall.data+1, &p, 0);
+		if(p == nil){
+			filsysrespond(x->fs, x, &fc, Eshort);
+			return;
+		}
+		pt.y = strtoul(p, nil, 0);
+		if(w==input && wpointto(mouse->xy)==w)
+			wsendctlmesg(w, Movemouse, Rpt(pt, pt), nil);
+		break;
+
+	case Qsnarf:
+		/* always append only */
+		if(ntsnarf > MAXSNARF){	/* avoid thrashing when people cut huge text */
+			filsysrespond(x->fs, x, &fc, Elong);
+			return;
+		}
+		tsnarf = erealloc(tsnarf, ntsnarf+cnt+1);	/* room for NUL */
+		memmove(tsnarf+ntsnarf, x->Fcall.data, cnt);
+		ntsnarf += cnt;
+		snarfversion++;
+		break;
+
+	case Qwdir:
+		if(cnt == 0)
+			break;
+		if(x->Fcall.data[cnt-1] == '\n'){
+			if(cnt == 1)
+				break;
+			x->Fcall.data[cnt-1] = '\0';
+		}
+		/* assume data comes in a single write */
+		/*
+		  * Problem: programs like dossrv, ftp produce illegal UTF;
+		  * we must cope by converting it first.
+		  */
+		snprint(buf, sizeof buf, "%.*s", cnt, x->Fcall.data);
+		if(buf[0] == '/'){
+			free(w->dir);
+			w->dir = estrdup(buf);
+		}else{
+			p = emalloc(strlen(w->dir) + 1 + strlen(buf) + 1);
+			sprint(p, "%s/%s", w->dir, buf);
+			free(w->dir);
+			w->dir = cleanname(p);
+		}
+		break;
+
+	case Qkbdin:
+		keyboardsend(x->Fcall.data, cnt);
+		break;
+
+	case Qwctl:
+		if(writewctl(x, buf) < 0){
+			filsysrespond(x->fs, x, &fc, buf);
+			return;
+		}
+		flushimage(display, 1);
+		break;
+
+	default:
+		fprint(2, buf, "unknown qid %d in write\n", qid);
+		sprint(buf, "unknown qid in write");
+		filsysrespond(x->fs, x, &fc, buf);
+		return;
+	}
+	fc.count = cnt;
+	filsysrespond(x->fs, x, &fc, nil);
+}
+
+int
+readwindow(Image *i, char *t, Rectangle r, int offset, int n)
+{
+	int ww, y;
+
+	offset -= 5*12;
+	ww = bytesperline(r, screen->depth);
+	r.min.y += offset/ww;
+	if(r.min.y >= r.max.y)
+		return 0;
+	y = r.min.y + n/ww;
+	if(y < r.max.y)
+		r.max.y = y;
+	if(r.max.y <= r.min.y)
+		return 0;
+	return unloadimage(i, r, (uint8_t*)t, n);
+}
+
+void
+xfidread(Xfid *x)
+{
+	Fcall fc;
+	int n, off, cnt, c;
+	char *jc;
+	uint qid;
+	char buf[128], *t;
+	char cbuf[30];
+	Window *w;
+	Mouse ms;
+	Rectangle r;
+	Image *i;
+	Channel *c1, *c2;	/* chan (tuple(char*, int)) */
+	Consreadmesg crm;
+	Mousereadmesg mrm;
+	Consreadmesg cwrm;
+	Stringpair pair;
+	enum { CRdata, CRflush, NCR };
+	enum { MRdata, MRflush, NMR };
+	enum { WCRdata, WCRflush, NWCR };
+	Alt alts[NCR+1];
+
+	w = x->f->w;
+	if(w->deleted){
+		filsysrespond(x->fs, x, &fc, Edeleted);
+		return;
+	}
+	qid = FILE(x->f->qid);
+	off = x->Fcall.offset;
+	cnt = x->Fcall.count;
+	/* for now, a zero length read of anything, even invalid things,
+	 * just returns immediately.
+	 */
+	if (cnt == 0){
+		qlock(&x->active);
+		x->flushtag = -1;
+		fc.data = nil;
+		fc.count = 0;
+		filsysrespond(x->fs, x, &fc, nil);
+		qunlock(&x->active);
+		return;
+	}
+	switch(qid){
+	case Qcons:
+		x->flushtag = x->Fcall.tag;
+
+		alts[CRdata].c = w->consread;
+		alts[CRdata].v = &crm;
+		alts[CRdata].op = CHANRCV;
+		alts[CRflush].c = x->flushc;
+		alts[CRflush].v = nil;
+		alts[CRflush].op = CHANRCV;
+		alts[NMR].op = CHANEND;
+
+		switch(alt(alts)){
+		case CRdata:
+			break;
+		case CRflush:
+			filsyscancel(x);
+			return;
+		}
+
+		/* received data */
+		x->flushtag = -1;
+		c1 = crm.c1;
+		c2 = crm.c2;
+		t = malloc(cnt+UTFmax+1);	/* room to unpack partial rune plus */
+		pair.s = t;
+		pair.ns = cnt;
+		send(c1, &pair);
+		if(x->flushing){
+			recv(x->flushc, nil);	/* wake up flushing xfid */
+			recv(c2, nil);			/* wake up window and toss data */
+			free(t);
+			filsyscancel(x);
+			return;
+		}
+		qlock(&x->active);
+		recv(c2, &pair);
+		fc.data = pair.s;
+		fc.count = pair.ns;
+		filsysrespond(x->fs, x, &fc, nil);
+		free(t);
+		qunlock(&x->active);
+		break;
+
+	case Qlabel:
+		n = strlen(w->label);
+		if(off > n)
+			off = n;
+		if(off+cnt > n)
+			cnt = n-off;
+		fc.data = w->label+off;
+		fc.count = cnt;
+		filsysrespond(x->fs, x, &fc, nil);
+		break;
+
+	case Qjayctl:
+		jc = getjayconfig();
+		n = strlen(jc);
+		if(off > n)
+			off = n;
+		if(off+cnt > n)
+			cnt = n-off;
+		fc.data = jc+off;
+		fc.count = cnt;
+		filsysrespond(x->fs, x, &fc, nil);
+		break;
+
+	case Qmouse:
+		x->flushtag = x->Fcall.tag;
+
+		alts[MRdata].c = w->mouseread;
+		alts[MRdata].v = &mrm;
+		alts[MRdata].op = CHANRCV;
+		alts[MRflush].c = x->flushc;
+		alts[MRflush].v = nil;
+		alts[MRflush].op = CHANRCV;
+		alts[NMR].op = CHANEND;
+
+		switch(alt(alts)){
+		case MRdata:
+			break;
+		case MRflush:
+			filsyscancel(x);
+			return;
+		}
+
+		/* received data */
+		x->flushtag = -1;
+		if(x->flushing){
+			recv(x->flushc, nil);		/* wake up flushing xfid */
+			recv(mrm.cm, nil);			/* wake up window and toss data */
+			filsyscancel(x);
+			return;
+		}
+		qlock(&x->active);
+		recv(mrm.cm, &ms);
+		c = 'm';
+		if(w->resized)
+			c = 'r';
+		n = sprint(buf, "%c%11d %11d %11d %11ld ", c, ms.xy.x, ms.xy.y, ms.buttons, ms.msec);
+		w->resized = 0;
+		fc.data = buf;
+		fc.count = min(n, cnt);
+		filsysrespond(x->fs, x, &fc, nil);
+		qunlock(&x->active);
+		break;
+
+	case Qcursor:
+		filsysrespond(x->fs, x, &fc, "cursor read not implemented");
+		break;
+
+	/* The algorithm for snarf and text is expensive but easy and rarely used */
+	case Qsnarf:
+		getsnarf();
+		if(nsnarf)
+			t = runetobyte(snarf, nsnarf, &n);
+		else {
+			t = nil;
+			n = 0;
+		}
+		goto Text;
+
+	case Qtext:
+		t = wcontents(w, &n);
+		goto Text;
+
+	Text:
+		if(off > n){
+			off = n;
+			cnt = 0;
+		}
+		if(off+cnt > n)
+			cnt = n-off;
+		fc.data = t+off;
+		fc.count = cnt;
+		filsysrespond(x->fs, x, &fc, nil);
+		free(t);
+		break;
+
+	case Qwdir:
+		t = estrdup(w->dir);
+		n = strlen(t);
+		goto Text;
+
+	case Qwinid:
+		n = sprint(buf, "%11d ", w->id);
+		t = estrdup(buf);
+		goto Text;
+
+
+	case Qwinname:
+		n = strlen(w->name);
+		if(n == 0){
+			filsysrespond(x->fs, x, &fc, "window has no name");
+			break;
+		}
+		t = estrdup(w->name);
+		goto Text;
+
+	case Qwindow:
+		i = w->i;
+		if(i == nil || Dx(w->screenr)<=0){
+			filsysrespond(x->fs, x, &fc, Enowindow);
+			return;
+		}
+		r = w->screenr;
+		goto caseImage;
+
+	case Qscreen:
+		i = display->image;
+		if(i == nil){
+			filsysrespond(x->fs, x, &fc, "no top-level screen");
+			break;
+		}
+		r = i->r;
+		/* fall through */
+
+	caseImage:
+		if(off < 5*12){
+			n = sprint(buf, "%11s %11d %11d %11d %11d ",
+				chantostr(cbuf, screen->chan),
+				i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y);
+			t = estrdup(buf);
+			goto Text;
+		}
+		t = malloc(cnt);
+		fc.data = t;
+		n = readwindow(i, t, r, off, cnt);	/* careful; fc.count is unsigned */
+		if(n < 0){
+			buf[0] = 0;
+			errstr(buf, sizeof buf);
+			filsysrespond(x->fs, x, &fc, buf);
+		}else{
+			fc.count = n;
+			filsysrespond(x->fs, x, &fc, nil);
+		}
+		free(t);
+		return;
+
+	case Qwctl:	/* read returns rectangle, hangs if not resized */
+		if(cnt < 4*12){
+			filsysrespond(x->fs, x, &fc, Etooshort);
+			break;
+		}
+		x->flushtag = x->Fcall.tag;
+
+		alts[WCRdata].c = w->wctlread;
+		alts[WCRdata].v = &cwrm;
+		alts[WCRdata].op = CHANRCV;
+		alts[WCRflush].c = x->flushc;
+		alts[WCRflush].v = nil;
+		alts[WCRflush].op = CHANRCV;
+		alts[NMR].op = CHANEND;
+
+		switch(alt(alts)){
+		case WCRdata:
+			break;
+		case WCRflush:
+			filsyscancel(x);
+			return;
+		}
+
+		/* received data */
+		x->flushtag = -1;
+		c1 = cwrm.c1;
+		c2 = cwrm.c2;
+		t = malloc(cnt+1);	/* be sure to have room for NUL */
+		pair.s = t;
+		pair.ns = cnt+1;
+		send(c1, &pair);
+		if(x->flushing){
+			recv(x->flushc, nil);	/* wake up flushing xfid */
+			recv(c2, nil);			/* wake up window and toss data */
+			free(t);
+			filsyscancel(x);
+			return;
+		}
+		qlock(&x->active);
+		recv(c2, &pair);
+		fc.data = pair.s;
+		if(pair.ns > cnt)
+			pair.ns = cnt;
+		fc.count = pair.ns;
+		filsysrespond(x->fs, x, &fc, nil);
+		free(t);
+		qunlock(&x->active);
+		break;
+
+	default:
+		fprint(2, "unknown qid %d in read\n", qid);
+		sprint(buf, "unknown qid in read");
+		filsysrespond(x->fs, x, &fc, buf);
+		break;
+	}
+}

+ 10 - 0
usr/harvey/lib/menu.conf

@@ -0,0 +1,10 @@
+[
+ { name = "New"
+ action = "/bin/rc" }
+ { name = "Clock" action = "/bin/clock" }
+ { name = "Sudoku" action = "/bin/sudoku"}
+ { name = "Test"
+   submenu = [ { name = "ignoreme" action = "none" }]
+ }
+
+]