ref: 9339cf07c8fb51a828d2feb3414170f3cea01a75
parent: 494281e28693c5505a1a077b8c6694b503488dc2
author: qwx <qwx@sciops.net>
date: Mon Aug 14 01:43:06 EDT 2023
add a bof rio fork
--- /dev/null
+++ b/sys/src/cmd/bof/dat.h
@@ -1,0 +1,355 @@
+enum
+{
+ Qdir, /* /dev for this window */
+ Qscreen,
+ Qsnarf,
+ Qwctl,
+ Qtap,
+ Qwsys, /* directory of window directories */
+ Qwsysdir, /* window directory, child of wsys */
+
+ Qcons,
+ Qconsctl,
+ Qcursor,
+ Qwdir,
+ Qwinid,
+ Qwinname,
+ Qlabel,
+ Qkbd,
+ Qmouse,
+ Qtext,
+ Qwindow,
+ Qdot,
+
+ QMAX,
+ Qglobal = Qcons, /* anything >= must have non nil window */
+};
+
+#define STACK 8192
+#define MAXSNARF 100*1024
+
+typedef struct Consreadmesg Consreadmesg;
+typedef struct Conswritemesg Conswritemesg;
+typedef struct Kbdreadmesg Kbdreadmesg;
+typedef struct Stringpair Stringpair;
+typedef struct Dirtab Dirtab;
+typedef struct Fid Fid;
+typedef struct Filsys Filsys;
+typedef struct Mouseinfo Mouseinfo;
+typedef struct Mousereadmesg Mousereadmesg;
+typedef struct Mousestate Mousestate;
+typedef struct Ref Ref;
+typedef struct Timer Timer;
+typedef struct Wctlmesg Wctlmesg;
+typedef struct Window Window;
+typedef struct Xfid Xfid;
+typedef struct Tapmesg Tapmesg;
+
+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,
+ Topped,
+ Repaint,
+ Refresh,
+ Movemouse,
+ Rawon,
+ Rawoff,
+ Holdon,
+ Holdoff,
+ Truncate,
+ Deleted,
+ Exited,
+};
+
+struct Wctlmesg
+{
+ int type;
+ Rectangle r;
+ void *p;
+};
+
+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; /* window image, nil when deleted */
+ Mousectl mc;
+ Mouseinfo mouse;
+ Channel *ck; /* chan(char*) */
+ Channel *cctl; /* chan(Wctlmesg)[4] */
+ Channel *conswrite; /* chan(Conswritemesg) */
+ Channel *consread; /* chan(Consreadmesg) */
+ Channel *mouseread; /* chan(Mousereadmesg) */
+ Channel *wctlread; /* chan(Consreadmesg) */
+ Channel *kbdread; /* chan(Consreadmesg) */
+ Channel *complete; /* chan(Completion*) */
+ Channel *gone; /* chan(char*) */
+ 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;
+ uchar kbdopen;
+ uchar winnameread;
+ char *label;
+ char *dir;
+};
+
+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 wclose(Window*);
+uint wbacknl(Window*, uint, uint);
+void wcurrent(Window*);
+void wuncurrent(Window*);
+void wcut(Window*);
+void wpaste(Window*);
+void wplumb(Window*);
+void wlook(Window*);
+void wscrdraw(Window*);
+void wscroll(Window*, int);
+void wsend(Window*);
+void wsendctlmesg(Window*, int, Rectangle, void*);
+void wsetcursor(Window*, int);
+void wsetname(Window*);
+void wsetorigin(Window*, uint, int);
+void wsetpid(Window*, int, int);
+void wshow(Window*, uint);
+void wsnarf(Window*);
+void wscrsleep(Window*, uint);
+
+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;
+ 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*) */
+ Channel *csyncflush; /* chan(int) */
+ Fid *fids[Nhash];
+};
+
+Filsys* filsysinit(Channel*);
+int filsysmount(Filsys*, int);
+Xfid* filsysrespond(Filsys*, Xfid*, Fcall*, char*);
+void filsyscancel(Xfid*);
+
+void deletetimeoutproc(void*);
+
+struct Timer
+{
+ int dt;
+ int cancel;
+ Channel *c; /* chan(int) */
+ Timer *next;
+};
+
+Font *font;
+Mousectl *mousectl;
+Mouse *mouse;
+Display *display;
+Image *view;
+Screen *wscreen;
+Cursor boxcursor;
+Cursor crosscursor;
+Cursor sightcursor;
+Cursor whitearrow;
+Cursor query;
+Cursor *corners[9];
+Cursor skull;
+
+enum {
+ Cback,
+ Chigh,
+ Cbord,
+ Ctext,
+ Chtext,
+ Ctitle,
+ Cltitle,
+ Chold,
+ Clhold,
+ Cpalehold,
+ Cpaletext,
+ Csize,
+ Crioback,
+ NCOLS,
+};
+Image *cols[NCOLS];
+
+Window **window;
+Window *wkeyboard; /* window of simulated keyboard */
+int nwindow;
+int snarffd;
+int gotscreen;
+int servekbd;
+
+enum{
+ Tapon = 'b',
+ Tapoff = 'e',
+ Tapfocus = 'z',
+};
+Channel *ctltap; /* open/close */
+Channel *resptap; /* open/close err */
+Channel *fromtap; /* input from kbd tap program to window */
+Channel *totap; /* our keyboard input to tap program */
+Channel *wintap; /* tell the tapthread which Window to send to */
+
+Window *input;
+QLock all; /* BUG */
+Filsys *filsys;
+Window *hidden[100];
+int nhidden;
+int nsnarf;
+Rune* snarf;
+int scrolling;
+int maxtab;
+Channel* winclosechan;
+char *startdir;
+int sweeping;
+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 */
+int shiftdown;
+int debug;
--- /dev/null
+++ b/sys/src/cmd/bof/data.c
@@ -1,0 +1,214 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.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 skull = {
+ {-7,-7},
+ {0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7,
+ 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8,
+ 0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff,
+ 0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00,},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03,
+ 0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0,
+ 0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27,
+ 0xC3, 0xC3, 0x00, 0x00, 0x00, 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)
+{
+ int i;
+
+ Theme th[nelem(cols)] = {
+ [Cback] { "back", 0xFFFFFFFF },
+ [Cbord] { "border", 0x999999FF },
+ [Ctext] { "text", DBlack },
+ [Chtext] { "htext", DBlack },
+ [Chigh] { "high", 0xCCCCCCFF },
+ [Ctitle] { "title", DGreygreen },
+ [Cltitle] { "ltitle", DPalegreygreen },
+ [Chold] { "hold", DMedblue },
+ [Clhold] { "lhold", DGreyblue },
+ [Cpalehold] { "palehold", DPalegreyblue },
+ [Cpaletext] { "paletext", 0x666666FF },
+ [Csize] { "size", DRed },
+ [Crioback] { "rioback", 0x777777FF },
+ };
+ readtheme(th, nelem(th), nil);
+ for(i=0; i<nelem(cols); i++){
+ if((cols[i] = allocimage(display, Rect(0,0,1,1),
+ screen->chan, 1, th[i].c)) == nil)
+ sysfatal("allocimage: %r");
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/bof/fns.h
@@ -1,0 +1,36 @@
+int whide(Window*);
+int wunhide(Window*);
+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 min(int, int);
+int max(int, int);
+Rune* strrune(Rune*, Rune);
+int isalnum(Rune);
+int isspace(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);
+int inborder(Rectangle, Point);
+
+#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))
--- /dev/null
+++ b/sys/src/cmd/bof/fsys.c
@@ -1,0 +1,702 @@
+#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";
+char Enomem[] = "out of memory";
+
+int messagesize = 8192+IOHDRSZ; /* good start */
+
+Dirtab dirtab[]=
+{
+ { ".", QTDIR, Qdir, 0500|DMDIR },
+ { "screen", QTFILE, Qscreen, 0400 },
+ { "snarf", QTFILE, Qsnarf, 0600 },
+ { "wctl", QTFILE, Qwctl, 0600 },
+ { "kbdtap", QTFILE, Qtap, 0660 },
+ { "wsys", QTDIR, Qwsys, 0500|DMDIR },
+
+ { "cons", QTFILE, Qcons, 0600 },
+ { "cursor", QTFILE, Qcursor, 0600 },
+ { "consctl", QTFILE, Qconsctl, 0200 },
+ { "winid", QTFILE, Qwinid, 0400 },
+ { "winname", QTFILE, Qwinname, 0400 },
+ { "label", QTFILE, Qlabel, 0600 },
+ { "kbd", QTFILE, Qkbd, 0600 },
+ { "mouse", QTFILE, Qmouse, 0600 },
+ { "text", QTFILE, Qtext, 0600 },
+ { "wdir", QTFILE, Qwdir, 0600 },
+ { "window", QTFILE, Qwindow, 0400 },
+ { "dot", QTFILE, Qdot, 0600 },
+ { 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* 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);
+ snprint(buf, sizeof(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) == -1)
+ 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)
+{
+ Filsys *fs;
+
+ fs = emalloc(sizeof(Filsys));
+ if(cexecpipe(&fs->cfd, &fs->sfd) < 0)
+ goto Rescue;
+ fmtinstall('F', fcallfmt);
+ clockfd = open("/dev/time", OREAD|OCEXEC);
+ fs->user = getuser();
+ fs->csyncflush = chancreate(sizeof(int), 0);
+ if(fs->csyncflush == nil)
+ error("chancreate syncflush");
+ fs->cxfidalloc = cxfidalloc;
+
+ proccreate(filsysproc, fs, 10000);
+ snprint(srvpipe, sizeof(srvpipe), "/srv/rio.%s.%lud", fs->user, (ulong)getpid());
+ 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 = malloc(messagesize+UTFmax); /* UTFmax for appending partial rune in xfidwrite */
+ if(buf == nil)
+ error(Enomem);
+ 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 */
+ snprint(buf, sizeof buf, "%d", id);
+ if(mount(fs->cfd, -1, "/mnt/wsys", MREPL, buf) == -1){
+ fprint(2, "mount failed: %r\n");
+ return -1;
+ }
+ if(bind("/mnt/wsys", "/dev", MBEFORE) == -1){
+ 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)
+ error("no buffer in respond");
+ 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;
+ x->flushtag = -1;
+ 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;
+ t.version = "9P2000";
+ if(strncmp(x->version, "9P", 2) != 0)
+ t.version = "unknown";
+ 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 *fs, Xfid *x, Fid*)
+{
+ sendp(x->c, xfidflush);
+
+ /*
+ * flushes need to be replied in order. xfidflush() will
+ * awaken us when the flush has been queued.
+ */
+ recv(fs->csyncflush, nil);
+
+ 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
+int
+skipdir(char *name)
+{
+ /* don't serve these if it's provided in the environment */
+ if(snarffd>=0 && strcmp(name, "snarf")==0)
+ return 1;
+ if(gotscreen && strcmp(name, "screen")==0)
+ return 1;
+ if(!servekbd && strcmp(name, "kbd")==0)
+ return 1;
+ return 0;
+}
+
+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;
+ if(f->w != nil)
+ 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);
+ if(f->w)
+ sendp(winclosechan, f->w);
+ f->w = w;
+ dir = dirtab;
+ goto Accept;
+ }
+ if(skipdir(x->wname[i]))
+ break;
+ id = WIN(f->qid);
+ d = dirtab;
+ d++; /* skip '.' */
+ for(; d->name; d++)
+ if(strcmp(x->wname[i], d->name) == 0){
+ if(f->w == nil && d->qid >= Qglobal)
+ break;
+ 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 but Qtext, so just disregard */
+ if(FILE(f->qid) != Qtext)
+ x->mode &= ~OTRUNC;
+ x->mode &= ~OCEXEC;
+ /* can't execute or remove anything */
+ if(x->mode==OEXEC || (x->mode&ORCLOSE))
+ goto Deny;
+ switch(x->mode & ~OTRUNC){
+ 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[32];
+
+ 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, Enomem);
+ n = 0;
+ switch(FILE(f->qid)){
+ case Qdir:
+ case Qwsysdir:
+ d = dirtab;
+ d++; /* first entry is '.' */
+ for(i=0; d->name!=nil && i<e; d++){
+ if(skipdir(d->name))
+ continue;
+ if(f->w == nil && d->qid >= Qglobal)
+ continue;
+ len = dostat(fs, WIN(x->f->qid), d, b+n, x->count-n, clock);
+ if(len <= BIT16SZ)
+ break;
+ if(i >= o)
+ n += len;
+ i += len;
+ }
+ 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);
+}
--- /dev/null
+++ b/sys/src/cmd/bof/mkfile
@@ -1,0 +1,30 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=bof
+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
--- /dev/null
+++ b/sys/src/cmd/bof/rio.c
@@ -1,0 +1,1428 @@
+#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);
+void confirmexit(void);
+Image* sweep(void);
+Image* bandsize(Window*);
+Image* drag(Window*);
+void resized(void);
+Channel *exitchan; /* chan(int) */
+Channel *winclosechan; /* chan(Window*); */
+Channel *kbdchan; /* chan(char*); */
+Rectangle viewr;
+int threadrforkflag = 0; /* should be RFENVG but that hides rio from plumber */
+
+void mousethread(void*);
+void keyboardtap(void*);
+void winclosethread(void*);
+void initcmd(void*);
+Channel* initkbd(void);
+
+char *fontname;
+
+enum
+{
+ New,
+ Reshape,
+ Move,
+ Delete,
+ Hide,
+ Exit,
+};
+
+enum
+{
+ Cut,
+ Paste,
+ Snarf,
+ Plumb,
+ Look,
+ Send,
+ Scroll,
+};
+
+char *menu2str[] = {
+ [Cut] "cut",
+ [Paste] "paste",
+ [Snarf] "snarf",
+ [Plumb] "plumb",
+ [Look] "look",
+ [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 [-b] [-f font] [-i initcmd] [-k kbdcmd] [-s]\n");
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *initstr, *kbdin, *s;
+ char buf[256];
+ Image *i;
+ Rectangle r;
+
+ initstr = nil;
+ kbdin = nil;
+ maxtab = 0;
+ ARGBEGIN{
+ case 'f':
+ fontname = EARGF(usage());
+ break;
+ case 'i':
+ initstr = EARGF(usage());
+ break;
+ case 'k':
+ if(kbdin != nil)
+ usage();
+ kbdin = EARGF(usage());
+ break;
+ case 's':
+ scrolling = TRUE;
+ break;
+ case 'D':
+ debug++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(getwd(buf, sizeof buf) == nil)
+ startdir = estrdup(".");
+ else
+ startdir = estrdup(buf);
+ if(fontname == nil)
+ fontname = getenv("font");
+ s = getenv("tabstop");
+ if(s != nil)
+ maxtab = strtol(s, nil, 0);
+ if(maxtab == 0)
+ maxtab = 4;
+ free(s);
+
+ if(fontname){
+ /* 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);
+ gotscreen = access("/dev/screen", AEXIST)==0;
+
+ if(geninitdraw(nil, derror, nil, "rio", nil, Refnone) < 0){
+ fprint(2, "rio: can't open display: %r\n");
+ exits("display open");
+ }
+ iconinit();
+
+ exitchan = chancreate(sizeof(int), 0);
+ winclosechan = chancreate(sizeof(Window*), 0);
+
+ view = screen;
+ viewr = view->r;
+ mousectl = initmouse(nil, screen);
+ if(mousectl == nil)
+ error("can't find mouse");
+ mouse = mousectl;
+ kbdchan = initkbd();
+ if(kbdchan == nil)
+ error("can't find keyboard");
+ totap = chancreate(sizeof(char*), 32);
+ fromtap = chancreate(sizeof(char*), 32);
+ wintap = chancreate(sizeof(Window*), 0);
+ ctltap = chancreate(sizeof(char*), 0);
+ resptap = chancreate(sizeof(char*), 0);
+ proccreate(keyboardtap, nil, STACK);
+
+ wscreen = allocscreen(screen, cols[Crioback], 0);
+ if(wscreen == nil)
+ error("can't allocate screen");
+ draw(view, viewr, cols[Crioback], nil, ZP);
+ flushimage(display, 1);
+
+ timerinit();
+ threadcreate(mousethread, nil, STACK);
+ threadcreate(winclosethread, 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.min.y = r.max.y-Dy(r)/3;
+ i = allocwindow(wscreen, r, Refbackup, DNofill);
+ 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();
+ closedisplay(display);
+ 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|OCEXEC);
+ 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 *s, *sn;
+
+ if(snarffd < 0)
+ return;
+ sn = nil;
+ i = 0;
+ seek(snarffd, 0, 0);
+ for(;;){
+ if(i > MAXSNARF)
+ break;
+ if((s = realloc(sn, i+1024+1)) == nil)
+ break;
+ sn = s;
+ if((n = read(snarffd, sn+i, 1024)) <= 0)
+ break;
+ i += n;
+ }
+ if(i == 0)
+ return;
+ sn[i] = 0;
+ if((snarf = runerealloc(snarf, i+1)) != nil)
+ cvttorunes(sn, i, snarf, &nb, &nsnarf, &nulls);
+ free(sn);
+}
+
+void
+initcmd(void *arg)
+{
+ char *cmd;
+ char *wsys;
+ int fd;
+
+ cmd = arg;
+ rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
+ wsys = getenv("wsys");
+ fd = open(wsys, ORDWR);
+ if(fd < 0)
+ fprint(2, "rio: failed to open wsys: %r\n");
+ if(mount(fd, -1, "/mnt/wsys", MREPL, "none") < 0)
+ fprint(2, "rio: failed to mount wsys: %r\n");
+ if(bind("/mnt/wsys", "/dev/", MBEFORE) < 0)
+ fprint(2, "rio: failed to bind wsys: %r\n");
+ free(wsys);
+ close(fd);
+ 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++)
+ if(window[i]->notefd >= 0)
+ write(window[i]->notefd, "hangup", 6);
+}
+
+static int tapseats[] = { [OREAD] Tapoff, [OWRITE] Tapoff };
+
+char*
+tapctlmsg(char *msg)
+{
+ int perm;
+
+ perm = msg[1];
+ switch(msg[0]){
+ case Tapoff:
+ if(perm == ORDWR)
+ tapseats[OREAD] = Tapoff, tapseats[OWRITE] = Tapoff;
+ else
+ tapseats[perm] = Tapoff;
+ break;
+ case Tapon:
+ switch(perm){
+ case ORDWR:
+ if(tapseats[OREAD] != Tapoff || tapseats[OWRITE] != Tapoff)
+ return "seat taken";
+ tapseats[OREAD] = Tapon, tapseats[OWRITE] = Tapon;
+ break;
+ case OREAD: case OWRITE:
+ if(tapseats[perm] != Tapoff)
+ return "seat taken";
+ tapseats[perm] = Tapon;
+ break;
+ }
+ break;
+ }
+ return nil;
+}
+
+void
+keyboardtap(void*)
+{
+ char *s, *ctl;
+ char *e;
+ char *watched;
+ Window *w, *cur;
+ static char keys[64];
+
+ threadsetname("keyboardtap");
+ enum { Awin, Actl, Afrom, Adev, Ato, Ainp, Awatch, NALT };
+ static Alt alts[NALT+1];
+ /* ctl */
+ alts[Awin].c = wintap;
+ alts[Awin].v = &w;
+ alts[Awin].op = CHANRCV;
+ alts[Actl].c = ctltap;
+ alts[Actl].v = &ctl;
+ alts[Actl].op = CHANRCV;
+ /* kbd input */
+ alts[Afrom].c = fromtap;
+ alts[Afrom].v = &s;
+ alts[Afrom].op = CHANRCV;
+ alts[Adev].c = kbdchan;
+ alts[Adev].v = &s;
+ alts[Adev].op = CHANRCV;
+ /* kbd output */
+ alts[Ato].c = totap;
+ alts[Ato].v = &s;
+ alts[Ato].op = CHANNOP;
+ alts[Ainp].c = nil;
+ alts[Ainp].v = &s;
+ alts[Ainp].op = CHANNOP;
+ alts[Awatch].c = totap;
+ alts[Awatch].v = &watched;
+ alts[Awatch].op = CHANNOP;
+ alts[NALT].op = CHANEND;
+
+ cur = nil;
+ watched = nil;
+ keys[0] = 0;
+ for(;;)
+ switch(alt(alts)){
+ case Awin:
+ cur = w;
+ if(cur != nil){
+ alts[Ainp].c = cur->ck;
+ if(tapseats[OREAD] != Tapoff){
+ if(alts[Awatch].op == CHANSND)
+ free(watched);
+ watched = smprint("%c%d", Tapfocus, cur->id);
+ alts[Awatch].op = CHANSND;
+ }
+ }
+ if(alts[Ainp].op != CHANNOP || alts[Ato].op != CHANNOP)
+ free(s);
+ if(cur == nil)
+ goto Reset;
+ s = smprint("K%s", keys);
+ alts[Ainp].op = CHANSND;
+ alts[Ato].op = CHANNOP;
+ break;
+ case Actl:
+ e = tapctlmsg(ctl);
+ sendp(resptap, e);
+ if(e != nil || *ctl != Tapoff){
+ free(ctl);
+ break;
+ }
+ free(ctl);
+ goto Reset;
+ case Afrom:
+ if(cur == nil){
+ free(s);
+ break;
+ }
+ alts[Afrom].op = CHANNOP;
+ alts[Adev].op = CHANNOP;
+ alts[Ato].op = CHANNOP;
+ alts[Ainp].op = CHANSND;
+ break;
+ case Adev:
+ if(s[0] == 'k' || s[0] == 'K')
+ strcpy(keys, s+1);
+ if(tapseats[OWRITE] == Tapoff && cur == nil){
+ free(s);
+ break;
+ }
+ alts[Afrom].op = CHANNOP;
+ alts[Adev].op = CHANNOP;
+ if(tapseats[OWRITE] == Tapoff)
+ alts[Ainp].op = CHANSND;
+ else
+ alts[Ato].op = CHANSND;
+ break;
+ case Awatch:
+ alts[Awatch].op = CHANNOP;
+ break;
+ case Ainp:
+ if(*s == 'k' || *s == 'K')
+ shiftdown = utfrune(s+1, Kshift) != nil;
+ case Ato:
+ Reset:
+ alts[Ainp].op = CHANNOP;
+ alts[Ato].op = CHANNOP;
+ alts[Afrom].op = CHANRCV;
+ alts[Adev].op = CHANRCV;
+ break;
+ }
+}
+
+int
+inborder(Rectangle r, Point xy)
+{
+ return ptinrect(xy, r) && !ptinrect(xy, insetrect(r, Selborder));
+}
+
+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+1, r.max.y);
+ break;
+ case 6: /* bottom left */
+ r = Rect(p.x, r.min.y, r.max.x, p.y+1);
+ break;
+ case 8: /* bottom right */
+ r = Rect(r.min.x, r.min.y, p.x+1, p.y+1);
+ 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+1, r.max.y);
+ break;
+ case 7: /* bottom edge */
+ r = Rect(r.min.x, r.min.y, r.max.x, p.y+1);
+ break;
+ case 3: /* left edge */
+ r = Rect(p.x, r.min.y, r.max.x, r.max.y);
+ break;
+ }
+ return canonrect(r);
+}
+
+int
+portion(int x, int lo, int hi)
+{
+ x -= lo;
+ hi -= lo;
+ if(x < hi/2){
+ if(x < 20)
+ return 0;
+ } else {
+ if(x > hi-20)
+ return 2;
+ }
+ return 1;
+}
+
+int
+whichcorner(Rectangle r, Point p)
+{
+ int i, j;
+
+ i = portion(p.x, r.min.x, r.max.x);
+ j = portion(p.y, r.min.y, r.max.y);
+ return 3*j+i;
+}
+
+/* thread to allow fsysproc to synchronize window closing with main proc */
+void
+winclosethread(void*)
+{
+ Window *w;
+
+ threadsetname("winclosethread");
+ for(;;){
+ w = recvp(winclosechan);
+ wclose(w);
+ }
+}
+
+/*
+ * 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;
+ Window *w, *winput;
+ Image *i;
+ Point xy;
+ Mouse tmp;
+ enum {
+ MReshape,
+ MMouse,
+ NALT
+ };
+ static Alt alts[NALT+1];
+
+ threadsetname("mousethread");
+ sending = FALSE;
+ scrolling = 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:
+ moving = FALSE;
+ 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->deleted && 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 && inborder(winput->screenr, mouse->xy) && winput->topped>0)
+ moving = TRUE;
+ else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1)))
+ sending = TRUE;
+ }else
+ sending = FALSE;
+ if(sending){
+ Sending:
+ wsetcursor(winput, FALSE);
+ if(mouse->buttons == 0)
+ sending = FALSE;
+ tmp = mousectl->Mouse;
+ tmp.xy = xy;
+ send(winput->mc.c, &tmp);
+ continue;
+ }
+ if(moving && (mouse->buttons&7)){
+ incref(winput);
+ sweeping = TRUE;
+ if(mouse->buttons & 3)
+ i = bandsize(winput);
+ else
+ i = drag(winput);
+ sweeping = FALSE;
+ if(i != nil){
+ wcurrent(winput);
+ wsendctlmesg(winput, Reshaped, i->r, i);
+ }
+ wclose(winput);
+ continue;
+ }
+ w = wpointto(mouse->xy);
+ if(w!=nil && inborder(w->screenr, mouse->xy))
+ riosetcursor(corners[whichcorner(w->screenr, mouse->xy)]);
+ else
+ wsetcursor(w, FALSE);
+ /* 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->deleted && !winput->mouseopen){
+ incref(winput);
+ button2menu(winput);
+ wclose(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 || inborder(w->screenr, mouse->xy)))
+ goto Again;
+ goto Drain;
+ }
+ }
+ break;
+
+ Drain:
+ do
+ readmouse(mousectl);
+ while(mousectl->buttons);
+ goto Again; /* recalculate mouse position, cursor */
+ }
+}
+
+int
+wtopcmp(void *a, void *b)
+{
+ return (*(Window**)a)->topped - (*(Window**)b)->topped;
+}
+
+void
+resized(void)
+{
+ Image *im;
+ int i, j;
+ 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, cols[Crioback], 0);
+ if(wscreen == nil)
+ error("can't re-allocate screen");
+ draw(view, view->r, cols[Crioback], nil, ZP);
+ o = subpt(viewr.max, viewr.min);
+ n = subpt(view->clipr.max, view->clipr.min);
+ qsort(window, nwindow, sizeof(window[0]), wtopcmp);
+ for(i=0; i<nwindow; i++){
+ w = window[i];
+ 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, view->clipr.min);
+ if(!goodrect(r))
+ r = rectsubpt(w->i->r, subpt(w->i->r.min, r.min));
+ for(j=0; j<nhidden; j++)
+ if(w == hidden[j])
+ break;
+ incref(w);
+ if(j < nhidden){
+ im = allocimage(display, r, screen->chan, 0, DNofill);
+ r = ZR;
+ } else {
+ im = allocwindow(wscreen, r, Refbackup, DNofill);
+ }
+ if(im!=nil)
+ wsendctlmesg(w, Reshaped, r, im);
+ wclose(w);
+ }
+ viewr = view->r;
+ flushimage(display, 1);
+}
+
+int
+obscured(Window *w, Rectangle r, int i)
+{
+ Window *t;
+
+ if(Dx(r) < font->height || Dy(r) < font->height)
+ return 1;
+ if(!rectclip(&r, screen->r))
+ return 1;
+ for(; i<nwindow; i++){
+ t = window[i];
+ if(t == w || t->topped <= w->topped)
+ continue;
+ if(Dx(t->screenr) == 0 || Dy(t->screenr) == 0 || rectXrect(r, t->screenr) == 0)
+ continue;
+ if(r.min.y < t->screenr.min.y)
+ if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->screenr.min.y), i))
+ return 0;
+ if(r.min.x < t->screenr.min.x)
+ if(!obscured(w, Rect(r.min.x, r.min.y, t->screenr.min.x, r.max.y), i))
+ return 0;
+ if(r.max.y > t->screenr.max.y)
+ if(!obscured(w, Rect(r.min.x, t->screenr.max.y, r.max.x, r.max.y), i))
+ return 0;
+ if(r.max.x > t->screenr.max.x)
+ if(!obscured(w, Rect(t->screenr.max.x, r.min.y, r.max.x, r.max.y), i))
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+static char*
+shortlabel(char *s)
+{
+ enum { NBUF=60 };
+ static char buf[NBUF*UTFmax];
+ int i, k, l;
+ Rune r;
+
+ l = utflen(s);
+ if(l < NBUF-2)
+ return estrdup(s);
+ k = i = 0;
+ while(i < NBUF/2){
+ k += chartorune(&r, s+k);
+ i++;
+ }
+ strncpy(buf, s, k);
+ strcpy(buf+k, "...");
+ while((l-i) >= NBUF/2-4){
+ k += chartorune(&r, s+k);
+ i++;
+ }
+ strcat(buf, s+k);
+ return estrdup(buf);
+}
+
+void
+button3menu(void)
+{
+ int i, j, n;
+
+ n = nhidden;
+ for(i=0; i<nwindow; i++){
+ for(j=0; j<n; j++)
+ if(window[i] == hidden[j])
+ break;
+ if(j == n)
+ if(obscured(window[i], window[i]->screenr, 0)){
+ hidden[n++] = window[i];
+ if(n >= nelem(hidden))
+ break;
+ }
+ }
+ if(n >= nelem(menu3str)-Hidden)
+ n = nelem(menu3str)-Hidden-1;
+ for(i=0; i<n; i++){
+ free(menu3str[i+Hidden]);
+ menu3str[i+Hidden] = shortlabel(hidden[i]->label);
+ }
+ for(i+=Hidden; menu3str[i]; i++){
+ free(menu3str[i]);
+ menu3str[i] = nil;
+ }
+ sweeping = TRUE;
+ menu3.lasthit = 0;
+ 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:
+ confirmexit();
+ break;
+ default:
+ unhide(i);
+ break;
+ }
+ sweeping = FALSE;
+}
+
+void
+button2menu(Window *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 Look:
+ wlook(w);
+ break;
+
+ case Send:
+ wsend(w);
+ break;
+
+ case Scroll:
+ if(w->scrolling ^= 1)
+ wshow(w, w->nr);
+ break;
+ }
+ flushimage(display, 1);
+ wsendctlmesg(w, Wakeup, ZR, nil);
+}
+
+Point
+onscreen(Point p)
+{
+ p.x = max(screen->clipr.min.x, p.x);
+ p.x = min(screen->clipr.max.x-1, p.x);
+ p.y = max(screen->clipr.min.y, p.y);
+ p.y = min(screen->clipr.max.y-1, p.y);
+ return p;
+}
+
+Image*
+sweep(void)
+{
+ Image *i, *oi;
+ Rectangle r;
+ Point p0, p;
+
+ i = nil;
+ menuing = TRUE;
+ riosetcursor(&crosscursor);
+ while(mouse->buttons == 0)
+ readmouse(mousectl);
+ p0 = onscreen(mouse->xy);
+ p = p0;
+ r.min = p;
+ r.max = p;
+ oi = nil;
+ while(mouse->buttons == 4){
+ if(!eqpt(mouse->xy, p)){
+ p = onscreen(mouse->xy);
+ r = canonrect(Rpt(p0, p));
+ r = whichrect(r, p, whichcorner(r, p));
+ if(Dx(r)>5 && Dy(r)>5){
+ i = allocwindow(wscreen, r, Refnone, DNofill);
+ freeimage(oi);
+ if(i == nil)
+ goto Rescue;
+ oi = i;
+ border(i, r, Selborder, cols[Csize], ZP);
+ draw(i, insetrect(r, Selborder), cols[BACK], nil, ZP);
+ }
+ }
+ readmouse(mousectl);
+ }
+ if(mouse->buttons != 0)
+ goto Rescue;
+ if(i==nil || !goodrect(r))
+ goto Rescue;
+ oi = i;
+ i = allocwindow(wscreen, oi->r, Refbackup, DNofill);
+ freeimage(oi);
+ if(i == nil)
+ goto Rescue;
+ riosetcursor(corners[whichcorner(i->r, mouse->xy)]);
+ goto Return;
+
+ Rescue:
+ riosetcursor(nil);
+ freeimage(i);
+ i = nil;
+ flushimage(display, 1);
+ while(mouse->buttons)
+ readmouse(mousectl);
+
+ Return:
+ menuing = FALSE;
+ return i;
+}
+
+void
+drawedge(Image **bp, Image *col, 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);
+ b = allocwindow(wscreen, r, Refbackup, DNofill);
+ if(b != nil) draw(b, r, col, nil, ZP);
+ *bp = b;
+ }
+}
+
+void
+drawborder(Rectangle r, Image *col)
+{
+ static Image *b[4], *lastcol;
+
+ if(col != lastcol){
+ freeimage(b[0]), b[0] = nil;
+ freeimage(b[1]), b[1] = nil;
+ freeimage(b[2]), b[2] = nil;
+ freeimage(b[3]), b[3] = nil;
+ }
+ if(col != nil){
+ r = canonrect(r);
+ drawedge(&b[0], col, Rect(r.min.x, r.min.y, r.min.x+Borderwidth, r.max.y));
+ drawedge(&b[1], col, Rect(r.min.x+Borderwidth, r.min.y, r.max.x-Borderwidth, r.min.y+Borderwidth));
+ drawedge(&b[2], col, Rect(r.max.x-Borderwidth, r.min.y, r.max.x, r.max.y));
+ drawedge(&b[3], col, Rect(r.min.x+Borderwidth, r.max.y-Borderwidth, r.max.x-Borderwidth, r.max.y));
+ }
+ lastcol = col;
+}
+
+Image*
+drag(Window *w)
+{
+ Point p, op, d, dm, om;
+ Rectangle r;
+
+ menuing = TRUE;
+ riosetcursor(&boxcursor);
+ om = mouse->xy;
+ dm = subpt(om, w->screenr.min);
+ d = subpt(w->screenr.max, w->screenr.min);
+ op = subpt(om, dm);
+ drawborder(Rect(op.x, op.y, op.x+d.x, op.y+d.y), cols[Csize]);
+ 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), cols[Csize]);
+ op = p;
+ }
+ readmouse(mousectl);
+ }
+ r = Rect(op.x, op.y, op.x+d.x, op.y+d.y);
+ drawborder(r, nil);
+ p = mouse->xy;
+ riosetcursor(inborder(r, p) ? corners[whichcorner(r, p)] : nil);
+ menuing = FALSE;
+ if(mouse->buttons!=0 || !goodrect(r) || eqrect(r, w->screenr)){
+ flushimage(display, 1);
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return nil;
+ }
+ return allocwindow(wscreen, r, Refbackup, DNofill);
+}
+
+Image*
+bandsize(Window *w)
+{
+ Rectangle r, or;
+ Point p, startp;
+ int which, owhich, but;
+
+ owhich = -1;
+ or = w->screenr;
+ but = mouse->buttons;
+ startp = onscreen(mouse->xy);
+ drawborder(or, cols[Csize]);
+ while(mouse->buttons == but) {
+ p = onscreen(mouse->xy);
+ which = whichcorner(or, p);
+ if(which != owhich && which != 4 && (owhich|~which) & 1){
+ owhich = which;
+ riosetcursor(corners[which]);
+ }
+ r = whichrect(or, p, owhich);
+ if(!eqrect(r, or) && goodrect(r)){
+ drawborder(r, cols[Csize]);
+ or = r;
+ }
+ readmouse(mousectl);
+ }
+ drawborder(or, nil);
+ if(!goodrect(or))
+ riosetcursor(nil);
+ if(mouse->buttons!=0 || !goodrect(or) || eqrect(or, w->screenr)
+ || abs(p.x-startp.x)+abs(p.y-startp.y) <= 1){
+ flushimage(display, 1);
+ while(mouse->buttons)
+ readmouse(mousectl);
+ return nil;
+ }
+ return allocwindow(wscreen, or, Refbackup, DNofill);
+}
+
+Window*
+pointto(int wait)
+{
+ Window *w;
+
+ menuing = TRUE;
+ riosetcursor(&sightcursor);
+ 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 */
+ riosetcursor(nil);
+ w = nil;
+ }
+ readmouse(mousectl);
+ }
+ if(w != nil && wpointto(mouse->xy) != w)
+ w = nil;
+ }
+ riosetcursor(nil);
+ menuing = FALSE;
+ return w;
+}
+
+void
+delete(void)
+{
+ Window *w;
+
+ w = pointto(TRUE);
+ if(w!=nil)
+ wsendctlmesg(w, Deleted, ZR, nil);
+}
+
+void
+confirmexit(void)
+{
+ menuing = TRUE;
+ riosetcursor(&skull);
+ while(mouse->buttons == 0)
+ readmouse(mousectl);
+ if(mouse->buttons != 4)
+ goto Nope;
+ while(mouse->buttons){
+ if(mouse->buttons != 4)
+ goto Nope;
+ readmouse(mousectl);
+ }
+ send(exitchan, nil);
+Nope:
+ riosetcursor(nil);
+ menuing = FALSE;
+}
+
+void
+resize(void)
+{
+ Window *w;
+ Image *i;
+
+ w = pointto(TRUE);
+ if(w == nil)
+ return;
+ incref(w);
+ i = sweep();
+ if(i!=nil){
+ wcurrent(w);
+ wsendctlmesg(w, Reshaped, i->r, i);
+ }
+ wclose(w);
+}
+
+void
+move(void)
+{
+ Window *w;
+ Image *i;
+
+ w = pointto(FALSE);
+ if(w == nil)
+ return;
+ incref(w);
+ i = drag(w);
+ if(i!=nil){
+ wcurrent(w);
+ wsendctlmesg(w, Reshaped, i->r, i);
+ }
+ wclose(w);
+}
+
+int
+whide(Window *w)
+{
+ Image *i;
+ int j;
+
+ for(j=0; j<nhidden; j++)
+ if(hidden[j] == w) /* already hidden */
+ return -1;
+ if(nhidden >= nelem(hidden))
+ return 0;
+ incref(w);
+ wuncurrent(w);
+ i = allocimage(display, w->screenr, w->i->chan, 0, DNofill);
+ if(i!=nil){
+ hidden[nhidden++] = w;
+ wsendctlmesg(w, Reshaped, ZR, i);
+ }
+ wclose(w);
+ return i!=0;
+}
+
+int
+wunhide(Window *w)
+{
+ int j;
+ Image *i;
+
+ for(j=0; j<nhidden; j++)
+ if(hidden[j] == w)
+ break;
+ if(j == nhidden)
+ return -1; /* not hidden */
+ incref(w);
+ wcurrent(w);
+ i = allocwindow(wscreen, w->i->r, Refbackup, DNofill);
+ if(i!=nil){
+ --nhidden;
+ memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*));
+ wsendctlmesg(w, Reshaped, w->i->r, i);
+ }
+ wclose(w);
+ return i!=0;
+}
+
+void
+hide(void)
+{
+ Window *w;
+
+ w = pointto(TRUE);
+ if(w)
+ whide(w);
+}
+
+void
+unhide(int j)
+{
+ Window *w;
+
+ if(j < Hidden)
+ return;
+ j -= Hidden;
+ w = hidden[j];
+ if(w == nil)
+ return;
+ if(j < nhidden){
+ wunhide(w);
+ return;
+ }
+ /* uncover obscured window */
+ for(j=0; j<nwindow; j++)
+ if(window[j] == w){
+ incref(w);
+ wcurrent(w);
+ wtopme(w);
+ wsendctlmesg(w, Topped, ZR, nil);
+ wclose(w);
+ return;
+ }
+}
+
+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;
+ if(hideit && nhidden >= nelem(hidden)){
+ freeimage(i);
+ return nil;
+ }
+ cm = chancreate(sizeof(Mouse), 0);
+ ck = chancreate(sizeof(char*), 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, STACK);
+ if(!hideit)
+ wcurrent(w);
+ 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, STACK);
+ 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){
+ free(w->dir);
+ w->dir = estrdup(dir);
+ }
+ chanfree(cpid);
+ return w;
+}
+
+static void
+kbdproc(void *arg)
+{
+ Channel *c = arg;
+ char buf[1024], *p, *e;
+ int fd, cfd, kfd, n;
+
+ threadsetname("kbdproc");
+
+ if((fd = open("/dev/cons", OREAD)) < 0){
+ chanprint(c, "%r");
+ return;
+ }
+ if((cfd = open("/dev/consctl", OWRITE)) < 0){
+ chanprint(c, "%r");
+ return;
+ }
+ fprint(cfd, "rawon");
+
+ if(sendp(c, nil) <= 0)
+ return;
+
+ if((kfd = open("/dev/kbd", OREAD)) >= 0){
+ close(fd);
+
+ /* only serve a kbd file per window when we got one */
+ servekbd = 1;
+
+ /* read kbd state */
+ while((n = read(kfd, buf, sizeof(buf)-1)) > 0){
+ e = buf+n;
+ e[-1] = 0;
+ e[0] = 0;
+ for(p = buf; p < e; p += strlen(p)+1)
+ chanprint(c, "%s", p);
+ }
+ } else {
+ /* read single characters */
+ p = buf;
+ for(;;){
+ Rune r;
+
+ e = buf + sizeof(buf);
+ if((n = read(fd, p, e-p)) <= 0)
+ break;
+ e = p + n;
+ while(p < e && fullrune(p, e - p)){
+ p += chartorune(&r, p);
+ if(r)
+ chanprint(c, "c%C", r);
+ }
+ n = e - p;
+ memmove(buf, p, n);
+ p = buf + n;
+ }
+ }
+ send(exitchan, nil);
+}
+
+Channel*
+initkbd(void)
+{
+ Channel *c;
+ char *e;
+
+ c = chancreate(sizeof(char*), 16);
+ procrfork(kbdproc, c, STACK, RFCFDG);
+ if(e = recvp(c)){
+ chanfree(c);
+ c = nil;
+ werrstr("%s", e);
+ free(e);
+ }
+ return c;
+}
--- /dev/null
+++ b/sys/src/cmd/bof/scrl.c
@@ -1,0 +1,181 @@
+#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
+Image*
+scrtemps(void)
+{
+ int h;
+
+ if(scrtmp == nil){
+ h = BIG*Dy(screen->r);
+ scrtmp = allocimage(display, Rect(0, 0, 32, h), screen->chan, 0, DNofill);
+ }
+ return scrtmp;
+}
+
+void
+freescrtemps(void)
+{
+ if(scrtmp){
+ 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;
+
+ b = scrtemps();
+ if(b == nil || w->i == nil)
+ return;
+ r = w->scrollr;
+ 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];
+
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ 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 y, my, h, first;
+
+ s = insetrect(w->scrollr, 1);
+ h = s.max.y-s.min.y;
+ oldp0 = ~0;
+ first = TRUE;
+ do{
+ my = w->mc.xy.y;
+ if(my < s.min.y)
+ my = s.min.y;
+ if(my >= s.max.y)
+ my = s.max.y;
+ 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 || but == 4){
+ y = max(1, (my-s.min.y)/w->font->height);
+ p0 = wbacknl(w, w->org, y);
+ }else{
+ y = max(my, s.min.y+w->font->height);
+ p0 = w->org+frcharofpt(w, Pt(s.max.x, y));
+ }
+ if(oldp0 != p0)
+ wsetorigin(w, p0, TRUE);
+ oldp0 = p0;
+ /* debounce */
+ if(first){
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ if(but > 3)
+ return;
+ 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);
+}
--- /dev/null
+++ b/sys/src/cmd/bof/time.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/bof/util.c
@@ -1,0 +1,159 @@
+#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");
+ setrealloctag(p, getcallerpc(&p));
+ return p;
+}
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("malloc failed");
+ setmalloctag(p, getcallerpc(&n));
+ memset(p, 0, n);
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *p;
+
+ p = malloc(strlen(s)+1);
+ if(p == nil)
+ error("strdup failed");
+ setmalloctag(p, getcallerpc(&s));
+ 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;
+}
+
+int
+isspace(Rune c)
+{
+ return c == 0 || c == ' ' || c == '\t' ||
+ c == '\n' || c == '\r' || c == '\v';
+}
+
+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;
+}
+
--- /dev/null
+++ b/sys/src/cmd/bof/wctl.c
@@ -1,0 +1,499 @@
+#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(badrect(r) || !eqrect(canonrect(r), r))
+ return 0;
+ /* reasonable sizes only please */
+ if(Dx(r) > BIG*Dx(screen->r))
+ return 0;
+ if(Dy(r) > BIG*Dy(screen->r))
+ return 0;
+ /*
+ * the height has to be big enough to fit one line of text.
+ * that includes the border on each side with an extra pixel
+ * so that the text is still drawn
+ */
+ if(Dx(r) < 100 || Dy(r) < 2*(Borderwidth+1)+font->height)
+ return 0;
+ /* window must be on screen */
+ if(!rectXrect(screen->r, r))
+ return 0;
+ /* must have some screen and border visible so we can move it out of the way */
+ if(rectinrect(screen->r, insetrect(r, Borderwidth)))
+ 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 = max(Dx(screen->r) / 4, 400);
+ dy = min(Dy(screen->r) / 3, Dy(screen->r) - 1.5*Borderwidth);
+ minx = 16*i;
+ miny = 16*i;
+ i++;
+ i %= 10;
+ return Rect(minx, miny, minx+dx, miny+dy);
+}
+
+void
+shift(int *minp, int *maxp, int min, int max)
+{
+ if(*maxp > max){
+ *minp += max-*maxp;
+ *maxp = max;
+ }
+ if(*minp < min){
+ *maxp += min-*minp;
+ if(*maxp > max)
+ *maxp = max;
+ *minp = min;
+ }
+}
+
+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, n, nt, param, xy, sign;
+ char *f[2], *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;
+ if((nt = gettokens(*cdp, f, nelem(f), " \t\r\n\v\f")) < 1)
+ return -1;
+ n = strlen(*cdp);
+ if((*cdp)[0] == '\'' && (*cdp)[n-1] == '\'')
+ ((*cdp)++)[n-1] = '\0'; /* drop quotes */
+ s += n+(nt-1);
+ 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, DNofill);
+ else
+ i = allocwindow(wscreen, rect, Refbackup, DNofill);
+ if(i == nil){
+ strcpy(err, Ewalloc);
+ return -1;
+ }
+
+ new(i, hideit, scrollit, pid, dir, "/bin/rc", argv);
+
+ free(argv); /* when new() returns, argv and args have been copied */
+ return 1;
+}
+
+int
+wctlcmd(Window *w, Rectangle r, int cmd, char *err)
+{
+ Image *i;
+
+ switch(cmd){
+ case Move:
+ r = Rect(r.min.x, r.min.y, r.min.x+Dx(w->screenr), r.min.y+Dy(w->screenr));
+ r = rectonscreen(r);
+ /* fall through */
+ case Resize:
+ if(!goodrect(r)){
+ strcpy(err, Ebadwr);
+ return -1;
+ }
+ if(Dx(w->screenr) > 0){
+ if(eqrect(r, w->screenr))
+ return 1;
+ if(w != input){
+ strcpy(err, "window not current");
+ return -1;
+ }
+ i = allocwindow(wscreen, r, Refbackup, DNofill);
+ } else { /* hidden */
+ if(eqrect(r, w->i->r))
+ return 1;
+ wuncurrent(w);
+ i = allocimage(display, r, w->i->chan, 0, DNofill);
+ r = ZR;
+ }
+ if(i == nil){
+ strcpy(err, Ewalloc);
+ return -1;
+ }
+ wsendctlmesg(w, Reshaped, 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:
+ if(Dx(w->screenr)<=0){
+ strcpy(err, "window is hidden");
+ return -1;
+ }
+ wcurrent(w);
+ wtopme(w);
+ wsendctlmesg(w, Topped, ZR, nil);
+ 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:
+ switch(wunhide(w)){
+ case -1:
+ strcpy(err, "window not hidden");
+ return -1;
+ case 0:
+ strcpy(err, "hide failed");
+ return -1;
+ default:
+ break;
+ }
+ return 1;
+ case Delete:
+ wsendctlmesg(w, Deleted, ZR, nil);
+ return 1;
+ }
+
+ strcpy(err, "invalid wctl message");
+ return -1;
+}
+
+int
+writewctl(Xfid *x, char *err)
+{
+ int cnt, cmd, id, hideit, scrollit, pid;
+ char *arg, *dir;
+ Rectangle r;
+ Window *w;
+
+ w = x->f->w;
+ cnt = x->count;
+ x->data[cnt] = '\0';
+ id = 0;
+
+ if(w == nil)
+ r = ZR;
+ else
+ r = rectsubpt(w->screenr, screen->r.min);
+ cmd = parsewctl(&arg, r, &r, &pid, &id, &hideit, &scrollit, &dir, x->data, err);
+ if(cmd < 0)
+ return -1;
+
+ if(id != 0){
+ w = wlookid(id);
+ if(w == 0){
+ strcpy(err, "no such window id");
+ return -1;
+ }
+ }
+
+ if(w == nil && cmd != New){
+ strcpy(err, "command needs to be run within a window");
+ return -1;
+ }
+
+ switch(cmd){
+ case New:
+ return wctlnew(r, arg, pid, hideit, scrollit, dir, err);
+ case Set:
+ if(pid > 0)
+ wsetpid(w, pid, 0);
+ return 1;
+ }
+
+ incref(w);
+ id = wctlcmd(w, r, cmd, err);
+ wclose(w);
+
+ return id;
+}
--- /dev/null
+++ b/sys/src/cmd/bof/wind.c
@@ -1,0 +1,1830 @@
+#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"
+
+Window*
+wlookid(int id)
+{
+ int i;
+
+ for(i=0; i<nwindow; i++)
+ if(window[i]->id == id)
+ return window[i];
+ return nil;
+}
+
+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(w==nil || v->topped>w->topped)
+ w = v;
+ }
+ return w;
+}
+
+static int topped;
+
+void
+wtopme(Window *w)
+{
+ if(w!=nil && w->i!=nil && w->topped!=topped){
+ w->topped = ++topped;
+ topwindow(w->i);
+ flushimage(display, 1);
+ }
+}
+
+void
+wbottomme(Window *w)
+{
+ if(w!=nil && w->i!=nil){
+ w->topped = - ++topped;
+ bottomwindow(w->i);
+ flushimage(display, 1);
+ }
+}
+
+Window*
+wtop(Point pt)
+{
+ Window *w;
+
+ w = wpointto(pt);
+ if(w!=nil){
+ incref(w);
+ wcurrent(w);
+ wtopme(w);
+ wsendctlmesg(w, Topped, ZR, nil);
+ wclose(w);
+ }
+ return w;
+}
+
+void
+wcurrent(Window *w)
+{
+ Channel *c;
+
+ if(input == nil){
+ sendp(wintap, w);
+ input = w;
+ return;
+ }
+ if(w == input)
+ return;
+ incref(input);
+ sendp(wintap, w);
+ c = chancreate(sizeof(Window*), 0);
+ wsendctlmesg(input, Repaint, ZR, c);
+ sendp(c, w); /* send the new input */
+ wclose(recvp(c)); /* release old input */
+ chanfree(c);
+}
+
+void
+wuncurrent(Window *w)
+{
+ Channel *c;
+
+ if(input == nil || w != input)
+ return;
+ c = chancreate(sizeof(Window*), 0);
+ wsendctlmesg(w, Repaint, ZR, c);
+ sendp(c, nil);
+ recvp(c);
+ chanfree(c);
+}
+
+static Cursor *lastcursor;
+
+void
+riosetcursor(Cursor *p)
+{
+ if(p==lastcursor)
+ return;
+ setcursor(mousectl, p);
+ lastcursor = p;
+}
+
+void
+wsetcursor(Window *w, int force)
+{
+ Cursor *p;
+
+ if(menuing || sweeping || (w!=input && wpointto(mouse->xy)!=w))
+ return;
+ if(w==nil)
+ p = nil;
+ else {
+ p = w->cursorp;
+ if(p==nil && w->holding)
+ p = &whitearrow;
+ }
+ if(p && force) /* force cursor reload */
+ lastcursor = nil;
+ riosetcursor(p);
+}
+
+static 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;
+}
+
+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 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;
+}
+
+static void
+wfill(Window *w)
+{
+ Rune *rp;
+ int i, n, m, nl;
+
+ while(w->lastlinefull == FALSE){
+ n = w->nr-(w->org+w->nchars);
+ if(n == 0)
+ break;
+ if(n > 2000) /* educated guess at reasonable amount */
+ n = 2000;
+ rp = w->r+(w->org+w->nchars);
+
+ /*
+ * 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);
+ }
+}
+
+static 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;
+}
+
+static void
+wborder(Window *w, int type)
+{
+ Image *col;
+
+ if(w->i == nil)
+ return;
+ if(w->holding){
+ if(type == Selborder)
+ col = cols[Chold];
+ else
+ col = cols[Cpalehold];
+ }else{
+ if(type == Selborder)
+ col = cols[Ctitle];
+ else
+ col = cols[Cltitle];
+ }
+ border(w->i, w->i->r, Selborder, col, ZP);
+}
+
+static void
+wsetcols(Window *w, int topped)
+{
+ if(w->holding)
+ if(topped)
+ w->cols[TEXT] = cols[Chold];
+ else
+ w->cols[TEXT] = cols[Clhold];
+ else
+ if(topped)
+ w->cols[TEXT] = cols[Ctext];
+ else
+ w->cols[TEXT] = cols[Cpaletext];
+}
+
+void
+wsetname(Window *w)
+{
+ int i, n;
+ char err[ERRMAX];
+
+ n = snprint(w->name, sizeof(w->name)-2, "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);
+}
+
+static void
+wresize(Window *w, Image *i)
+{
+ Rectangle r;
+
+ w->i = i;
+ 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;
+ frclear(w, FALSE);
+ frinit(w, r, w->font, w->i, cols);
+ wsetcols(w, w == input);
+ w->maxtab = maxtab*stringwidth(w->font, "0");
+ if(!w->mouseopen || !w->winnameread){
+ 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);
+ }
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+ flushimage(display, 1);
+ wsetname(w);
+ w->topped = ++topped;
+ w->resized = TRUE;
+ w->winnameread = FALSE;
+ w->mc.buttons = 0; /* avoid re-triggering clicks on resize */
+ w->mouse.counter++;
+ w->wctlready = 1;
+}
+
+static void
+wrepaint(Window *w)
+{
+ wsetcols(w, w == input);
+ if(!w->mouseopen || !w->winnameread)
+ frredraw(w);
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+}
+
+static void
+wrefresh(Window *w)
+{
+ Rectangle r;
+
+ if(w == input)
+ wborder(w, Selborder);
+ else
+ wborder(w, Unselborder);
+ r = insetrect(w->i->r, Selborder);
+ draw(w->i, r, w->cols[BACK], nil, w->entire.min);
+ wfill(w);
+ 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);
+}
+
+/*
+ * 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.
+ */
+static void
+interruptproc(void *v)
+{
+ int *notefd;
+
+ notefd = v;
+ write(*notefd, "interrupt", 9);
+ close(*notefd);
+ free(notefd);
+}
+
+typedef struct Completejob Completejob;
+struct Completejob
+{
+ char *dir;
+ char *str;
+ Window *win;
+};
+
+static void
+completeproc(void *arg)
+{
+ Completejob *job;
+ Completion *c;
+
+ job = arg;
+ threadsetname("namecomplete %s", job->dir);
+
+ c = complete(job->dir, job->str);
+ if(c != nil && sendp(job->win->complete, c) <= 0)
+ freecompletion(c);
+
+ wclose(job->win);
+
+ free(job->dir);
+ free(job->str);
+ free(job);
+}
+
+static int
+windfilewidth(Window *w, uint q0, int oneelement)
+{
+ uint q;
+ Rune r;
+
+ q = q0;
+ while(q > 0){
+ r = w->r[q-1];
+ if(r<=' ' || r=='=' || r=='^' || r=='(' || r=='{')
+ break;
+ if(oneelement && r=='/')
+ break;
+ --q;
+ }
+ return q0-q;
+}
+
+static void
+namecomplete(Window *w)
+{
+ int nstr, npath;
+ Rune *path, *str;
+ char *dir, *root;
+ Completejob *job;
+
+ /* 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;
+ nstr = windfilewidth(w, w->q0, TRUE);
+ str = w->r+(w->q0-nstr);
+ npath = windfilewidth(w, w->q0-nstr, FALSE);
+ path = w->r+(w->q0-nstr-npath);
+
+ /* is path rooted? if not, we need to make it relative to window path */
+ if(npath>0 && path[0]=='/')
+ dir = runetobyte(path, npath, &npath);
+ else {
+ if(strcmp(w->dir, "") == 0)
+ root = ".";
+ else
+ root = w->dir;
+ dir = smprint("%s/%.*S", root, npath, path);
+ }
+ if(dir == nil)
+ return;
+
+ /* run in background, winctl will collect the result on w->complete chan */
+ job = emalloc(sizeof *job);
+ job->str = runetobyte(str, nstr, &nstr);
+ job->dir = cleanname(dir);
+ job->win = w;
+ incref(w);
+ proccreate(completeproc, job, STACK);
+}
+
+static void
+showcandidates(Window *w, Completion *c)
+{
+ int i;
+ Fmt f;
+ Rune *rp;
+ uint nr, qline;
+ 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");
+ }
+ rp = runefmtstrflush(&f);
+ nr = runestrlen(rp);
+
+ /* place text at beginning of line before cursor and host point */
+ qline = min(w->qh, w->q0);
+ while(qline>0 && w->r[qline-1] != '\n')
+ qline--;
+
+ if(qline == w->qh){
+ /* advance host point to avoid readback */
+ w->qh = winsert(w, rp, nr, qline)+nr;
+ } else {
+ winsert(w, rp, nr, qline);
+ }
+ free(rp);
+}
+
+static 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
+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 = w->r+org;
+ frinsert(w, r, r+n, 0);
+ }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);
+}
+
+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;
+}
+
+char*
+wcontents(Window *w, int *ip)
+{
+ return runetobyte(w->r, w->nr, ip);
+}
+
+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
+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
+wsend(Window *w)
+{
+ getsnarf();
+ wsnarf(w);
+ if(nsnarf == 0)
+ return;
+ 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);
+}
+
+static 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);
+ }
+}
+
+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
+wlook(Window *w)
+{
+ int i, n, e;
+
+ i = w->q1;
+ n = i - w->q0;
+ e = w->nr - n;
+ if(n <= 0 || e < n)
+ return;
+
+ if(i > e)
+ i = 0;
+
+ while(runestrncmp(w->r+w->q0, w->r+i, n) != 0){
+ if(i < e)
+ i++;
+ else
+ i = 0;
+ }
+
+ wsetselect(w, i, i+n);
+ wshow(w, i);
+}
+
+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++;
+ snprint(buf, sizeof(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);
+ sleep(300);
+ riosetcursor(c);
+ }
+ plumbfree(m);
+}
+
+static void
+wkeyctl(Window *w, Rune r)
+{
+ uint q0 ,q1;
+ int n, nb;
+ int *notefd;
+
+ switch(r){
+ case 0:
+ case Kcaps:
+ case Knum:
+ case Kshift:
+ case Kalt:
+ case Kctl:
+ case Kaltgr:
+ return;
+ }
+
+ if(w->i==nil)
+ return;
+ /* navigation keys work only when mouse and kbd is not open */
+ if(!w->mouseopen)
+ switch(r){
+ case Kdown:
+ n = shiftdown ? 1 : 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 = shiftdown ? 1 : 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 Kscroll:
+ w->scrolling ^= 1;
+ wshow(w, w->nr);
+ return;
+ case Ksoh: /* ^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 Kenq: /* ^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;
+ case Kstx: /* ^B: output point */
+ wsetselect(w, w->qh, w->qh);
+ wshow(w, w->q0);
+ return;
+ }
+ if(w->rawing && (w->q0==w->nr || w->mouseopen)){
+ waddraw(w, &r, 1);
+ return;
+ }
+ if(r==Kesc || (w->holding && r==Kdel)){ /* toggle hold */
+ if(w->holding)
+ --w->holding;
+ else
+ w->holding++;
+ wsetcursor(w, FALSE);
+ wrepaint(w);
+ if(r == Kesc)
+ return;
+ }
+ if(r != Kdel){
+ wsnarf(w);
+ wcut(w);
+ }
+ switch(r){
+ case Kdel: /* send interrupt */
+ w->qh = w->nr;
+ wshow(w, w->qh);
+ if(w->notefd < 0)
+ return;
+ notefd = emalloc(sizeof(int));
+ *notefd = dup(w->notefd, -1);
+ proccreate(interruptproc, notefd, 4096);
+ return;
+ case Kack: /* ^F: file name completion */
+ case Kins: /* Insert: file name completion */
+ namecomplete(w);
+ return;
+ case Kbs: /* ^H: erase character */
+ case Knack: /* ^U: erase line */
+ case Ketb: /* ^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);
+}
+
+static Window *clickwin;
+static uint clickmsec;
+static Point clickpt;
+static uint clickcount;
+static Window *selectwin;
+static uint selectq;
+
+static 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);
+}
+
+/*
+ * called from frame library
+ */
+static void
+framescroll(Frame *f, int dl)
+{
+ if(f != &selectwin->Frame)
+ error("frameselect not right frame");
+ wframescroll(selectwin, dl);
+}
+
+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 };
+
+static Rune *left[] = {
+ left1,
+ left2,
+ left3,
+ nil
+};
+static Rune *right[] = {
+ right1,
+ left2,
+ left3,
+ nil
+};
+
+static 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;
+}
+
+static int
+inmode(Rune r, int mode)
+{
+ return (mode == 1) ? isalnum(r) : r && !isspace(r);
+}
+
+static void
+wstretchsel(Window *w, uint pt, uint *q0, uint *q1, int mode)
+{
+ int c, i;
+ Rune *r, *l, *p;
+ uint q;
+
+ *q0 = pt;
+ *q1 = pt;
+ 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 && inmode(w->r[*q1], mode))
+ (*q1)++;
+ /* try filling out word to left */
+ while(*q0>0 && inmode(w->r[*q0-1], mode))
+ (*q0)--;
+}
+
+static void
+wselect(Window *w)
+{
+ uint q0, q1;
+ int b, x, y, dx, dy, mode, first;
+
+ first = 1;
+ selectwin = w;
+ /*
+ * Double-click immediately if it might make sense.
+ */
+ b = w->mc.buttons;
+ q0 = w->q0;
+ q1 = w->q1;
+ dx = abs(clickpt.x - w->mc.xy.x);
+ dy = abs(clickpt.y - w->mc.xy.y);
+ clickpt = w->mc.xy;
+ selectq = w->org+frcharofpt(w, w->mc.xy);
+ clickcount++;
+ if(w->mc.msec-clickmsec >= 500 || clickwin != w || clickcount > 3 || dx > 3 || dy > 3)
+ clickcount = 0;
+ if(clickwin == w && clickcount >= 1 && w->mc.msec-clickmsec < 500){
+ mode = (clickcount > 2) ? 2 : clickcount;
+ wstretchsel(w, selectq, &q0, &q1, mode);
+ wsetselect(w, q0, q1);
+ x = w->mc.xy.x;
+ y = w->mc.xy.y;
+ /* stay here until something interesting happens */
+ while(1){
+ readmouse(&w->mc);
+ dx = abs(w->mc.xy.x-x);
+ dy = abs(w->mc.xy.y-y);
+ if(w->mc.buttons != b || dx >= 3 && dy >= 3)
+ break;
+ clickcount++;
+ clickmsec = w->mc.msec;
+ }
+ 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 = w->org+frcharofpt(w, w->mc.xy);
+ }
+ if(w->mc.buttons == b && clickcount == 0){
+ 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){
+ mode = (clickcount > 2) ? 2 : clickcount;
+ if(q0==w->q0 && clickwin==w && w->mc.msec-clickmsec<500)
+ wstretchsel(w, selectq, &q0, &q1, mode);
+ else
+ clickwin = w;
+ clickmsec = w->mc.msec;
+ }
+ wsetselect(w, q0, q1);
+ 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);
+ while(w->mc.buttons == b)
+ readmouse(&w->mc);
+ if(w->mc.msec-clickmsec >= 500)
+ clickwin = nil;
+ }
+}
+
+/*
+ * Convert back to physical coordinates
+ */
+static void
+wmovemouse(Window *w, Point p)
+{
+ if(w != input || menuing || sweeping)
+ return;
+ 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);
+}
+
+
+Window*
+wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl, int scrolling)
+{
+ static int id;
+
+ Window *w;
+ Rectangle r;
+
+ 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->kbdread = chancreate(sizeof(Consreadmesg), 0);
+ w->mouseread = chancreate(sizeof(Mousereadmesg), 0);
+ w->wctlread = chancreate(sizeof(Consreadmesg), 0);
+ w->complete = chancreate(sizeof(Completion*), 0);
+ w->gone = chancreate(sizeof(char*), 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;
+}
+
+static void
+wclosewin(Window *w)
+{
+ Image *i = w->i;
+ if(i == nil)
+ return;
+ w->i = nil;
+ /* move it off-screen to hide it, in case client is slow in letting it go */
+ originwindow(i, i->r.min, view->r.max);
+ freeimage(i);
+}
+
+static void
+wclunk(Window *w)
+{
+ int i;
+
+ if(w->deleted)
+ return;
+ w->deleted = TRUE;
+ if(w == input){
+ sendp(wintap, nil);
+ input = nil;
+ riosetcursor(nil);
+ }
+ 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]));
+ break;
+ }
+ for(i=0; i<nwindow; i++)
+ if(window[i] == w){
+ --nwindow;
+ memmove(window+i, window+i+1, (nwindow-i)*sizeof(window[0]));
+ break;
+ }
+}
+
+int
+wclose(Window *w)
+{
+ int i;
+
+ i = decref(w);
+ if(i > 0)
+ return 0;
+ if(i < 0)
+ error("negative ref count");
+ wclunk(w);
+ wsendctlmesg(w, Exited, ZR, nil);
+ return 1;
+}
+
+void
+wsendctlmesg(Window *w, int type, Rectangle r, void *p)
+{
+ Wctlmesg wcm;
+
+ wcm.type = type;
+ wcm.r = r;
+ wcm.p = p;
+ send(w->cctl, &wcm);
+}
+
+static int
+wctlmesg(Window *w, int m, Rectangle r, void *p)
+{
+ Image *i = p;
+
+ switch(m){
+ default:
+ error("unknown control message");
+ break;
+ case Wakeup:
+ break;
+ case Reshaped:
+ if(w->deleted){
+ freeimage(i);
+ break;
+ }
+ w->screenr = r;
+ wclosewin(w);
+ wresize(w, i);
+ wsetcursor(w, FALSE);
+ break;
+ case Topped:
+ if(w->deleted)
+ break;
+ w->wctlready = 1;
+ wsetcursor(w, FALSE);
+ /* fall thrugh for redraw after input change */
+ case Repaint:
+ if(p != nil){
+ /* sync with input change from wcurrent()/wuncurrent() */
+ Channel *c = p;
+ input = recvp(c);
+
+ /* when we lost input, release mouse buttons */
+ if(w->mc.buttons){
+ w->mc.buttons = 0;
+ w->mouse.counter++;
+ }
+ w->wctlready = 1;
+
+ sendp(c, w);
+ }
+ if(w->i==nil || Dx(w->screenr)<=0)
+ break;
+ wrepaint(w);
+ flushimage(display, 1);
+ break;
+ case Refresh:
+ if(w->i==nil || Dx(w->screenr)<=0)
+ break;
+ wrefresh(w);
+ flushimage(display, 1);
+ break;
+ case Movemouse:
+ if(w->i==nil || Dx(w->screenr)<=0 || !ptinrect(r.min, w->i->r))
+ break;
+ wmovemouse(w, r.min);
+ case Rawon:
+ break;
+ case Rawoff:
+ 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->i==nil)
+ break;
+ wsetcursor(w, FALSE);
+ wrepaint(w);
+ flushimage(display, 1);
+ break;
+ case Truncate:
+ wdelete(w, 0, w->nr);
+ break;
+ case Deleted:
+ wclunk(w);
+ if(w->notefd >= 0)
+ write(w->notefd, "hangup", 6);
+ wclosewin(w);
+ flushimage(display, 1);
+ break;
+ case Exited:
+ wclosewin(w);
+ frclear(w, TRUE);
+ flushimage(display, 1);
+ if(w->notefd >= 0)
+ 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);
+ chanfree(w->kbdread);
+ chanfree(w->complete);
+ chanfree(w->gone);
+ free(w->raw);
+ free(w->r);
+ free(w->dir);
+ free(w->label);
+ free(w);
+ break;
+ }
+ return m;
+}
+
+static void
+wmousectl(Window *w)
+{
+ int but;
+
+ for(but=1;; but++){
+ if(but > 5)
+ return;
+ if(w->mc.buttons == 1<<(but-1))
+ break;
+ }
+
+ incref(w); /* hold up window while we track */
+ if(w->i != nil){
+ if(shiftdown && but > 3)
+ wkeyctl(w, but == 4 ? Kscrolloneup : Kscrollonedown);
+ else if(ptinrect(w->mc.xy, w->scrollr) || (but > 3))
+ wscroll(w, but);
+ else if(but == 1)
+ wselect(w);
+ }
+ wclose(w);
+}
+
+void
+winctl(void *arg)
+{
+ Rune *rp, *up, r;
+ uint qh, q0;
+ int nr, nb, c, wid, i, npart, initial, lastb;
+ char *s, *t, part[3];
+ Window *w;
+ Mousestate *mp, m;
+ enum { WKbd, WKbdread, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, WComplete, Wgone, NWALT };
+ Alt alts[NWALT+1];
+ Consreadmesg crm;
+ Mousereadmesg mrm;
+ Conswritemesg cwm;
+ Stringpair pair;
+ Wctlmesg wcm;
+ Completion *cr;
+ char *kbdq[32], *kbds;
+ uint kbdqr, kbdqw;
+
+ w = arg;
+ threadsetname("winctl-id%d", w->id);
+
+ mrm.cm = chancreate(sizeof(Mouse), 0);
+ crm.c1 = chancreate(sizeof(Stringpair), 0);
+ crm.c2 = chancreate(sizeof(Stringpair), 0);
+ cwm.cw = chancreate(sizeof(Stringpair), 0);
+
+ alts[WKbd].c = w->ck;
+ alts[WKbd].v = &kbds;
+ alts[WKbd].op = CHANRCV;
+ alts[WKbdread].c = w->kbdread;
+ alts[WKbdread].v = &crm;
+ alts[WKbdread].op = CHANSND;
+ 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 = &crm;
+ alts[WWread].op = CHANSND;
+ alts[WComplete].c = w->complete;
+ alts[WComplete].v = &cr;
+ alts[WComplete].op = CHANRCV;
+ alts[Wgone].c = w->gone;
+ alts[Wgone].v = "window deleted";
+ alts[Wgone].op = CHANNOP;
+ alts[NWALT].op = CHANEND;
+
+ kbdqr = kbdqw = 0;
+ npart = 0;
+ lastb = -1;
+ for(;;){
+ if(w->i==nil){
+ /* window deleted */
+ alts[Wgone].op = CHANSND;
+
+ alts[WKbdread].op = CHANNOP;
+ alts[WMouseread].op = CHANNOP;
+ alts[WCwrite].op = CHANNOP;
+ alts[WWread].op = CHANNOP;
+ alts[WCread].op = CHANNOP;
+ } else {
+ alts[WKbdread].op = (w->kbdopen && kbdqw != kbdqr) ?
+ CHANSND : CHANNOP;
+ alts[WMouseread].op = (w->mouseopen && w->mouse.counter != w->mouse.lastcounter) ?
+ CHANSND : CHANNOP;
+ alts[WCwrite].op = w->scrolling || w->mouseopen || (w->qh <= w->org+w->nchars) ?
+ CHANSND : CHANNOP;
+ alts[WWread].op = w->wctlready ?
+ CHANSND : CHANNOP;
+ /* 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 WKbd:
+ if(kbdqw - kbdqr < nelem(kbdq))
+ kbdq[kbdqw++ % nelem(kbdq)] = kbds;
+ else
+ free(kbds);
+ if(w->kbdopen)
+ continue;
+ while(kbdqr != kbdqw){
+ kbds = kbdq[kbdqr++ % nelem(kbdq)];
+ if(*kbds == 'c'){
+ chartorune(&r, kbds+1);
+ if(r)
+ wkeyctl(w, r);
+ }
+ free(kbds);
+ }
+ break;
+ case WKbdread:
+ recv(crm.c1, &pair);
+ nb = 0;
+ while(kbdqr != kbdqw){
+ kbds = kbdq[kbdqr % nelem(kbdq)];
+ i = strlen(kbds)+1;
+ if(nb+i > pair.ns)
+ break;
+ memmove((char*)pair.s + nb, kbds, i);
+ free(kbds);
+ nb += i;
+ kbdqr++;
+ }
+ pair.ns = nb;
+ send(crm.c2, &pair);
+ continue;
+ 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.p) == Exited){
+ while(kbdqr != kbdqw)
+ free(kbdq[kbdqr++ % nelem(kbdq)]);
+ chanfree(crm.c1);
+ chanfree(crm.c2);
+ chanfree(mrm.cm);
+ chanfree(cwm.cw);
+ threadexits(nil);
+ }
+ continue;
+ case WCwrite:
+ recv(cwm.cw, &pair);
+ rp = pair.s;
+ nr = pair.ns;
+ for(i=0; i<nr; i++)
+ if(rp[i] == '\b'){
+ up = rp+i;
+ initial = 0;
+ for(; i<nr; i++){
+ if(rp[i] == '\b'){
+ if(up == rp)
+ initial++;
+ else
+ up--;
+ }else
+ *up++ = rp[i];
+ }
+ if(initial){
+ if(initial > w->qh)
+ initial = w->qh;
+ qh = w->qh-initial;
+ wdelete(w, qh, qh+initial);
+ w->qh = qh;
+ }
+ nr = up - rp;
+ 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(crm.c1, &pair);
+ s = Dx(w->screenr) > 0 ? "visible" : "hidden";
+ t = "notcurrent";
+ if(w == input)
+ t = "current";
+ pair.ns = snprint(pair.s, pair.ns+1, "%11d %11d %11d %11d %11s %11s ",
+ w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
+ send(crm.c2, &pair);
+ continue;
+ case WComplete:
+ if(w->i!=nil){
+ if(!cr->advance)
+ showcandidates(w, cr);
+ if(cr->advance){
+ rp = runesmprint("%s", cr->string);
+ if(rp){
+ nr = runestrlen(rp);
+ q0 = w->q0;
+ q0 = winsert(w, rp, nr, q0);
+ wshow(w, q0+nr);
+ free(rp);
+ }
+ }
+ }
+ freecompletion(cr);
+ break;
+ }
+ if(w->i!=nil && Dx(w->screenr) > 0 && display->bufp > display->buf)
+ flushimage(display, 1);
+ }
+}
+
+void
+wsetpid(Window *w, int pid, int dolabel)
+{
+ char buf[32];
+ int ofd;
+
+ ofd = w->notefd;
+ if(pid <= 0)
+ w->notefd = -1;
+ else {
+ if(dolabel){
+ snprint(buf, sizeof(buf), "rc %lud", (ulong)pid);
+ free(w->label);
+ w->label = estrdup(buf);
+ }
+ snprint(buf, sizeof(buf), "/proc/%lud/notepg", (ulong)pid);
+ w->notefd = open(buf, OWRITE|OCEXEC);
+ }
+ if(ofd >= 0)
+ close(ofd);
+}
+
+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");
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/bof/xfid.c
@@ -1,0 +1,921 @@
+#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"
+
+char Einuse[] = "file in use";
+char Edeleted[] = "window deleted";
+char Etooshort[] = "buffer too small";
+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 Enope[] = "not appearing in this film";
+
+extern char Eperm[];
+extern char Enomem[];
+
+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*);
+
+ x = arg;
+ threadsetname("xfid.%p", x);
+ for(;;){
+ f = recvp(x->c);
+ if(f){
+ x->flushtag = x->tag;
+ (*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){
+ incref(xf); /* to hold data structures up at tail of synchronization */
+ if(xf->ref == 1)
+ error("ref 1 in flush");
+ xf->flushtag = -1;
+ break;
+ }
+
+ /* take over flushtag so follow up flushes wait for us */
+ x->flushtag = x->oldtag;
+
+ /*
+ * wakeup filsysflush() in the filsysproc so the next
+ * flush can come in.
+ */
+ sendul(x->fs->csyncflush, 0);
+
+ if(xf){
+ enum { Done, Flush, End };
+ Alt alts[End+1];
+ void *f;
+ int z;
+
+ z = 0;
+ f = nil;
+
+ alts[Done].c = xf->c;
+ alts[Done].v = &f;
+ alts[Done].op = CHANSND;
+ alts[Flush].c = xf->flushc;
+ alts[Flush].v = &z;
+ alts[Flush].op = CHANSND;
+ alts[End].op = CHANEND;
+
+ while(alt(alts) != Done)
+ ;
+ }
+ if(nbrecv(x->flushc, nil)){
+ filsyscancel(x);
+ return;
+ }
+ 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;
+ dir = nil;
+ newlymade = FALSE;
+ hideit = 0;
+ scrollit = scrolling;
+
+ 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, DNofill);
+ else
+ i = allocwindow(wscreen, r, Refbackup, DNofill);
+ if(i){
+ if(pid == 0)
+ pid = -1; /* make sure we don't pop a shell! - UGH */
+ w = new(i, hideit, scrollit, pid, dir, nil, nil);
+ 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 if(strncmp(x->aname, "none", 4) == 0){
+ x->f->w = nil;
+ goto Done;
+ }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);
+ Done:
+ qunlock(&all);
+ filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidopen(Xfid *x)
+{
+ Fcall t;
+ Window *w;
+ char *s;
+
+ w = x->f->w;
+ if(w != nil && w->deleted){
+ filsysrespond(x->fs, x, &t, Edeleted);
+ return;
+ }
+ switch(FILE(x->f->qid)){
+ case Qtext:
+ if(x->mode&OTRUNC)
+ wsendctlmesg(w, Truncate, ZR, nil);
+ break;
+ case Qconsctl:
+ if(w->ctlopen){
+ filsysrespond(x->fs, x, &t, Einuse);
+ return;
+ }
+ w->ctlopen = TRUE;
+ break;
+ case Qkbd:
+ if(w->kbdopen){
+ filsysrespond(x->fs, x, &t, Einuse);
+ return;
+ }
+ w->kbdopen = TRUE;
+ 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)
+ ntsnarf = 0;
+ break;
+ case Qwctl:
+ if(w != nil && (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;
+ case Qtap:
+ chanprint(ctltap, "%c%c", Tapon, x->mode);
+ s = recvp(resptap);
+ if(s == nil)
+ break;
+ filsysrespond(x->fs, x, &t, s);
+ return;
+ case Qdot:
+ if(x->mode == ORDWR || x->mode == OWRITE){
+ filsysrespond(x->fs, x, &t, Enope);
+ return;
+ }
+ 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 Qkbd:
+ w->kbdopen = FALSE;
+ break;
+ case Qmouse:
+ w->resized = FALSE;
+ w->mouseopen = FALSE;
+ w->winnameread = 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);
+ ntsnarf = 0;
+ }
+ break;
+ case Qwctl:
+ if(w != nil && (x->f->mode==OREAD || x->f->mode==ORDWR))
+ w->wctlopen = FALSE;
+ break;
+ case Qtap:
+ chanprint(ctltap, "%c%c", Tapoff, x->f->mode);
+ recvp(resptap);
+ break;
+ }
+ if(w)
+ wclose(w);
+ filsysrespond(x->fs, x, &t, nil);
+}
+
+void
+xfidwrite(Xfid *x)
+{
+ Fcall fc;
+ int cnt, qid, nb, nr;
+ char err[ERRMAX], *p, *e;
+ Point pt;
+ Window *w;
+ Rune *r;
+ Conswritemesg cwm;
+ Stringpair pair;
+ enum { CWdata, CWgone, CWflush, NCW };
+ Alt alts[NCW+1];
+
+ w = x->f->w;
+ if(w != nil && w->deleted){
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ }
+ qid = FILE(x->f->qid);
+ cnt = x->count;
+ x->data[cnt] = 0;
+ switch(qid){
+ case Qcons:
+ case Qtext:
+ alts[CWdata].c = w->conswrite;
+ alts[CWdata].v = &cwm;
+ alts[CWdata].op = CHANRCV;
+ alts[CWgone].c = w->gone;
+ alts[CWgone].v = nil;
+ alts[CWgone].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 CWgone:
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ case CWflush:
+ filsyscancel(x);
+ return;
+ }
+
+ 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;
+ }
+ r = runemalloc(cnt);
+ if(r == nil){
+ pair.ns = 0;
+ send(cwm.cw, &pair);
+ filsysrespond(x->fs, x, &fc, Enomem);
+ return;
+ }
+ x->f->nrpart = 0;
+ cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
+ /* approach end of buffer */
+ while(fullrune(x->data+nb, cnt-nb)){
+ nb += chartorune(&r[nr], x->data+nb);
+ if(r[nr])
+ nr++;
+ }
+ if(nb < cnt){
+ memmove(x->f->rpart, x->data+nb, cnt-nb);
+ x->f->nrpart = cnt-nb;
+ }
+
+ pair.s = r;
+ pair.ns = nr;
+ send(cwm.cw, &pair);
+ fc.count = x->count;
+ filsysrespond(x->fs, x, &fc, nil);
+ 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 == 0)
+ wsendctlmesg(w, Holdoff, ZR, nil);
+ break;
+ }
+ if(strncmp(x->data, "rawon", 5)==0){
+ if(w->holding){
+ w->holding = 0;
+ 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, TRUE);
+ break;
+
+ case Qlabel:
+ p = realloc(w->label, cnt+1);
+ if(p == nil){
+ filsysrespond(x->fs, x, &fc, Enomem);
+ return;
+ }
+ w->label = p;
+ w->label[cnt] = 0;
+ memmove(w->label, x->data, cnt);
+ 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:
+ if(cnt == 0)
+ break;
+ /* always append only */
+ if(ntsnarf > MAXSNARF){ /* avoid thrashing when people cut huge text */
+ filsysrespond(x->fs, x, &fc, Elong);
+ return;
+ }
+ p = realloc(tsnarf, ntsnarf+cnt+1); /* room for NUL */
+ if(p == nil){
+ filsysrespond(x->fs, x, &fc, Enomem);
+ return;
+ }
+ tsnarf = p;
+ 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 */
+ if(x->data[0] == '/'){
+ p = smprint("%.*s", cnt, x->data);
+ }else{
+ p = smprint("%s/%.*s", w->dir, cnt, x->data);
+ }
+ if(p == nil){
+ filsysrespond(x->fs, x, &fc, Enomem);
+ return;
+ }
+ free(w->dir);
+ w->dir = cleanname(p);
+ break;
+
+ case Qwctl:
+ if(writewctl(x, err) < 0){
+ filsysrespond(x->fs, x, &fc, err);
+ return;
+ }
+ break;
+
+ case Qdot:
+ filsysrespond(x->fs, x, &fc, Enope);
+ return;
+
+ case Qtap:
+ if(cnt < 2){
+ filsysrespond(x->fs, x, &fc, "malformed key");
+ return;
+ }
+ e = x->data + cnt;
+ for(p = x->data; p < e; p += strlen(p)+1){
+ switch(*p){
+ case '\0':
+ fc.count = p - x->data;
+ filsysrespond(x->fs, x, &fc, "null message type");
+ return;
+ case Tapfocus:
+ /* cleanup our own pollution */
+ break;
+ default:
+ chanprint(fromtap, "%s", p);
+ break;
+ }
+ }
+ break;
+
+ default:
+ fprint(2, "unknown qid %d in write\n", qid);
+ filsysrespond(x->fs, x, &fc, "unknown qid in write");
+ return;
+ }
+ fc.count = cnt;
+ filsysrespond(x->fs, x, &fc, nil);
+}
+
+int
+readwindow(Image *i, char *t, Rectangle r, int offset, int n)
+{
+ int ww, oo, y, m;
+ uchar *tt;
+
+ ww = bytesperline(r, i->depth);
+ r.min.y += offset/ww;
+ if(r.min.y >= r.max.y)
+ return 0;
+ y = r.min.y + (n + ww-1)/ww;
+ if(y < r.max.y)
+ r.max.y = y;
+ m = ww * Dy(r);
+ oo = offset % ww;
+ if(oo == 0 && n >= m)
+ return unloadimage(i, r, (uchar*)t, n);
+ if((tt = malloc(m)) == nil)
+ return -1;
+ m = unloadimage(i, r, tt, m) - oo;
+ if(m > 0){
+ if(n < m) m = n;
+ memmove(t, tt + oo, m);
+ }
+ free(tt);
+ return m;
+}
+
+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;
+ Stringpair pair;
+ enum { Adata, Agone, Aflush, Aend };
+ Alt alts[Aend+1];
+
+ w = x->f->w;
+ if(w != nil && w->deleted){
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ }
+ qid = FILE(x->f->qid);
+ off = x->offset;
+ cnt = x->count;
+ switch(qid){
+ case Qwctl:
+ if(w == nil){
+ if(off >= 6*12){
+ filsysrespond(x->fs, x, &fc, Etooshort);
+ return;
+ }
+ n = sprint(buf, "%11d %11d %11d %11d nowindow nowindow ",
+ screen->r.min.x, screen->r.min.y, screen->r.max.x, screen->r.max.y);
+ t = estrdup(buf);
+ goto Text;
+ }
+ if(cnt < 4*12){
+ filsysrespond(x->fs, x, &fc, Etooshort);
+ return;
+ }
+ alts[Adata].c = w->wctlread;
+ goto Consmesg;
+
+ case Qkbd:
+ alts[Adata].c = w->kbdread;
+ goto Consmesg;
+
+ case Qcons:
+ alts[Adata].c = w->consread;
+
+ Consmesg:
+ alts[Adata].v = &crm;
+ alts[Adata].op = CHANRCV;
+ alts[Agone].c = w->gone;
+ alts[Agone].v = nil;
+ alts[Agone].op = CHANRCV;
+ alts[Aflush].c = x->flushc;
+ alts[Aflush].v = nil;
+ alts[Aflush].op = CHANRCV;
+ alts[Aend].op = CHANEND;
+
+ switch(alt(alts)){
+ case Adata:
+ break;
+ case Agone:
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ case Aflush:
+ filsyscancel(x);
+ return;
+ }
+ c1 = crm.c1;
+ c2 = crm.c2;
+ t = emalloc(cnt+UTFmax+1); /* room to unpack partial rune plus */
+ pair.s = t;
+ pair.ns = cnt;
+ send(c1, &pair);
+ recv(c2, &pair);
+ fc.data = pair.s;
+ fc.count = min(cnt, pair.ns);
+ filsysrespond(x->fs, x, &fc, nil);
+ free(t);
+ break;
+
+ case Qtap:
+ alts[Adata].c = totap;
+ alts[Adata].v = &t;
+ alts[Adata].op = CHANRCV;
+ if(w != nil){
+ alts[Agone].c = w->gone;
+ alts[Agone].v = nil;
+ alts[Agone].op = CHANRCV;
+ } else
+ alts[Agone].op = CHANNOP;
+ alts[Aflush].c = x->flushc;
+ alts[Aflush].v = nil;
+ alts[Aflush].op = CHANRCV;
+ alts[Aend].op = CHANEND;
+
+ switch(alt(alts)){
+ case Adata:
+ break;
+ case Agone:
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ case Aflush:
+ filsyscancel(x);
+ return;
+ }
+ fc.data = t;
+ /* kbdproc ensures we're only dealing with one message */
+ fc.count = strlen(t)+1;
+ filsysrespond(x->fs, x, &fc, nil);
+ free(t);
+ 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:
+ alts[Adata].c = w->mouseread;
+ alts[Adata].v = &mrm;
+ alts[Adata].op = CHANRCV;
+ alts[Agone].c = w->gone;
+ alts[Agone].v = nil;
+ alts[Agone].op = CHANRCV;
+ alts[Aflush].c = x->flushc;
+ alts[Aflush].v = nil;
+ alts[Aflush].op = CHANRCV;
+ alts[Aend].op = CHANEND;
+
+ switch(alt(alts)){
+ case Adata:
+ break;
+ case Agone:
+ filsysrespond(x->fs, x, &fc, Edeleted);
+ return;
+ case Aflush:
+ filsyscancel(x);
+ return;
+ }
+
+ 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);
+ 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;
+
+ case Qdot:
+ /* assumption on mountpoint */
+ n = snprint(buf, sizeof buf, "/dev/wsys/%d/text:#%d,#%d", w->id, w->q0, w->q1);
+ t = estrdup(buf);
+ 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);
+ w->winnameread = TRUE;
+ goto Text;
+
+ case Qwindow:
+ i = w->i;
+ if(i == nil){
+ filsysrespond(x->fs, x, &fc, Enowindow);
+ return;
+ }
+ r = i->r;
+ goto caseImage;
+
+ case Qscreen:
+ i = screen;
+ r = screen->r;
+
+ caseImage:
+ if(off < 5*12){
+ n = sprint(buf, "%11s %11d %11d %11d %11d ",
+ chantostr(cbuf, i->chan),
+ r.min.x, r.min.y, r.max.x, r.max.y);
+ t = estrdup(buf);
+ goto Text;
+ }
+ off -= 5*12;
+ n = -1;
+ t = malloc(cnt);
+ if(t){
+ 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;
+
+ default:
+ fprint(2, "unknown qid %d in read\n", qid);
+ snprint(buf, sizeof(buf), "unknown qid in read");
+ filsysrespond(x->fs, x, &fc, buf);
+ break;
+ }
+}