Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"

#define MAXSNARF        100*1024

char Einuse[] =         "file in use";
char Edeleted[] =       "window deleted";
char Ebadreq[] =        "bad graphics request";
char Etooshort[] =      "buffer too small";
char Ebadtile[] =       "unknown tile";
char Eshort[] =         "short i/o request";
char Elong[] =          "snarf buffer too long";
char Eunkid[] =         "unknown id in attach";
char Ebadrect[] =       "bad rectangle in attach";
char Ewindow[] =        "cannot make window";
char Enowindow[] =      "window has no image";
char Ebadmouse[] =      "bad format on /dev/mouse";
char Ebadwrect[] =      "rectangle outside screen";
char Ebadoffset[] =     "window read not on scan line boundary";
extern char Eperm[];

static  Xfid    *xfidfree;
static  Xfid    *xfid;
static  Channel *cxfidalloc;    /* chan(Xfid*) */
static  Channel *cxfidfree;     /* chan(Xfid*) */

static  char    *tsnarf;
static  int     ntsnarf;

void
xfidallocthread(void*)
{
        Xfid *x;
        enum { Alloc, Free, N };
        static Alt alts[N+1];

        alts[Alloc].c = cxfidalloc;
        alts[Alloc].v = nil;
        alts[Alloc].op = CHANRCV;
        alts[Free].c = cxfidfree;
        alts[Free].v = &x;
        alts[Free].op = CHANRCV;
        alts[N].op = CHANEND;
        for(;;){
                switch(alt(alts)){
                case Alloc:
                        x = xfidfree;
                        if(x)
                                xfidfree = x->free;
                        else{
                                x = emalloc(sizeof(Xfid));
                                x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
                                x->flushc = chancreate(sizeof(int), 0); /* notification only; no data */
                                x->flushtag = -1;
                                x->next = xfid;
                                xfid = x;
                                threadcreate(xfidctl, x, 16384);
                        }
                        if(x->ref != 0){
                                fprint(2, "%p incref %ld\n", x, x->ref);
                                error("incref");
                        }
                        if(x->flushtag != -1)
                                error("flushtag in allocate");
                        incref(x);
                        sendp(cxfidalloc, x);
                        break;
                case Free:
                        if(x->ref != 0){
                                fprint(2, "%p decref %ld\n", x, x->ref);
                                error("decref");
                        }
                        if(x->flushtag != -1)
                                error("flushtag in free");
                        x->free = xfidfree;
                        xfidfree = x;
                        break;
                }
        }
}

Channel*
xfidinit(void)
{
        cxfidalloc = chancreate(sizeof(Xfid*), 0);
        cxfidfree = chancreate(sizeof(Xfid*), 0);
        threadcreate(xfidallocthread, nil, STACK);
        return cxfidalloc;
}

void
xfidctl(void *arg)
{
        Xfid *x;
        void (*f)(Xfid*);
        char buf[64];

        x = arg;
        snprint(buf, sizeof buf, "xfid.%p", x);
        threadsetname(buf);
        for(;;){
                f = recvp(x->c);
                (*f)(x);
                if(decref(x) == 0)
                        sendp(cxfidfree, x);
        }
}

void
xfidflush(Xfid *x)
{
        Fcall t;
        Xfid *xf;

        for(xf=xfid; xf; xf=xf->next)
                if(xf->flushtag == x->oldtag){
                        xf->flushtag = -1;
                        xf->flushing = TRUE;
                        incref(xf);     /* to hold data structures up at tail of synchronization */
                        if(xf->ref == 1)
                                error("ref 1 in flush");
                        if(canqlock(&xf->active)){
                                qunlock(&xf->active);
                                sendul(xf->flushc, 0);
                        }else{
                                qlock(&xf->active);     /* wait for him to finish */
                                qunlock(&xf->active);
                        }
                        xf->flushing = FALSE;
                        if(decref(xf) == 0)
                                sendp(cxfidfree, xf);
                        break;
                }
        filsysrespond(x->fs, x, &t, nil);
}

void
xfidattach(Xfid *x)
{
        Fcall t;
        int id, hideit, scrollit;
        Window *w;
        char *err, *n, *dir, errbuf[ERRMAX];
        int pid, newlymade;
        Rectangle r;
        Image *i;

        t.qid = x->f->qid;
        qlock(&all);
        w = nil;
        err = Eunkid;
        newlymade = FALSE;
        hideit = 0;

        if(x->aname[0] == 'N'){ /* N 100,100, 200, 200 - old syntax */
                n = x->aname+1;
                pid = strtoul(n, &n, 0);
                if(*n == ',')
                        n++;
                r.min.x = strtoul(n, &n, 0);
                if(*n == ',')
                        n++;
                r.min.y = strtoul(n, &n, 0);
                if(*n == ',')
                        n++;
                r.max.x = strtoul(n, &n, 0);
                if(*n == ',')
                        n++;
                r.max.y = strtoul(n, &n, 0);
  Allocate:
                if(!goodrect(r))
                        err = Ebadrect;
                else{
                        if(hideit)
                                i = allocimage(display, r, screen->chan, 0, DWhite);
                        else
                                i = allocwindow(wscreen, r, Refbackup, DWhite);
                        if(i){
                                border(i, r, Selborder, display->black, ZP);
                                if(pid == 0)
                                        pid = -1;       /* make sure we don't pop a shell! - UGH */
                                w = new(i, hideit, scrolling, pid, nil, nil, nil);
                                flushimage(display, 1);
                                newlymade = TRUE;
                        }else
                                err = Ewindow;
                }
        }else if(strncmp(x->aname, "new", 3) == 0){     /* new -dx -dy - new syntax, as in wctl */
                pid = 0;
                if(parsewctl(nil, ZR, &r, &pid, nil, &hideit, &scrollit, &dir, x->aname, errbuf) < 0)
                        err = errbuf;
                else
                        goto Allocate;
        }else{
                id = atoi(x->aname);
                w = wlookid(id);
        }
        x->f->w = w;
        if(w == nil){
                qunlock(&all);
                x->f->busy = FALSE;
                filsysrespond(x->fs, x, &t, err);
                return;
        }
        if(!newlymade)  /* counteract dec() in winshell() */
                incref(w);
        qunlock(&all);
        filsysrespond(x->fs, x, &t, nil);
}

void
xfidopen(Xfid *x)
{
        Fcall t;
        Window *w;

        w = x->f->w;
        if(w->deleted){
                filsysrespond(x->fs, x, &t, Edeleted);
                return;
        }
        switch(FILE(x->f->qid)){
        case Qconsctl:
                if(w->ctlopen){
                        filsysrespond(x->fs, x, &t, Einuse);
                        return;
                }
                w->ctlopen = TRUE;
                break;
        case Qkbdin:
                if(w !=  wkeyboard){
                        filsysrespond(x->fs, x, &t, Eperm);
                        return;
                }
                break;
        case Qmouse:
                if(w->mouseopen){
                        filsysrespond(x->fs, x, &t, Einuse);
                        return;
                }
                /*
                 * Reshaped: there's a race if the appl. opens the
                 * window, is resized, and then opens the mouse,
                 * but that's rare.  The alternative is to generate
                 * a resized event every time a new program starts
                 * up in a window that has been resized since the
                 * dawn of time.  We choose the lesser evil.
                 */
                w->resized = FALSE;
                w->mouseopen = TRUE;
                break;
        case Qsnarf:
                if(x->mode==ORDWR || x->mode==OWRITE){
                        if(tsnarf)
                                free(tsnarf);   /* collision, but OK */
                        ntsnarf = 0;
                        tsnarf = malloc(1);
                }
                break;
        case Qwctl:
                if(x->mode==OREAD || x->mode==ORDWR){
                        /*
                         * It would be much nicer to implement fan-out for wctl reads,
                         * so multiple people can see the resizings, but rio just isn't
                         * structured for that.  It's structured for /dev/cons, which gives
                         * alternate data to alternate readers.  So to keep things sane for
                         * wctl, we compromise and give an error if two people try to
                         * open it.  Apologies.
                         */
                        if(w->wctlopen){
                                filsysrespond(x->fs, x, &t, Einuse);
                                return;
                        }
                        w->wctlopen = TRUE;
                        w->wctlready = 1;
                        wsendctlmesg(w, Wakeup, ZR, nil);
                }
                break;
        }
        t.qid = x->f->qid;
        t.iounit = messagesize-IOHDRSZ;
        x->f->open = TRUE;
        x->f->mode = x->mode;
        filsysrespond(x->fs, x, &t, nil);
}

void
xfidclose(Xfid *x)
{
        Fcall t;
        Window *w;
        int nb, nulls;

        w = x->f->w;
        switch(FILE(x->f->qid)){
        case Qconsctl:
                if(w->rawing){
                        w->rawing = FALSE;
                        wsendctlmesg(w, Rawoff, ZR, nil);
                }
                if(w->holding){
                        w->holding = FALSE;
                        wsendctlmesg(w, Holdoff, ZR, nil);
                }
                w->ctlopen = FALSE;
                break;
        case Qcursor:
                w->cursorp = nil;
                wsetcursor(w, FALSE);
                break;
        case Qmouse:
                w->resized = FALSE;
                w->mouseopen = FALSE;
                if(w->i != nil)
                        wsendctlmesg(w, Refresh, w->i->r, nil);
                break;
        /* odd behavior but really ok: replace snarf buffer when /dev/snarf is closed */
        case Qsnarf:
                if(x->f->mode==ORDWR || x->f->mode==OWRITE){
                        snarf = runerealloc(snarf, ntsnarf+1);
                        cvttorunes(tsnarf, ntsnarf, snarf, &nb, &nsnarf, &nulls);
                        free(tsnarf);
                        tsnarf = nil;
                        ntsnarf = 0;
                }
                break;
        case Qwctl:
                if(x->f->mode==OREAD || x->f->mode==ORDWR)
                        w->wctlopen = FALSE;
                break;
        }
        wclose(w);
        filsysrespond(x->fs, x, &t, nil);
}

void
xfidwrite(Xfid *x)
{
        Fcall fc;
        int c, cnt, qid, nb, off, nr;
        char buf[256], *p;
        Point pt;
        Window *w;
        Rune *r;
        Conswritemesg cwm;
        Stringpair pair;
        enum { CWdata, CWflush, NCW };
        Alt alts[NCW+1];

        w = x->f->w;
        if(w->deleted){
                filsysrespond(x->fs, x, &fc, Edeleted);
                return;
        }
        qid = FILE(x->f->qid);
        cnt = x->count;
        off = x->offset;
        x->data[cnt] = 0;
        switch(qid){
        case Qcons:
                nr = x->f->nrpart;
                if(nr > 0){
                        memmove(x->data+nr, x->data, cnt);      /* there's room: see malloc in filsysproc */
                        memmove(x->data, x->f->rpart, nr);
                        cnt += nr;
                        x->f->nrpart = 0;
                }
                r = runemalloc(cnt);
                cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
                /* approach end of buffer */
                while(fullrune(x->data+nb, cnt-nb)){
                        c = nb;
                        nb += chartorune(&r[nr], x->data+c);
                        if(r[nr])
                                nr++;
                }
                if(nb < cnt){
                        memmove(x->f->rpart, x->data+nb, cnt-nb);
                        x->f->nrpart = cnt-nb;
                }
                x->flushtag = x->tag;

                alts[CWdata].c = w->conswrite;
                alts[CWdata].v = &cwm;
                alts[CWdata].op = CHANRCV;
                alts[CWflush].c = x->flushc;
                alts[CWflush].v = nil;
                alts[CWflush].op = CHANRCV;
                alts[NCW].op = CHANEND;
        
                switch(alt(alts)){
                case CWdata:
                        break;
                case CWflush:
                        filsyscancel(x);
                        return;
                }

                /* received data */
                x->flushtag = -1;
                if(x->flushing){
                        recv(x->flushc, nil);   /* wake up flushing xfid */
                        pair.s = runemalloc(1);
                        pair.ns = 0;
                        send(cwm.cw, &pair);            /* wake up window with empty data */
                        filsyscancel(x);
                        return;
                }
                qlock(&x->active);
                pair.s = r;
                pair.ns = nr;
                send(cwm.cw, &pair);
                fc.count = x->count;
                filsysrespond(x->fs, x, &fc, nil);
                qunlock(&x->active);
                return;

        case Qconsctl:
                if(strncmp(x->data, "holdon", 6)==0){
                        if(w->holding++ == 0)
                                wsendctlmesg(w, Holdon, ZR, nil);
                        break;
                }
                if(strncmp(x->data, "holdoff", 7)==0 && w->holding){
                        if(--w->holding == FALSE)
                                wsendctlmesg(w, Holdoff, ZR, nil);
                        break;
                }
                if(strncmp(x->data, "rawon", 5)==0){
                        if(w->holding){
                                w->holding = FALSE;
                                wsendctlmesg(w, Holdoff, ZR, nil);
                        }
                        if(w->rawing++ == 0)
                                wsendctlmesg(w, Rawon, ZR, nil);
                        break;
                }
                if(strncmp(x->data, "rawoff", 6)==0 && w->rawing){
                        if(--w->rawing == 0)
                                wsendctlmesg(w, Rawoff, ZR, nil);
                        break;
                }
                filsysrespond(x->fs, x, &fc, "unknown control message");
                return;

        case Qcursor:
                if(cnt < 2*4+2*2*16)
                        w->cursorp = nil;
                else{
                        w->cursor.offset.x = BGLONG(x->data+0*4);
                        w->cursor.offset.y = BGLONG(x->data+1*4);
                        memmove(w->cursor.clr, x->data+2*4, 2*2*16);
                        w->cursorp = &w->cursor;
                }
                wsetcursor(w, !sweeping);
                break;

        case Qlabel:
                if(off != 0){
                        filsysrespond(x->fs, x, &fc, "non-zero offset writing label");
                        return;
                }
                free(w->label);
                w->label = emalloc(cnt+1);
                memmove(w->label, x->data, cnt);
                w->label[cnt] = 0;
                break;

        case Qmouse:
                if(w!=input || Dx(w->screenr)<=0)
                        break;
                if(x->data[0] != 'm'){
                        filsysrespond(x->fs, x, &fc, Ebadmouse);
                        return;
                }
                p = nil;
                pt.x = strtoul(x->data+1, &p, 0);
                if(p == nil){
                        filsysrespond(x->fs, x, &fc, Eshort);
                        return;
                }
                pt.y = strtoul(p, nil, 0);
                if(w==input && wpointto(mouse->xy)==w)
                        wsendctlmesg(w, Movemouse, Rpt(pt, pt), nil);
                break;

        case Qsnarf:
                /* always append only */
                if(ntsnarf > MAXSNARF){ /* avoid thrashing when people cut huge text */
                        filsysrespond(x->fs, x, &fc, Elong);
                        return;
                }
                tsnarf = erealloc(tsnarf, ntsnarf+cnt+1);       /* room for NUL */
                memmove(tsnarf+ntsnarf, x->data, cnt);
                ntsnarf += cnt;
                snarfversion++;
                break;

        case Qwdir:
                if(cnt == 0)
                        break;
                if(x->data[cnt-1] == '\n'){
                        if(cnt == 1)
                                break;
                        x->data[cnt-1] = '\0';
                }
                /* assume data comes in a single write */
                /*
                  * Problem: programs like dossrv, ftp produce illegal UTF;
                  * we must cope by converting it first.
                  */
                snprint(buf, sizeof buf, "%.*s", cnt, x->data);
                if(buf[0] == '/'){
                        free(w->dir);
                        w->dir = estrdup(buf);
                }else{
                        p = emalloc(strlen(w->dir) + 1 + strlen(buf) + 1);
                        sprint(p, "%s/%s", w->dir, buf);
                        free(w->dir);
                        w->dir = cleanname(p);
                }
                break;

        case Qkbdin:
                keyboardsend(x->data, cnt);
                break;

        case Qwctl:
                if(writewctl(x, buf) < 0){
                        filsysrespond(x->fs, x, &fc, buf);
                        return;
                }
                flushimage(display, 1);
                break;

        default:
                fprint(2, buf, "unknown qid %d in write\n", qid);
                sprint(buf, "unknown qid in write");
                filsysrespond(x->fs, x, &fc, buf);
                return;
        }
        fc.count = cnt;
        filsysrespond(x->fs, x, &fc, nil);
}

int
readwindow(Image *i, char *t, Rectangle r, int offset, int n)
{
        int ww, y;

        offset -= 5*12;
        ww = bytesperline(r, screen->depth);
        r.min.y += offset/ww;
        if(r.min.y >= r.max.y)
                return 0;
        y = r.min.y + n/ww;
        if(y < r.max.y)
                r.max.y = y;
        if(r.max.y <= r.min.y)
                return 0;
        return unloadimage(i, r, (uchar*)t, n);
}

void
xfidread(Xfid *x)
{
        Fcall fc;
        int n, off, cnt, c;
        uint qid;
        char buf[128], *t;
        char cbuf[30];
        Window *w;
        Mouse ms;
        Rectangle r;
        Image *i;
        Channel *c1, *c2;       /* chan (tuple(char*, int)) */
        Consreadmesg crm;
        Mousereadmesg mrm;
        Consreadmesg cwrm;
        Stringpair pair;
        enum { CRdata, CRflush, NCR };
        enum { MRdata, MRflush, NMR };
        enum { WCRdata, WCRflush, NWCR };
        Alt alts[NCR+1];

        w = x->f->w;
        if(w->deleted){
                filsysrespond(x->fs, x, &fc, Edeleted);
                return;
        }
        qid = FILE(x->f->qid);
        off = x->offset;
        cnt = x->count;
        switch(qid){
        case Qcons:
                x->flushtag = x->tag;

                alts[CRdata].c = w->consread;
                alts[CRdata].v = &crm;
                alts[CRdata].op = CHANRCV;
                alts[CRflush].c = x->flushc;
                alts[CRflush].v = nil;
                alts[CRflush].op = CHANRCV;
                alts[NMR].op = CHANEND;

                switch(alt(alts)){
                case CRdata:
                        break;
                case CRflush:
                        filsyscancel(x);
                        return;
                }

                /* received data */
                x->flushtag = -1;
                c1 = crm.c1;
                c2 = crm.c2;
                t = malloc(cnt+UTFmax+1);       /* room to unpack partial rune plus */
                pair.s = t;
                pair.ns = cnt;
                send(c1, &pair);
                if(x->flushing){
                        recv(x->flushc, nil);   /* wake up flushing xfid */
                        recv(c2, nil);                  /* wake up window and toss data */
                        free(t);
                        filsyscancel(x);
                        return;
                }
                qlock(&x->active);
                recv(c2, &pair);
                fc.data = pair.s;
                fc.count = pair.ns;
                filsysrespond(x->fs, x, &fc, nil);
                free(t);
                qunlock(&x->active);
                break;

        case Qlabel:
                n = strlen(w->label);
                if(off > n)
                        off = n;
                if(off+cnt > n)
                        cnt = n-off;
                fc.data = w->label+off;
                fc.count = cnt;
                filsysrespond(x->fs, x, &fc, nil);
                break;

        case Qmouse:
                x->flushtag = x->tag;

                alts[MRdata].c = w->mouseread;
                alts[MRdata].v = &mrm;
                alts[MRdata].op = CHANRCV;
                alts[MRflush].c = x->flushc;
                alts[MRflush].v = nil;
                alts[MRflush].op = CHANRCV;
                alts[NMR].op = CHANEND;

                switch(alt(alts)){
                case MRdata:
                        break;
                case MRflush:
                        filsyscancel(x);
                        return;
                }

                /* received data */
                x->flushtag = -1;
                if(x->flushing){
                        recv(x->flushc, nil);           /* wake up flushing xfid */
                        recv(mrm.cm, nil);                      /* wake up window and toss data */
                        filsyscancel(x);
                        return;
                }
                qlock(&x->active);
                recv(mrm.cm, &ms);
                c = 'm';
                if(w->resized)
                        c = 'r';
                n = sprint(buf, "%c%11d %11d %11d %11ld ", c, ms.xy.x, ms.xy.y, ms.buttons, ms.msec);
                w->resized = 0;
                fc.data = buf;
                fc.count = min(n, cnt);
                filsysrespond(x->fs, x, &fc, nil);
                qunlock(&x->active);
                break;

        case Qcursor:
                filsysrespond(x->fs, x, &fc, "cursor read not implemented");
                break;

        /* The algorithm for snarf and text is expensive but easy and rarely used */
        case Qsnarf:
                getsnarf();
                if(nsnarf)
                        t = runetobyte(snarf, nsnarf, &n);
                else {
                        t = nil;
                        n = 0;
                }
                goto Text;

        case Qtext:
                t = wcontents(w, &n);
                goto Text;

        Text:
                if(off > n){
                        off = n;
                        cnt = 0;
                }
                if(off+cnt > n)
                        cnt = n-off;
                fc.data = t+off;
                fc.count = cnt;
                filsysrespond(x->fs, x, &fc, nil);
                free(t);
                break;

        case Qwdir:
                t = estrdup(w->dir);
                n = strlen(t);
                goto Text;

        case Qwinid:
                n = sprint(buf, "%11d ", w->id);
                t = estrdup(buf);
                goto Text;


        case Qwinname:
                n = strlen(w->name);
                if(n == 0){
                        filsysrespond(x->fs, x, &fc, "window has no name");
                        break;
                }
                t = estrdup(w->name);
                goto Text;

        case Qwindow:
                i = w->i;
                if(i == nil || Dx(w->screenr)<=0){
                        filsysrespond(x->fs, x, &fc, Enowindow);
                        return;
                }
                r = w->screenr;
                goto caseImage;

        case Qscreen:
                i = display->image;
                if(i == nil){
                        filsysrespond(x->fs, x, &fc, "no top-level screen");
                        break;
                }
                r = i->r;
                /* fall through */

        caseImage:
                if(off < 5*12){
                        n = sprint(buf, "%11s %11d %11d %11d %11d ",
                                chantostr(cbuf, screen->chan),
                                i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y);
                        t = estrdup(buf);
                        goto Text;
                }
                t = malloc(cnt);
                fc.data = t;
                n = readwindow(i, t, r, off, cnt);      /* careful; fc.count is unsigned */
                if(n < 0){
                        buf[0] = 0;
                        errstr(buf, sizeof buf);
                        filsysrespond(x->fs, x, &fc, buf);
                }else{
                        fc.count = n;
                        filsysrespond(x->fs, x, &fc, nil);
                }
                free(t);
                return;

        case Qwctl:     /* read returns rectangle, hangs if not resized */
                if(cnt < 4*12){
                        filsysrespond(x->fs, x, &fc, Etooshort);
                        break;
                }
                x->flushtag = x->tag;

                alts[WCRdata].c = w->wctlread;
                alts[WCRdata].v = &cwrm;
                alts[WCRdata].op = CHANRCV;
                alts[WCRflush].c = x->flushc;
                alts[WCRflush].v = nil;
                alts[WCRflush].op = CHANRCV;
                alts[NMR].op = CHANEND;

                switch(alt(alts)){
                case WCRdata:
                        break;
                case WCRflush:
                        filsyscancel(x);
                        return;
                }

                /* received data */
                x->flushtag = -1;
                c1 = cwrm.c1;
                c2 = cwrm.c2;
                t = malloc(cnt+1);      /* be sure to have room for NUL */
                pair.s = t;
                pair.ns = cnt+1;
                send(c1, &pair);
                if(x->flushing){
                        recv(x->flushc, nil);   /* wake up flushing xfid */
                        recv(c2, nil);                  /* wake up window and toss data */
                        free(t);
                        filsyscancel(x);
                        return;
                }
                qlock(&x->active);
                recv(c2, &pair);
                fc.data = pair.s;
                if(pair.ns > cnt)
                        pair.ns = cnt;
                fc.count = pair.ns;
                filsysrespond(x->fs, x, &fc, nil);
                free(t);
                qunlock(&x->active);
                break;

        default:
                fprint(2, "unknown qid %d in read\n", qid);
                sprint(buf, "unknown qid in read");
                filsysrespond(x->fs, x, &fc, buf);
                break;
        }
}