Subversion Repositories planix.SVN

Rev

Go to most recent revision | 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"
        /* for generating syms in mkfile only: */
        #include <bio.h>
        #include "edit.h"

void    mousethread(void*);
void    keyboardthread(void*);
void    waitthread(void*);
void    xfidallocthread(void*);
void    newwindowthread(void*);
void plumbproc(void*);

Reffont **fontcache;
int             nfontcache;
char            wdir[512] = ".";
Reffont *reffonts[2];
int             snarffd = -1;
int             mainpid;
int             plumbsendfd;
int             plumbeditfd;

enum{
        NSnarf = 1000   /* less than 1024, I/O buffer size */
};
Rune    snarfrune[NSnarf+1];

char            *fontnames[2] =
{
        "/lib/font/bit/lucidasans/euro.8.font",
        "/lib/font/bit/lucm/unicode.9.font"
};

Command *command;

void    acmeerrorinit(void);
void    readfile(Column*, char*);
int     shutdown(void*, char*);

void
derror(Display*, char *errorstr)
{
        error(errorstr);
}

void
threadmain(int argc, char *argv[])
{
        int i;
        char *p, *loadfile;
        char buf[256];
        Column *c;
        int ncol;
        Display *d;
        static void *arg[1];

        rfork(RFENVG|RFNAMEG);

        ncol = -1;

        loadfile = nil;
        ARGBEGIN{
        case 'a':
                globalautoindent = TRUE;
                break;
        case 'b':
                bartflag = TRUE;
                break;
        case 'c':
                p = ARGF();
                if(p == nil)
                        goto Usage;
                ncol = atoi(p);
                if(ncol <= 0)
                        goto Usage;
                break;
        case 'f':
                fontnames[0] = ARGF();
                if(fontnames[0] == nil)
                        goto Usage;
                break;
        case 'F':
                fontnames[1] = ARGF();
                if(fontnames[1] == nil)
                        goto Usage;
                break;
        case 'l':
                loadfile = ARGF();
                if(loadfile == nil)
                        goto Usage;
                break;
        default:
        Usage:
                fprint(2, "usage: acme [-ab] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
                exits("usage");
        }ARGEND

        fontnames[0] = estrdup(fontnames[0]);
        fontnames[1] = estrdup(fontnames[1]);

        quotefmtinstall();
        cputype = getenv("cputype");
        objtype = getenv("objtype");
        home = getenv("home");
        p = getenv("tabstop");
        if(p != nil){
                maxtab = strtoul(p, nil, 0);
                free(p);
        }
        if(maxtab == 0)
                maxtab = 4; 
        if(loadfile)
                rowloadfonts(loadfile);
        putenv("font", fontnames[0]);
        snarffd = open("/dev/snarf", OREAD|OCEXEC);
        if(cputype){
                sprint(buf, "/acme/bin/%s", cputype);
                bind(buf, "/bin", MBEFORE);
        }
        bind("/acme/bin", "/bin", MBEFORE);
        getwd(wdir, sizeof wdir);

        if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
                fprint(2, "acme: can't open display: %r\n");
                exits("geninitdraw");
        }
        d = display;
        font = d->defaultfont;

        reffont.f = font;
        reffonts[0] = &reffont;
        incref(&reffont);       /* one to hold up 'font' variable */
        incref(&reffont);       /* one to hold up reffonts[0] */
        fontcache = emalloc(sizeof(Reffont*));
        nfontcache = 1;
        fontcache[0] = &reffont;

        iconinit();
        timerinit();
        rxinit();

        cwait = threadwaitchan();
        ccommand = chancreate(sizeof(Command**), 0);
        ckill = chancreate(sizeof(Rune*), 0);
        cxfidalloc = chancreate(sizeof(Xfid*), 0);
        cxfidfree = chancreate(sizeof(Xfid*), 0);
        cnewwindow = chancreate(sizeof(Channel*), 0);
        cerr = chancreate(sizeof(char*), 0);
        cedit = chancreate(sizeof(int), 0);
        cexit = chancreate(sizeof(int), 0);
        cwarn = chancreate(sizeof(void*), 1);
        if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
                fprint(2, "acme: can't create initial channels: %r\n");
                exits("channels");
        }

        mousectl = initmouse(nil, screen);
        if(mousectl == nil){
                fprint(2, "acme: can't initialize mouse: %r\n");
                exits("mouse");
        }
        mouse = mousectl;
        keyboardctl = initkeyboard(nil);
        if(keyboardctl == nil){
                fprint(2, "acme: can't initialize keyboard: %r\n");
                exits("keyboard");
        }
        mainpid = getpid();
        plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
        if(plumbeditfd >= 0){
                cplumb = chancreate(sizeof(Plumbmsg*), 0);
                proccreate(plumbproc, nil, STACK);
        }
        plumbsendfd = plumbopen("send", OWRITE|OCEXEC);

        fsysinit();

        #define WPERCOL 8
        disk = diskinit();
        if(!loadfile || !rowload(&row, loadfile, TRUE)){
                rowinit(&row, screen->clipr);
                if(ncol < 0){
                        if(argc == 0)
                                ncol = 2;
                        else{
                                ncol = (argc+(WPERCOL-1))/WPERCOL;
                                if(ncol < 2)
                                        ncol = 2;
                        }
                }
                if(ncol == 0)
                        ncol = 2;
                for(i=0; i<ncol; i++){
                        c = rowadd(&row, nil, -1);
                        if(c==nil && i==0)
                                error("initializing columns");
                }
                c = row.col[row.ncol-1];
                if(argc == 0)
                        readfile(c, wdir);
                else
                        for(i=0; i<argc; i++){
                                p = utfrrune(argv[i], '/');
                                if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
                                        readfile(c, argv[i]);
                                else
                                        readfile(row.col[i/WPERCOL], argv[i]);
                        }
        }
        flushimage(display, 1);

        acmeerrorinit();
        threadcreate(keyboardthread, nil, STACK);
        threadcreate(mousethread, nil, STACK);
        threadcreate(waitthread, nil, STACK);
        threadcreate(xfidallocthread, nil, STACK);
        threadcreate(newwindowthread, nil, STACK);

        threadnotify(shutdown, 1);
        recvul(cexit);
        killprocs();
        threadexitsall(nil);
}

void
readfile(Column *c, char *s)
{
        Window *w;
        Rune rb[256];
        int nb, nr;
        Runestr rs;

        w = coladd(c, nil, nil, -1);
        cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
        rs = cleanrname((Runestr){rb, nr});
        winsetname(w, rs.r, rs.nr);
        textload(&w->body, 0, s, 1);
        w->body.file->mod = FALSE;
        w->dirty = FALSE;
        winsettag(w);
        textscrdraw(&w->body);
        textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
}

char *oknotes[] ={
        "delete",
        "hangup",
        "kill",
        "exit",
        nil
};

int     dumping;

int
shutdown(void*, char *msg)
{
        int i;

        killprocs();
        if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
                dumping = TRUE;
                rowdump(&row, nil);
        }
        for(i=0; oknotes[i]; i++)
                if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
                        threadexitsall(msg);
        print("acme: %s\n", msg);
        abort();
        return 0;
}

void
killprocs(void)
{
        Command *c;

        fsysclose();
//      if(display)
//              flushimage(display, 1);

        for(c=command; c; c=c->next)
                postnote(PNGROUP, c->pid, "hangup");
        remove(acmeerrorfile);
}

static int errorfd;

void
acmeerrorproc(void *)
{
        char *buf;
        int n;

        threadsetname("acmeerrorproc");
        buf = emalloc(8192+1);
        while((n=read(errorfd, buf, 8192)) >= 0){
                buf[n] = '\0';
                sendp(cerr, estrdup(buf));
        }
}

void
acmeerrorinit(void)
{
        int fd, pfd[2];
        char buf[64];

        if(pipe(pfd) < 0)
                error("can't create pipe");
        sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
        fd = create(acmeerrorfile, OWRITE, 0666);
        if(fd < 0){
                remove(acmeerrorfile);
                fd = create(acmeerrorfile, OWRITE, 0666);
                if(fd < 0)
                        error("can't create acmeerror file");
        }
        sprint(buf, "%d", pfd[0]);
        write(fd, buf, strlen(buf));
        close(fd);
        /* reopen pfd[1] close on exec */
        sprint(buf, "/fd/%d", pfd[1]);
        errorfd = open(buf, OREAD|OCEXEC);
        if(errorfd < 0)
                error("can't re-open acmeerror file");
        close(pfd[1]);
        close(pfd[0]);
        proccreate(acmeerrorproc, nil, STACK);
}

void
plumbproc(void *)
{
        Plumbmsg *m;

        threadsetname("plumbproc");
        for(;;){
                m = plumbrecv(plumbeditfd);
                if(m == nil)
                        threadexits(nil);
                sendp(cplumb, m);
        }
}

void
keyboardthread(void *)
{
        Rune r;
        Timer *timer;
        Text *t;
        enum { KTimer, KKey, NKALT };
        static Alt alts[NKALT+1];

        alts[KTimer].c = nil;
        alts[KTimer].v = nil;
        alts[KTimer].op = CHANNOP;
        alts[KKey].c = keyboardctl->c;
        alts[KKey].v = &r;
        alts[KKey].op = CHANRCV;
        alts[NKALT].op = CHANEND;

        timer = nil;
        typetext = nil;
        threadsetname("keyboardthread");
        for(;;){
                switch(alt(alts)){
                case KTimer:
                        timerstop(timer);
                        t = typetext;
                        if(t!=nil && t->what==Tag){
                                winlock(t->w, 'K');
                                wincommit(t->w, t);
                                winunlock(t->w);
                                flushimage(display, 1);
                        }
                        alts[KTimer].c = nil;
                        alts[KTimer].op = CHANNOP;
                        break;
                case KKey:
                casekeyboard:
                        typetext = rowtype(&row, r, mouse->xy);
                        t = typetext;
                        if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright))       /* scrolling doesn't change activecol */
                                activecol = t->col;
                        if(t!=nil && t->w!=nil)
                                t->w->body.file->curtext = &t->w->body;
                        if(timer != nil)
                                timercancel(timer);
                        if(t!=nil && t->what==Tag) {
                                timer = timerstart(500);
                                alts[KTimer].c = timer->c;
                                alts[KTimer].op = CHANRCV;
                        }else{
                                timer = nil;
                                alts[KTimer].c = nil;
                                alts[KTimer].op = CHANNOP;
                        }
                        if(nbrecv(keyboardctl->c, &r) > 0)
                                goto casekeyboard;
                        flushimage(display, 1);
                        break;
                }
        }
}

void
mousethread(void *)
{
        Text *t, *argt;
        int but;
        uint q0, q1;
        Window *w;
        Plumbmsg *pm;
        Mouse m;
        char *act;
        enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
        static Alt alts[NMALT+1];

        threadsetname("mousethread");
        alts[MResize].c = mousectl->resizec;
        alts[MResize].v = nil;
        alts[MResize].op = CHANRCV;
        alts[MMouse].c = mousectl->c;
        alts[MMouse].v = &mousectl->Mouse;
        alts[MMouse].op = CHANRCV;
        alts[MPlumb].c = cplumb;
        alts[MPlumb].v = &pm;
        alts[MPlumb].op = CHANRCV;
        alts[MWarnings].c = cwarn;
        alts[MWarnings].v = nil;
        alts[MWarnings].op = CHANRCV;
        if(cplumb == nil)
                alts[MPlumb].op = CHANNOP;
        alts[NMALT].op = CHANEND;
        
        for(;;){
                qlock(&row);
                flushwarnings();
                qunlock(&row);
                flushimage(display, 1);
                switch(alt(alts)){
                case MResize:
                        if(getwindow(display, Refnone) < 0)
                                error("attach to window");
                        scrlresize();
                        rowresize(&row, screen->clipr);
                        break;
                case MPlumb:
                        if(strcmp(pm->type, "text") == 0){
                                act = plumblookup(pm->attr, "action");
                                if(act==nil || strcmp(act, "showfile")==0)
                                        plumblook(pm);
                                else if(strcmp(act, "showdata")==0)
                                        plumbshow(pm);
                        }
                        plumbfree(pm);
                        break;
                case MWarnings:
                        break;
                case MMouse:
                        /*
                         * Make a copy so decisions are consistent; mousectl changes
                         * underfoot.  Can't just receive into m because this introduces
                         * another race; see /sys/src/libdraw/mouse.c.
                         */
                        m = mousectl->Mouse;
                        qlock(&row);
                        t = rowwhich(&row, m.xy);
                        if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
                                winlock(mousetext->w, 'M');
                                mousetext->eq0 = ~0;
                                wincommit(mousetext->w, mousetext);
                                winunlock(mousetext->w);
                        }
                        mousetext = t;
                        if(t == nil)
                                goto Continue;
                        w = t->w;
                        if(t==nil || m.buttons==0)
                                goto Continue;
                        but = 0;
                        if(m.buttons == 1)
                                but = 1;
                        else if(m.buttons == 2)
                                but = 2;
                        else if(m.buttons == 4)
                                but = 3;
                        barttext = t;
                        if(t->what==Body && ptinrect(m.xy, t->scrollr)){
                                if(but){
                                        winlock(w, 'M');
                                        t->eq0 = ~0;
                                        textscroll(t, but);
                                        winunlock(w);
                                }
                                goto Continue;
                        }
                        /* scroll buttons, wheels, etc. */
                        if(t->what==Body && w != nil && (m.buttons & (8|16))){
                                if(m.buttons & 8)
                                        but = Kscrolloneup;
                                else
                                        but = Kscrollonedown;
                                winlock(w, 'M');
                                t->eq0 = ~0;
                                texttype(t, but);
                                winunlock(w);
                                goto Continue;
                        }
                        if(ptinrect(m.xy, t->scrollr)){
                                if(but){
                                        if(t->what == Columntag)
                                                rowdragcol(&row, t->col, but);
                                        else if(t->what == Tag){
                                                coldragwin(t->col, t->w, but);
                                                if(t->w)
                                                        barttext = &t->w->body;
                                        }
                                        if(t->col)
                                                activecol = t->col;
                                }
                                goto Continue;
                        }
                        if(m.buttons){
                                if(w)
                                        winlock(w, 'M');
                                t->eq0 = ~0;
                                if(w)
                                        wincommit(w, t);
                                else
                                        textcommit(t, TRUE);
                                if(m.buttons & 1){
                                        textselect(t);
                                        if(w)
                                                winsettag(w);
                                        argtext = t;
                                        seltext = t;
                                        if(t->col)
                                                activecol = t->col;     /* button 1 only */
                                        if(t->w!=nil && t==&t->w->body)
                                                activewin = t->w;
                                }else if(m.buttons & 2){
                                        if(textselect2(t, &q0, &q1, &argt))
                                                execute(t, q0, q1, FALSE, argt);
                                }else if(m.buttons & 4){
                                        if(textselect3(t, &q0, &q1))
                                                look3(t, q0, q1, FALSE);
                                }
                                if(w)
                                        winunlock(w);
                                goto Continue;
                        }
    Continue:
                        qunlock(&row);
                        break;
                }
        }
}

/*
 * There is a race between process exiting and our finding out it was ever created.
 * This structure keeps a list of processes that have exited we haven't heard of.
 */
typedef struct Pid Pid;
struct Pid
{
        int     pid;
        char    msg[ERRMAX];
        Pid     *next;
};

void
waitthread(void *)
{
        Waitmsg *w;
        Command *c, *lc;
        uint pid;
        int found, ncmd;
        Rune *cmd;
        char *err;
        Text *t;
        Pid *pids, *p, *lastp;
        enum { WErr, WKill, WWait, WCmd, NWALT };
        Alt alts[NWALT+1];

        threadsetname("waitthread");
        pids = nil;
        alts[WErr].c = cerr;
        alts[WErr].v = &err;
        alts[WErr].op = CHANRCV;
        alts[WKill].c = ckill;
        alts[WKill].v = &cmd;
        alts[WKill].op = CHANRCV;
        alts[WWait].c = cwait;
        alts[WWait].v = &w;
        alts[WWait].op = CHANRCV;
        alts[WCmd].c = ccommand;
        alts[WCmd].v = &c;
        alts[WCmd].op = CHANRCV;
        alts[NWALT].op = CHANEND;

        command = nil;
        for(;;){
                switch(alt(alts)){
                case WErr:
                        qlock(&row);
                        warning(nil, "%s", err);
                        free(err);
                        flushimage(display, 1);
                        qunlock(&row);
                        break;
                case WKill:
                        found = FALSE;
                        ncmd = runestrlen(cmd);
                        for(c=command; c; c=c->next){
                                /* -1 for blank */
                                if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
                                        if(postnote(PNGROUP, c->pid, "kill") < 0)
                                                warning(nil, "kill %S: %r\n", cmd);
                                        found = TRUE;
                                }
                        }
                        if(!found)
                                warning(nil, "Kill: no process %S\n", cmd);
                        free(cmd);
                        break;
                case WWait:
                        pid = w->pid;
                        lc = nil;
                        for(c=command; c; c=c->next){
                                if(c->pid == pid){
                                        if(lc)
                                                lc->next = c->next;
                                        else
                                                command = c->next;
                                        break;
                                }
                                lc = c;
                        }
                        qlock(&row);
                        t = &row.tag;
                        textcommit(t, TRUE);
                        if(c == nil){
                                /* helper processes use this exit status */
                                if(strncmp(w->msg, "libthread", 9) != 0){
                                        p = emalloc(sizeof(Pid));
                                        p->pid = pid;
                                        strncpy(p->msg, w->msg, sizeof(p->msg));
                                        p->next = pids;
                                        pids = p;
                                }
                        }else{
                                if(search(t, c->name, c->nname)){
                                        textdelete(t, t->q0, t->q1, TRUE);
                                        textsetselect(t, 0, 0);
                                }
                                if(w->msg[0])
                                        warning(c->md, "%s\n", w->msg);
                                flushimage(display, 1);
                        }
                        qunlock(&row);
                        free(w);
    Freecmd:
                        if(c){
                                if(c->iseditcmd)
                                        sendul(cedit, 0);
                                free(c->text);
                                free(c->name);
                                fsysdelid(c->md);
                                free(c);
                        }
                        break;
                case WCmd:
                        /* has this command already exited? */
                        lastp = nil;
                        for(p=pids; p!=nil; p=p->next){
                                if(p->pid == c->pid){
                                        if(p->msg[0])
                                                warning(c->md, "%s\n", p->msg);
                                        if(lastp == nil)
                                                pids = p->next;
                                        else
                                                lastp->next = p->next;
                                        free(p);
                                        goto Freecmd;
                                }
                                lastp = p;
                        }
                        c->next = command;
                        command = c;
                        qlock(&row);
                        t = &row.tag;
                        textcommit(t, TRUE);
                        textinsert(t, 0, c->name, c->nname, TRUE);
                        textsetselect(t, 0, 0);
                        flushimage(display, 1);
                        qunlock(&row);
                        break;
                }
        }
}

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

        threadsetname("xfidallocthread");
        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;

        xfree = nil;
        for(;;){
                switch(alt(alts)){
                case Alloc:
                        x = xfree;
                        if(x)
                                xfree = x->next;
                        else{
                                x = emalloc(sizeof(Xfid));
                                x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
                                x->arg = x;
                                threadcreate(xfidctl, x->arg, STACK);
                        }
                        sendp(cxfidalloc, x);
                        break;
                case Free:
                        x->next = xfree;
                        xfree = x;
                        break;
                }
        }
}

/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
void
newwindowthread(void*)
{
        Window *w;

        threadsetname("newwindowthread");

        for(;;){
                /* only fsysproc is talking to us, so synchronization is trivial */
                recvp(cnewwindow);
                w = makenewwindow(nil);
                winsettag(w);
                sendp(cnewwindow, w);
        }
}

Reffont*
rfget(int fix, int save, int setfont, char *name)
{
        Reffont *r;
        Font *f;
        int i;

        r = nil;
        if(name == nil){
                name = fontnames[fix];
                r = reffonts[fix];
        }
        if(r == nil){
                for(i=0; i<nfontcache; i++)
                        if(strcmp(name, fontcache[i]->f->name) == 0){
                                r = fontcache[i];
                                goto Found;
                        }
                f = openfont(display, name);
                if(f == nil){
                        warning(nil, "can't open font file %s: %r\n", name);
                        return nil;
                }
                r = emalloc(sizeof(Reffont));
                r->f = f;
                fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
                fontcache[nfontcache++] = r;
        }
    Found:
        if(save){
                incref(r);
                if(reffonts[fix])
                        rfclose(reffonts[fix]);
                reffonts[fix] = r;
                if(name != fontnames[fix]){
                        free(fontnames[fix]);
                        fontnames[fix] = estrdup(name);
                }
        }
        if(setfont){
                reffont.f = r->f;
                incref(r);
                rfclose(reffonts[0]);
                font = r->f;
                reffonts[0] = r;
                incref(r);
                iconinit();
        }
        incref(r);
        return r;
}

void
rfclose(Reffont *r)
{
        int i;

        if(decref(r) == 0){
                for(i=0; i<nfontcache; i++)
                        if(r == fontcache[i])
                                break;
                if(i >= nfontcache)
                        warning(nil, "internal error: can't find font in cache\n");
                else{
                        nfontcache--;
                        memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
                }
                freefont(r->f);
                free(r);
        }
}

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}
};

void
iconinit(void)
{
        Rectangle r;
        Image *tmp;

        /* Blue */
        tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
        tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
        tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
        tagcols[TEXT] = display->black;
        tagcols[HTEXT] = display->black;

        /* Yellow */
        textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
        textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
        textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
        textcols[TEXT] = display->black;
        textcols[HTEXT] = display->black;

        if(button){
                freeimage(button);
                freeimage(modbutton);
                freeimage(colbutton);
        }

        r = Rect(0, 0, Scrollwid+2, font->height+1);
        button = allocimage(display, r, screen->chan, 0, DNofill);
        draw(button, r, tagcols[BACK], nil, r.min);
        r.max.x -= 2;
        border(button, r, 2, tagcols[BORD], ZP);

        r = button->r;
        modbutton = allocimage(display, r, screen->chan, 0, DNofill);
        draw(modbutton, r, tagcols[BACK], nil, r.min);
        r.max.x -= 2;
        border(modbutton, r, 2, tagcols[BORD], ZP);
        r = insetrect(r, 2);
        tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
        draw(modbutton, r, tmp, nil, ZP);
        freeimage(tmp);

        r = button->r;
        colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);

        but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
        but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
}

/*
 * /dev/snarf updates when the file is closed, so we must open our own
 * fd here rather than use snarffd
 */

/* rio truncates larges snarf buffers, so this avoids using the
 * service if the string is huge */

#define MAXSNARF 100*1024

void
putsnarf(void)
{
        int fd, i, n;

        if(snarffd<0 || snarfbuf.nc==0)
                return;
        if(snarfbuf.nc > MAXSNARF)
                return;
        fd = open("/dev/snarf", OWRITE);
        if(fd < 0)
                return;
        for(i=0; i<snarfbuf.nc; i+=n){
                n = snarfbuf.nc-i;
                if(n >= NSnarf)
                        n = NSnarf;
                bufread(&snarfbuf, i, snarfrune, n);
                if(fprint(fd, "%.*S", n, snarfrune) < 0)
                        break;
        }
        close(fd);
}

void
getsnarf()
{
        int nulls;

        if(snarfbuf.nc > MAXSNARF)
                return;
        if(snarffd < 0)
                return;
        seek(snarffd, 0, 0);
        bufreset(&snarfbuf);
        bufload(&snarfbuf, 0, snarffd, &nulls);
}