summaryrefslogtreecommitdiff
path: root/sys/src/cmd/rio
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/rio
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/rio')
-rwxr-xr-xsys/src/cmd/rio/dat.h342
-rwxr-xr-xsys/src/cmd/rio/data.c180
-rwxr-xr-xsys/src/cmd/rio/fns.h35
-rwxr-xr-xsys/src/cmd/rio/fsys.c700
-rwxr-xr-xsys/src/cmd/rio/mkfile30
-rwxr-xr-xsys/src/cmd/rio/rio.c1191
-rwxr-xr-xsys/src/cmd/rio/scrl.c183
-rwxr-xr-xsys/src/cmd/rio/time.c124
-rwxr-xr-xsys/src/cmd/rio/util.c149
-rwxr-xr-xsys/src/cmd/rio/wctl.c515
-rwxr-xr-xsys/src/cmd/rio/wind.c1699
-rwxr-xr-xsys/src/cmd/rio/xfid.c846
12 files changed, 5994 insertions, 0 deletions
diff --git a/sys/src/cmd/rio/dat.h b/sys/src/cmd/rio/dat.h
new file mode 100755
index 000000000..8deb25199
--- /dev/null
+++ b/sys/src/cmd/rio/dat.h
@@ -0,0 +1,342 @@
+enum
+{
+ Qdir, /* /dev for this window */
+ Qcons,
+ Qconsctl,
+ Qcursor,
+ Qwdir,
+ Qwinid,
+ Qwinname,
+ 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;
+
+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) ((((ulong)(q).path)>>8) & 0xFFFFFF)
+#define FILE(q) (((ulong)(q).path) & 0xFF)
+
+enum /* control messages */
+{
+ Wakeup,
+ Reshaped,
+ Moved,
+ Refresh,
+ Movemouse,
+ Rawon,
+ Rawoff,
+ Holdon,
+ Holdoff,
+ Deleted,
+ Exited,
+};
+
+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;
+ ulong counter; /* serial no. of mouse event */
+};
+
+struct Mouseinfo
+{
+ Mousestate queue[16];
+ int ri; /* read index into queue */
+ int wi; /* write index */
+ ulong counter; /* serial no. of last mouse event we received */
+ ulong lastcounter; /* serial no. of last mouse event sent to client */
+ int lastb; /* last button state we received */
+ uchar qfull; /* filled the queue; no more recording until client comes back */
+};
+
+struct Window
+{
+ Ref;
+ QLock;
+ Frame;
+ Image *i;
+ 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 *r;
+ uint nraw;
+ Rune *raw;
+ uint org;
+ uint q0;
+ uint q1;
+ uint qh;
+ int id;
+ char name[32];
+ uint namecount;
+ Rectangle scrollr;
+ /*
+ * Rio 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;
+ uchar scrolling;
+ Cursor cursor;
+ Cursor *cursorp;
+ uchar holding;
+ uchar rawing;
+ uchar ctlopen;
+ uchar wctlopen;
+ uchar deleted;
+ uchar mouseopen;
+ char *label;
+ int pid;
+ char *dir;
+};
+
+int winborder(Window*, Point);
+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*);
+
+struct Dirtab
+{
+ char *name;
+ uchar 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;
+ uchar rpart[UTFmax];
+};
+
+struct Xfid
+{
+ Ref;
+ Xfid *next;
+ Xfid *free;
+ Fcall;
+ Channel *c; /* chan(void(*)(Xfid*)) */
+ Fid *f;
+ uchar *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 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[];
+char srvwctl[];
+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 */
diff --git a/sys/src/cmd/rio/data.c b/sys/src/cmd/rio/data.c
new file mode 100755
index 000000000..86e589b3e
--- /dev/null
+++ b/sys/src/cmd/rio/data.c
@@ -0,0 +1,180 @@
+#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},
+ {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
+ {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, }
+};
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, },
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, }
+};
+
+Cursor sightcursor = {
+ {-7, -7},
+ {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+ 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+ 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, },
+ {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+ 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+ 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, }
+};
+
+Cursor whitearrow = {
+ {0, 0},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
+ 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
+ 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
+ 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
+ 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
+ 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
+ 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
+};
+
+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 = {
+ {-4, -4},
+ {0xfe, 0x00, 0x82, 0x00, 0x8c, 0x00, 0x87, 0xff,
+ 0xa0, 0x01, 0xb0, 0x01, 0xd0, 0x01, 0x11, 0xff,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x1f, 0x00, },
+ {0x00, 0x00, 0x7c, 0x00, 0x70, 0x00, 0x78, 0x00,
+ 0x5f, 0xfe, 0x4f, 0xfe, 0x0f, 0xfe, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, }
+};
+
+Cursor t = {
+ {-7, -8},
+ {0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x06, 0xc0,
+ 0x1c, 0x70, 0x10, 0x10, 0x0c, 0x60, 0xfc, 0x7f,
+ 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80, 0x03, 0x80,
+ 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor tr = {
+ {-11, -4},
+ {0x00, 0x7f, 0x00, 0x41, 0x00, 0x31, 0xff, 0xe1,
+ 0x80, 0x05, 0x80, 0x0d, 0x80, 0x0b, 0xff, 0x88,
+ 0x00, 0x88, 0x0, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf8, },
+ {0x00, 0x00, 0x00, 0x3e, 0x00, 0x0e, 0x00, 0x1e,
+ 0x7f, 0xfa, 0x7f, 0xf2, 0x7f, 0xf0, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, }
+};
+
+Cursor r = {
+ {-8, -7},
+ {0x07, 0xc0, 0x04, 0x40, 0x04, 0x40, 0x04, 0x58,
+ 0x04, 0x68, 0x04, 0x6c, 0x04, 0x06, 0x04, 0x02,
+ 0x04, 0x06, 0x04, 0x6c, 0x04, 0x68, 0x04, 0x58,
+ 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x07, 0xc0, },
+ {0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
+ 0x03, 0x90, 0x03, 0x90, 0x03, 0xf8, 0x03, 0xfc,
+ 0x03, 0xf8, 0x03, 0x90, 0x03, 0x90, 0x03, 0x80,
+ 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor br = {
+ {-11, -11},
+ {0x00, 0xf8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0xff, 0x88, 0x80, 0x0b, 0x80, 0x0d, 0x80, 0x05,
+ 0xff, 0xe1, 0x00, 0x31, 0x00, 0x41, 0x00, 0x7f, },
+ {0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x0, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x7f, 0xf0, 0x7f, 0xf2, 0x7f, 0xfa,
+ 0x00, 0x1e, 0x00, 0x0e, 0x00, 0x3e, 0x00, 0x00, }
+};
+
+Cursor b = {
+ {-7, -7},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
+ 0xfc, 0x7f, 0x0c, 0x60, 0x10, 0x10, 0x1c, 0x70,
+ 0x06, 0xc0, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x03, 0x80, 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor bl = {
+ {-4, -11},
+ {0x1f, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0xff, 0xd0, 0x01, 0xb0, 0x01, 0xa0, 0x01,
+ 0x87, 0xff, 0x8c, 0x00, 0x82, 0x00, 0xfe, 0x00, },
+ {0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0f, 0xfe, 0x4f, 0xfe, 0x5f, 0xfe,
+ 0x78, 0x00, 0x70, 0x00, 0x7c, 0x00, 0x00, 0x0, }
+};
+
+Cursor l = {
+ {-7, -7},
+ {0x03, 0xe0, 0x02, 0x20, 0x02, 0x20, 0x1a, 0x20,
+ 0x16, 0x20, 0x36, 0x20, 0x60, 0x20, 0x40, 0x20,
+ 0x60, 0x20, 0x36, 0x20, 0x16, 0x20, 0x1a, 0x20,
+ 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x03, 0xe0, },
+ {0x00, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
+ 0x09, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0,
+ 0x1f, 0xc0, 0x09, 0xc0, 0x09, 0xc0, 0x01, 0xc0,
+ 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, }
+};
+
+Cursor *corners[9] = {
+ &tl, &t, &tr,
+ &l, nil, &r,
+ &bl, &b, &br,
+};
+
+void
+iconinit(void)
+{
+ background = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x777777FF);
+ red = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0xDD0000FF);
+}
diff --git a/sys/src/cmd/rio/fns.h b/sys/src/cmd/rio/fns.h
new file mode 100755
index 000000000..d6e152e35
--- /dev/null
+++ b/sys/src/cmd/rio/fns.h
@@ -0,0 +1,35 @@
+void keyboardsend(char*, int);
+int whide(Window*);
+int wunhide(int);
+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 riosetcursor(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 *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);
+
+#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))
diff --git a/sys/src/cmd/rio/fsys.c b/sys/src/cmd/rio/fsys.c
new file mode 100755
index 000000000..f8cfa3e14
--- /dev/null
+++ b/sys/src/cmd/rio/fsys.c
@@ -0,0 +1,700 @@
+#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 },
+ { "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*, uchar*, int, uint);
+
+int clockfd;
+int firstmessage = 1;
+
+char srvpipe[64];
+char srvwctl[64];
+
+static Xfid* filsysflush(Filsys*, Xfid*, Fid*);
+static Xfid* filsysversion(Filsys*, Xfid*, Fid*);
+static Xfid* filsysauth(Filsys*, Xfid*, Fid*);
+static Xfid* filsysnop(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/riowctl.%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/rio.%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;
+ uchar *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, "rio: %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) != n)
+ error("convert error in convM2S");
+ if(DEBUG)
+ fprint(2, "rio:<-%F\n", &x->Fcall);
+ if(fcall[x->type] == nil)
+ x = filsysrespond(fs, x, &t, Ebadfcall);
+ else{
+ if(x->type==Tversion || x->type==Tauth)
+ f = nil;
+ else
+ f = newfid(fs, x->fid);
+ x->f = f;
+ x = (*fcall[x->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) < 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->type+1;
+ t->fid = x->fid;
+ t->tag = x->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, "rio:->%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*)
+{
+ Fcall t;
+
+ if(!firstmessage)
+ return filsysrespond(x->fs, x, &t, "version request not first message");
+ if(x->msize < 256)
+ return filsysrespond(x->fs, x, &t, "version: message size too small");
+ messagesize = x->msize;
+ t.msize = messagesize;
+ if(strncmp(x->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*)
+{
+ Fcall t;
+
+ return filsysrespond(fs, x, &t, "rio: authentication not required");
+}
+
+static
+Xfid*
+filsysflush(Filsys*, Xfid *x, Fid*)
+{
+ sendp(x->c, xfidflush);
+ return nil;
+}
+
+static
+Xfid*
+filsysattach(Filsys *, Xfid *x, Fid *f)
+{
+ Fcall t;
+
+ if(strcmp(x->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;
+ uchar type;
+ ulong 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->fid != x->newfid){
+ /* BUG: check exists */
+ nf = newfid(fs, x->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);
+ 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->nwname > 0){
+ for(i=0; i<x->nwname; i++){
+ if((qid.type & QTDIR) == 0){
+ err = Enotdir;
+ break;
+ }
+ if(strcmp(x->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->wname[i]))
+ break;
+ /* yes: it's a directory */
+ id = atoi(x->wname[i]);
+ qlock(&all);
+ w = wlookid(id);
+ if(w == nil){
+ qunlock(&all);
+ break;
+ }
+ path = Qwsysdir;
+ type = QTDIR;
+ qunlock(&all);
+ incref(w);
+ sendp(winclosechan, f->w);
+ f->w = w;
+ dir = dirtab;
+ goto Accept;
+ }
+
+ if(snarffd>=0 && strcmp(x->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->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->nwname){
+ if(nf){
+ if(nf->w)
+ sendp(winclosechan, nf->w);
+ nf->open = FALSE;
+ nf->busy = FALSE;
+ }
+ }else if(t.nwqid == x->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->mode &= ~(OTRUNC|OCEXEC);
+ /* can't execute or remove anything */
+ if(x->mode==OEXEC || (x->mode&ORCLOSE))
+ goto Deny;
+ switch(x->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*)
+{
+ Fcall t;
+
+ return filsysrespond(fs, x, &t, Eperm);
+}
+
+static
+int
+idcmp(void *a, void *b)
+{
+ return *(int*)a - *(int*)b;
+}
+
+static
+Xfid*
+filsysread(Filsys *fs, Xfid *x, Fid *f)
+{
+ Fcall t;
+ uchar *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->offset;
+ e = x->offset+x->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->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->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*, Xfid *x, Fid*)
+{
+ 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*)
+{
+ 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*)
+{
+ 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, uchar *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);
+}
diff --git a/sys/src/cmd/rio/mkfile b/sys/src/cmd/rio/mkfile
new file mode 100755
index 000000000..5ffe21177
--- /dev/null
+++ b/sys/src/cmd/rio/mkfile
@@ -0,0 +1,30 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=rio
+OFILES=\
+ rio.$O\
+ data.$O\
+ fsys.$O\
+ scrl.$O\
+ time.$O\
+ util.$O\
+ wctl.$O\
+ wind.$O\
+ xfid.$O\
+
+HFILES=dat.h\
+ fns.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: /$objtype/lib/libdraw.a /$objtype/lib/libframe.a \
+ /$objtype/lib/libthread.a /$objtype/lib/libplumb.a /$objtype/lib/libc.a
+syms:V:
+ $CC -a $CFLAGS rio.c > syms
+ $CC -aa $CFLAGS *.c >>syms
diff --git a/sys/src/cmd/rio/rio.c b/sys/src/cmd/rio/rio.c
new file mode 100755
index 000000000..e699b0d69
--- /dev/null
+++ b/sys/src/cmd/rio/rio.c
@@ -0,0 +1,1191 @@
+#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"
+
+/*
+ * WASHINGTON (AP) - The Food and Drug Administration warned
+ * consumers Wednesday not to use ``Rio'' hair relaxer products
+ * because they may cause severe hair loss or turn hair green....
+ * The FDA urged consumers who have experienced problems with Rio
+ * to notify their local FDA office, local health department or the
+ * company at 1‑800‑543‑3002.
+ */
+
+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 rio 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] "snarf",
+ [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*, char *errorstr)
+{
+ error(errorstr);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: rio [-f font] [-i initcmd] [-k kbdcmd] [-s]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *initstr, *kbdin, *s;
+ static void *arg[1];
+ char buf[256];
+ Image *i;
+ Rectangle r;
+
+ 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, "rio: can't access %s: %r\n", fontname);
+ exits("font open");
+ }
+ putenv("font", fontname);
+
+ snarffd = open("/dev/snarf", OREAD|OCEXEC);
+
+ if(geninitdraw(nil, derror, nil, "rio", nil, Refnone) < 0){
+ fprint(2, "rio: can't open display: %r\n");
+ exits("display open");
+ }
+ iconinit();
+ view = screen;
+ viewr = view->r;
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil)
+ error("can't find mouse");
+ mouse = mousectl;
+ 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);
+
+ 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, "rio: can't create file system server: %r\n");
+ else{
+ errorshouldabort = 1; /* suicide if there's trouble after this */
+ 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");
+ }
+ 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, "rio: exec failed: %r\n");
+ exits("exec");
+}
+
+char *oknotes[] =
+{
+ "delete",
+ "hangup",
+ "kill",
+ "exit",
+ nil
+};
+
+int
+shutdown(void *, 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, "rio %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*)
+{
+ 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->screenr.min.x, w->screenr.max.x);
+ j = portion(p.y, w->screenr.min.y, w->screenr.max.y);
+ return 3*j+i;
+}
+
+void
+cornercursor(Window *w, Point p, int force)
+{
+ if(w!=nil && winborder(w, p))
+ riosetcursor(corners[whichcorner(w, p)], force);
+ else
+ wsetcursor(w, force);
+}
+
+/* thread to allow fsysproc to synchronize window closing with main proc */
+void
+winclosethread(void*)
+{
+ 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*)
+{
+ char *s;
+ Image *i;
+
+ threadsetname("deletethread");
+ for(;;){
+ s = recvp(deletechan);
+ i = namedimage(display, s);
+ 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);
+ 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*)
+{
+ 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 = &mousectl->Mouse;
+ 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(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));
+ 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(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 = mousectl->Mouse;
+ 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
+ riosetcursor(nil, 0);
+ if(moving && (mouse->buttons&7)){
+ oin = winput;
+ band = mouse->buttons & 3;
+ sweeping = 1;
+ if(band)
+ i = bandsize(winput);
+ else
+ 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);
+ wscreen = allocscreen(screen, background, 0);
+ if(wscreen == nil)
+ error("can't re-allocate screen");
+ draw(view, view->r, background, nil, ZP);
+ 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;
+ r = rectsubpt(w->i->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
+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);
+ 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')
+ waddraw(w, L"\n", 1);
+ }else{
+ winsert(w, snarf, nsnarf, w->nr);
+ if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
+ winsert(w, L"\n", 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;
+ riosetcursor(&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(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 *i, *ni;
+ Point p, op, d, dm, om;
+ Rectangle r;
+
+ i = w->i;
+ menuing = TRUE;
+ om = mouse->xy;
+ riosetcursor(&boxcursor, 1);
+ dm = subpt(mouse->xy, w->screenr.min);
+ d = subpt(i->r.max, i->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){
+ p = subpt(mouse->xy, dm);
+ if(!eqpt(p, op)){
+ drawborder(Rect(p.x, p.y, p.x+d.x, p.y+d.y), 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);
+ 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;
+ }
+ 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->screenr, p, which);
+ wmovemouse(w, p);
+ readmouse(mousectl);
+ r = whichrect(w->screenr, p, which);
+ drawborder(r, 1);
+ or = r;
+ startp = p;
+
+ while(mouse->buttons == but){
+ p = onscreen(mouse->xy);
+ r = whichrect(w->screenr, 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;
+ 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;
+ riosetcursor(&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);
+ 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);
+ 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;
+ if(hideit){
+ hidden[nhidden++] = w;
+ w->screenr = 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);
+ return w;
+}
diff --git a/sys/src/cmd/rio/scrl.c b/sys/src/cmd/rio/scrl.c
new file mode 100755
index 000000000..47480ac90
--- /dev/null
+++ b/sys/src/cmd/rio/scrl.c
@@ -0,0 +1,183 @@
+#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->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->cols[BORD], nil, ZP);
+ draw(b, r2, w->cols[BACK], nil, ZP);
+ r2.min.x = r2.max.x-1;
+ draw(b, r2, w->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 = &w->mc.Mouse;
+ 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->font->height);
+ else
+ p0 = w->org+frcharofpt(w, 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, &w->mc.Mouse);
+ first = FALSE;
+ }
+ wscrsleep(w, 100);
+ }
+ }while(w->mc.buttons & (1<<(but-1)));
+ while(w->mc.buttons)
+ readmouse(&w->mc);
+}
diff --git a/sys/src/cmd/rio/time.c b/sys/src/cmd/rio/time.c
new file mode 100755
index 000000000..7046ccbba
--- /dev/null
+++ b/sys/src/cmd/rio/time.c
@@ -0,0 +1,124 @@
+#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*)
+{
+ 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;
+}
diff --git a/sys/src/cmd/rio/util.c b/sys/src/cmd/rio/util.c
new file mode 100755
index 000000000..8a35d1d75
--- /dev/null
+++ b/sys/src/cmd/rio/util.c
@@ -0,0 +1,149 @@
+#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)
+{
+ uchar *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 = (uchar*)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, "rio: %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++)
+ 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;
+}
+
diff --git a/sys/src/cmd/rio/wctl.c b/sys/src/cmd/rio/wctl.c
new file mode 100755
index 000000000..07b882c9f
--- /dev/null
+++ b/sys/src/cmd/rio/wctl.c
@@ -0,0 +1,515 @@
+#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
+riostrtol(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 = riostrtol(s, &t);
+ if(t == s)
+ return -1;
+ s = t;
+ r.min.y = riostrtol(s, &t);
+ if(t == s)
+ return -1;
+ s = t;
+ r.max.x = riostrtol(s, &t);
+ if(t == s)
+ return -1;
+ s = t;
+ r.max.y = riostrtol(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 = riostrtol(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->count;
+ x->data[cnt] = '\0';
+ id = 0;
+
+ rect = rectsubpt(w->screenr, screen->r.min);
+ cmd = parsewctl(&arg, rect, &rect, &pid, &id, &hideit, &scrollit, &dir, x->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->screenr), rect.min.y+Dy(w->screenr));
+ rect = rectonscreen(rect);
+ /* fall through */
+ case Resize:
+ if(!goodrect(rect)){
+ strcpy(err, Ebadwr);
+ return -1;
+ }
+ if(eqrect(rect, w->screenr))
+ 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);
+ }
+}
diff --git a/sys/src/cmd/rio/wind.c b/sys/src/cmd/rio/wind.c
new file mode 100755
index 000000000..f3dc1a777
--- /dev/null
+++ b/sys/src/cmd/rio/wind.c
@@ -0,0 +1,1699 @@
+#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 *grey;
+static Image *darkgrey;
+static Cursor *lastcursor;
+static Image *titlecol;
+static Image *lighttitlecol;
+static Image *holdcol;
+static Image *lightholdcol;
+static Image *paleholdcol;
+
+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 */
+ grey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
+ darkgrey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF);
+ cols[BACK] = display->white;
+ cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
+ cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x999999FF);
+ cols[TEXT] = display->black;
+ cols[HTEXT] = display->black;
+ titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreygreen);
+ lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreygreen);
+ holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedblue);
+ lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
+ paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
+ }
+ w = emalloc(sizeof(Window));
+ w->screenr = i->r;
+ r = insetrect(i->r, Selborder+1);
+ w->i = i;
+ 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->lastsr = ZR;
+ r.min.x += Scrollwid+Scrollgap;
+ frinit(w, r, font, i, cols);
+ w->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>");
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, cols[BACK], nil, w->entire.min);
+ wborder(w, Selborder);
+ wscrdraw(w);
+ incref(w); /* 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];
+
+ 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)
+ 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, "rio: setname failed: %s\n", err);
+}
+
+void
+wresize(Window *w, Image *i, int move)
+{
+ Rectangle r, or;
+
+ or = w->i->r;
+ if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r)))
+ draw(i, i->r, w->i, nil, w->i->r.min);
+ freeimage(w->i);
+ w->i = i;
+ wsetname(w);
+ w->mc.image = i;
+ r = insetrect(i->r, Selborder+1);
+ w->scrollr = r;
+ w->scrollr.max.x = r.min.x+Scrollwid;
+ w->lastsr = ZR;
+ r.min.x += Scrollwid+Scrollgap;
+ if(move)
+ frsetrects(w, r, w->i);
+ else{
+ frclear(w, FALSE);
+ frinit(w, r, w->font, w->i, cols);
+ wsetcols(w);
+ w->maxtab = maxtab*stringwidth(w->font, "0");
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, cols[BACK], nil, w->entire.min);
+ wfill(w);
+ wsetselect(w, w->q0, w->q1);
+ wscrdraw(w);
+ }
+ wborder(w, Selborder);
+ w->topped = ++topped;
+ w->resized = TRUE;
+ w->mouse.counter++;
+}
+
+void
+wrefresh(Window *w, Rectangle)
+{
+ /* 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->cols[BACK], nil, w->i->r.min);
+ w->ticked = 0;
+ if(w->p0 > 0)
+ frdrawsel(w, frptofchar(w, 0), 0, w->p0, 0);
+ if(w->p1 < w->nchars)
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, w->nchars, 0);
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 1);
+ w->lastsr = ZR;
+ wscrdraw(w);
+}
+
+int
+wclose(Window *w)
+{
+ int i;
+
+ i = decref(w);
+ 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 = &w->mc.Mouse;
+ 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->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->r[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;
+ 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){w->mc.Mouse, w->mouse.counter};
+
+ w->mouse.lastcounter = m.counter;
+ send(mrm.cm, &m.Mouse);
+ 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->r[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->r[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->r[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->r[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->r[w->q0]>' ') /* must be at end of word */
+ return nil;
+ nstr = windfilewidth(w, w->q0, TRUE);
+ str = runemalloc(nstr);
+ runemove(str, w->r+(w->q0-nstr), nstr);
+ npath = windfilewidth(w, w->q0-nstr, FALSE);
+ path = runemalloc(npath);
+ runemove(path, w->r+(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->maxlines/3;
+ goto case_Down;
+ case Kscrollonedown:
+ n = mousescrollsize(w->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Down;
+ case Kpgdown:
+ n = 2*w->maxlines/3;
+ case_Down:
+ q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+n*w->font->height));
+ wsetorigin(w, q0, TRUE);
+ return;
+ case Kup:
+ n = w->maxlines/3;
+ goto case_Up;
+ case Kscrolloneup:
+ n = mousescrollsize(w->maxlines);
+ if(n <= 0)
+ n = 1;
+ goto case_Up;
+ case Kpgup:
+ n = 2*w->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->r[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->r[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->cols[TEXT] = w->cols[HTEXT] = holdcol;
+ else
+ w->cols[TEXT] = w->cols[HTEXT] = lightholdcol;
+ else
+ if(w == input)
+ w->cols[TEXT] = w->cols[HTEXT] = display->black;
+ else
+ w->cols[TEXT] = w->cols[HTEXT] = darkgrey;
+}
+
+void
+wrepaint(Window *w)
+{
+ wsetcols(w);
+ if(!w->mouseopen)
+ frredraw(w);
+ 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->r[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->r+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("rio");
+ 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->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
+ p0--;
+ while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[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->r+p0, p1-p0, &m->ndata);
+ if(plumbsend(fd, m) < 0){
+ c = lastcursor;
+ riosetcursor(&query, 1);
+ sleep(300);
+ riosetcursor(c, 1);
+ }
+ plumbfree(m);
+}
+
+int
+winborder(Window *w, Point xy)
+{
+ return ptinrect(xy, w->screenr) && !ptinrect(xy, insetrect(w->screenr, 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); /* 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->r+q0, w->r+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->nchars){
+ p1 = q1 - w->org;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(q0 < w->org){
+ w->org = q0;
+ p0 = 0;
+ }else
+ p0 = q0 - w->org;
+ frdelete(w, 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->p0)
+ wsetselect(w, w->org+w->p0, selectq);
+ else
+ wsetselect(w, selectq, w->org+w->p0);
+ }else{
+ if(w->org+w->nchars == w->nr)
+ return;
+ q0 = w->org+frcharofpt(w, Pt(w->Frame.r.min.x, w->Frame.r.min.y+dl*w->font->height));
+ if(selectq >= w->org+w->p1)
+ wsetselect(w, w->org+w->p1, selectq);
+ else
+ wsetselect(w, selectq, w->org+w->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, 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->scroll = framescroll;
+ frselect(w, &w->mc);
+ /* horrible botch: while asleep, may have lost selection altogether */
+ if(selectq > w->nr)
+ selectq = w->org + w->p0;
+ w->Frame.scroll = nil;
+ if(selectq < w->org)
+ q0 = selectq;
+ else
+ q0 = w->org + w->p0;
+ if(selectq > w->org+w->nchars)
+ q1 = selectq;
+ else
+ q1 = w->org+w->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);
+ 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->screenr)<=0 || !rectclip(&r, w->i->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, 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->r);
+ 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->screenr.min.x-w->i->r.min.x;
+ p.y += w->screenr.min.y-w->i->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);
+}
+
+Window*
+wpointto(Point pt)
+{
+ int i;
+ Window *v, *w;
+
+ w = nil;
+ for(i=0; i<nwindow; i++){
+ v = window[i];
+ if(ptinrect(pt, v->screenr))
+ if(!v->deleted)
+ if(w==nil || v->topped>w->topped)
+ w = v;
+ }
+ return w;
+}
+
+void
+wcurrent(Window *w)
+{
+ Window *oi;
+
+ 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->screenr)<=0)
+ p = nil;
+ else if(wpointto(mouse->xy) == w){
+ p = w->cursorp;
+ if(p==nil && w->holding)
+ p = &whitearrow;
+ }else
+ p = nil;
+ if(!menuing)
+ riosetcursor(p, force && !menuing);
+}
+
+void
+riosetcursor(Cursor *p, int force)
+{
+ if(!force && p==lastcursor)
+ return;
+ 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);
+ 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);
+ flushimage(display, 1);
+ w->topped = ++ topped;
+ }
+}
+
+void
+wbottomme(Window *w)
+{
+ if(w!=nil && w->i!=nil && !w->deleted){
+ bottomwindow(w->i);
+ 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);
+ 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);
+ }
+ 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->r[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->r[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->r[0]=='\n')
+ (*q0)++;
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ while(*q1<w->nr && isalnum(w->r[*q1]))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && isalnum(w->r[*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->r[*q];
+ (*q)++;
+ }else{
+ if(*q == 0)
+ break;
+ (*q)--;
+ c = w->r[*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->r[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->r[p-1]=='\n')
+ break;
+ }
+ return p;
+}
+
+void
+wshow(Window *w, uint q0)
+{
+ int qe;
+ int nl;
+ uint q;
+
+ qe = w->org+w->nchars;
+ if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
+ wscrdraw(w);
+ else{
+ nl = 4*w->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->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->r[org] == '\n'){
+ org++;
+ break;
+ }
+ org++;
+ }
+ }
+ a = org-w->org;
+ fixup = 0;
+ if(a>=0 && a<w->nchars){
+ frdelete(w, 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->nchars){
+ n = w->org - org;
+ r = runemalloc(n);
+ runemove(r, w->r+org, n);
+ frinsert(w, r, r+n, 0);
+ free(r);
+ }else
+ frdelete(w, 0, w->nchars);
+ w->org = org;
+ wfill(w);
+ wscrdraw(w);
+ wsetselect(w, w->q0, w->q1);
+ if(fixup && w->p1 > w->p0)
+ frdrawsel(w, frptofchar(w, w->p1-1), w->p1-1, w->p1, 1);
+}
+
+void
+wsetselect(Window *w, uint q0, uint q1)
+{
+ int p0, p1;
+
+ /* w->p0 and w->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->nchars)
+ p0 = w->nchars;
+ if(p1 > w->nchars)
+ p1 = w->nchars;
+ if(p0==w->p0 && p1==w->p1)
+ return;
+ /* screen disagrees with desired selection */
+ if(w->p1<=p0 || p1<=w->p0 || p0==p1 || w->p1==w->p0){
+ /* no overlap or too easy to bother trying */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, w->p1, 0);
+ frdrawsel(w, frptofchar(w, p0), p0, p1, 1);
+ goto Return;
+ }
+ /* overlap; avoid unnecessary painting */
+ if(p0 < w->p0){
+ /* extend selection backwards */
+ frdrawsel(w, frptofchar(w, p0), p0, w->p0, 1);
+ }else if(p0 > w->p0){
+ /* trim first part of selection */
+ frdrawsel(w, frptofchar(w, w->p0), w->p0, p0, 0);
+ }
+ if(p1 > w->p1){
+ /* extend selection forwards */
+ frdrawsel(w, frptofchar(w, w->p1), w->p1, p1, 1);
+ }else if(p1 < w->p1){
+ /* trim last part of selection */
+ frdrawsel(w, frptofchar(w, p1), p1, w->p1, 0);
+ }
+
+ Return:
+ w->p0 = p0;
+ w->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->r, w->r+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->r = runerealloc(w->r, m);
+ w->maxr = m;
+ }
+ }
+ runemove(w->r+q0+n, w->r+q0, w->nr-q0);
+ runemove(w->r+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->nchars)
+ frinsert(w, r, r+n, q0-w->org);
+ return q0;
+}
+
+void
+wfill(Window *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ if(w->lastlinefull)
+ return;
+ rp = malloc(messagesize);
+ do{
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ runemove(rp, w->r+(w->org+w->nchars), n);
+ /*
+ * it's expensive to frinsert more than we need, so
+ * count newlines.
+ */
+ nl = w->maxlines-w->nlines;
+ m = 0;
+ for(i=0; i<n; ){
+ if(rp[i++] == '\n'){
+ m++;
+ if(m >= nl)
+ break;
+ }
+ }
+ frinsert(w, rp, rp+i, w->nchars);
+ }while(w->lastlinefull == FALSE);
+ free(rp);
+}
+
+char*
+wcontents(Window *w, int *ip)
+{
+ return runetobyte(w->r, w->nr, ip);
+}
diff --git a/sys/src/cmd/rio/xfid.c b/sys/src/cmd/rio/xfid.c
new file mode 100755
index 000000000..c67f783fb
--- /dev/null
+++ b/sys/src/cmd/rio/xfid.c
@@ -0,0 +1,846 @@
+#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*)
+{
+ 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 != 0){
+ fprint(2, "%p incref %ld\n", x, x->ref);
+ error("incref");
+ }
+ if(x->flushtag != -1)
+ error("flushtag in allocate");
+ incref(x);
+ sendp(cxfidalloc, x);
+ break;
+ case Free:
+ if(x->ref != 0){
+ fprint(2, "%p decref %ld\n", x, x->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) == 0)
+ sendp(cxfidfree, x);
+ }
+}
+
+void
+xfidflush(Xfid *x)
+{
+ Fcall t;
+ Xfid *xf;
+
+ for(xf=xfid; xf; xf=xf->next)
+ if(xf->flushtag == x->oldtag){
+ xf->flushtag = -1;
+ xf->flushing = TRUE;
+ incref(xf); /* to hold data structures up at tail of synchronization */
+ if(xf->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) == 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->aname[0] == 'N'){ /* N 100,100, 200, 200 - old syntax */
+ n = x->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->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->aname, errbuf) < 0)
+ err = errbuf;
+ else
+ goto Allocate;
+ }else{
+ id = atoi(x->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);
+ 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->mode==ORDWR || x->mode==OWRITE){
+ if(tsnarf)
+ free(tsnarf); /* collision, but OK */
+ ntsnarf = 0;
+ tsnarf = malloc(1);
+ }
+ break;
+ case Qwctl:
+ if(x->mode==OREAD || x->mode==ORDWR){
+ /*
+ * It would be much nicer to implement fan-out for wctl reads,
+ * so multiple people can see the resizings, but rio 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->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;
+ 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->count;
+ off = x->offset;
+ x->data[cnt] = 0;
+ switch(qid){
+ case Qcons:
+ nr = x->f->nrpart;
+ if(nr > 0){
+ memmove(x->data+nr, x->data, cnt); /* there's room: see malloc in filsysproc */
+ memmove(x->data, x->f->rpart, nr);
+ cnt += nr;
+ x->f->nrpart = 0;
+ }
+ r = runemalloc(cnt);
+ cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
+ /* approach end of buffer */
+ while(fullrune(x->data+nb, cnt-nb)){
+ c = nb;
+ nb += chartorune(&r[nr], x->data+c);
+ if(r[nr])
+ nr++;
+ }
+ if(nb < cnt){
+ memmove(x->f->rpart, x->data+nb, cnt-nb);
+ x->f->nrpart = cnt-nb;
+ }
+ x->flushtag = x->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->count;
+ filsysrespond(x->fs, x, &fc, nil);
+ qunlock(&x->active);
+ return;
+
+ case Qconsctl:
+ if(strncmp(x->data, "holdon", 6)==0){
+ if(w->holding++ == 0)
+ wsendctlmesg(w, Holdon, ZR, nil);
+ break;
+ }
+ if(strncmp(x->data, "holdoff", 7)==0 && w->holding){
+ if(--w->holding == FALSE)
+ wsendctlmesg(w, Holdoff, ZR, nil);
+ break;
+ }
+ if(strncmp(x->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->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->data+0*4);
+ w->cursor.offset.y = BGLONG(x->data+1*4);
+ memmove(w->cursor.clr, x->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->data, cnt);
+ w->label[cnt] = 0;
+ break;
+
+ case Qmouse:
+ if(w!=input || Dx(w->screenr)<=0)
+ break;
+ if(x->data[0] != 'm'){
+ filsysrespond(x->fs, x, &fc, Ebadmouse);
+ return;
+ }
+ p = nil;
+ pt.x = strtoul(x->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->data, cnt);
+ ntsnarf += cnt;
+ snarfversion++;
+ break;
+
+ case Qwdir:
+ if(cnt == 0)
+ break;
+ if(x->data[cnt-1] == '\n'){
+ if(cnt == 1)
+ break;
+ x->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->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->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, (uchar*)t, n);
+}
+
+void
+xfidread(Xfid *x)
+{
+ Fcall fc;
+ int n, off, cnt, c;
+ 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->offset;
+ cnt = x->count;
+ switch(qid){
+ case Qcons:
+ x->flushtag = x->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 Qmouse:
+ x->flushtag = x->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->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;
+ }
+}