Subversion Repositories planix.SVN

Rev

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

#include        <u.h>
#include        <libc.h>
#include        "compat.h"
#include        "kbd.h"
#include        "error.h"

typedef struct Queue    Queue;
struct Queue
{
        QLock   qwait;
        Rendez  rwait;

        Lock    lock;
        int     notempty;
        char    buf[1024];
        char    *w;
        char    *r;
        char    *e;
};

Queue*  kbdq;                   /* unprocessed console input */
Queue*  lineq;                  /* processed console input */
Snarf   snarf = {
        .vers = 1
};

static struct
{
        QLock;
        int     raw;            /* true if we shouldn't process input */
        int     ctl;            /* number of opens to the control file */
        int     x;              /* index into line */
        char    line[1024];     /* current input line */
} kbd;

/*
 * cheapo fixed-length queues
 */
static void
qwrite(Queue *q, void *v, int n)
{
        char *buf, *next;
        int i;

        buf = v;
        lock(&q->lock);
        for(i = 0; i < n; i++){
                next = q->w+1;
                if(next >= q->e)
                        next = q->buf;
                if(next == q->r)
                        break;
                *q->w = buf[i];
                q->w = next;
        }
        q->notempty = 1;
        unlock(&q->lock);
        rendwakeup(&q->rwait);
}

static int
qcanread(void *vq)
{
        Queue *q;
        int ne;

        q = vq;
        lock(&q->lock);
        ne = q->notempty;
        unlock(&q->lock);
        return ne;
}

static int
qread(Queue *q, void *v, int n)
{
        char *a;
        int nn, notempty;

        if(n == 0)
                return 0;
        a = v;
        nn = 0;
        for(;;){
                lock(&q->lock);

                while(nn < n && q->r != q->w){
                        a[nn++] = *q->r++;
                        if(q->r >= q->e)
                                q->r = q->buf;
                }

                notempty = q->notempty;
                q->notempty = q->r != q->w;
                unlock(&q->lock);
                if(notempty)
                        break;

                /*
                 * wait for something to show up in the kbd buffer.
                 */
                qlock(&q->qwait);
                if(waserror()){
                        qunlock(&q->qwait);
                        nexterror();
                }
                rendsleep(&q->rwait, qcanread, q);
                qunlock(&q->qwait);
                poperror();
        }
        return nn;
}

static Queue *
mkqueue(void)
{
        Queue *q;

        q = smalloc(sizeof(Queue));
        q->r = q->buf;
        q->w = q->r;
        q->e = &q->buf[sizeof q->buf];
        q->notempty = 0;
        return q;
}

static void
echoscreen(char *buf, int n)
{
        char *e, *p;
        char ebuf[128];
        int x;

        p = ebuf;
        e = ebuf + sizeof(ebuf) - 4;
        while(n-- > 0){
                if(p >= e){
                        screenputs(ebuf, p - ebuf);
                        p = ebuf;
                }
                x = *buf++;
                if(x == 0x15){
                        *p++ = '^';
                        *p++ = 'U';
                        *p++ = '\n';
                } else
                        *p++ = x;
        }
        if(p != ebuf)
                screenputs(ebuf, p - ebuf);
}

/*
 *  Put character, possibly a rune, into read queue at interrupt time.
 *  Called at interrupt time to process a character.
 */
void
kbdputc(int ch)
{
        int n;
        char buf[UTFmax];
        Rune r;

        r = ch;
        n = runetochar(buf, &r);
        qwrite(kbdq, buf, n);
        if(!kbd.raw)
                echoscreen(buf, n);
}

static void
kbdputcinit(void)
{
        kbdq = mkqueue();
        lineq = mkqueue();
        kbd.raw = 0;
        kbd.ctl = 0;
        kbd.x = 0;
}

enum{
        Qdir,
        Qcons,
        Qconsctl,
        Qsnarf,
        Qwinname,
};

static Dirtab consdir[]={
        ".",            {Qdir, 0, QTDIR},       0,              DMDIR|0555,
        "cons",         {Qcons},        0,              0660,
        "consctl",      {Qconsctl},     0,              0220,
        "snarf",        {Qsnarf},       0,              0600,
        "winname",      {Qwinname},     0,              0000,
};

static void
consinit(void)
{
        kbdputcinit();
}

static Chan*
consattach(char *spec)
{
        return devattach('c', spec);
}

static Walkqid*
conswalk(Chan *c, Chan *nc, char **name, int nname)
{
        return devwalk(c, nc, name,nname, consdir, nelem(consdir), devgen);
}

static int
consstat(Chan *c, uchar *dp, int n)
{
        return devstat(c, dp, n, consdir, nelem(consdir), devgen);
}

static Chan*
consopen(Chan *c, int omode)
{
        c->aux = nil;
        c = devopen(c, omode, consdir, nelem(consdir), devgen);
        switch((ulong)c->qid.path){
        case Qconsctl:
                qlock(&kbd);
                kbd.ctl++;
                qunlock(&kbd);
                break;
        case Qsnarf:
                if((c->mode&3) == OWRITE || (c->mode&3) == ORDWR)
                        c->aux = smalloc(sizeof(Snarf));
                break;
        }
        return c;
}

void
setsnarf(char *buf, int n, int *vers)
{
        int i;

        qlock(&snarf);
        snarf.vers++;
        if(vers)
                *vers = snarf.vers;     
        for(i = 0; i < nelem(consdir); i++){
                if(consdir[i].qid.type == Qsnarf){
                        consdir[i].qid.vers = snarf.vers;
                        break;
                }
        }
        free(snarf.buf);
        snarf.n = n;
        snarf.buf = buf;
        qunlock(&snarf);
}

static void
consclose(Chan *c)
{
        Snarf *t;

        switch((ulong)c->qid.path){
        /* last close of control file turns off raw */
        case Qconsctl:
                if(c->flag&COPEN){
                        qlock(&kbd);
                        if(--kbd.ctl == 0)
                                kbd.raw = 0;
                        qunlock(&kbd);
                }
                break;
        /* odd behavior but really ok: replace snarf buffer when /dev/snarf is closed */
        case Qsnarf:
                t = c->aux;
                if(t == nil)
                        break;
                setsnarf(t->buf, t->n, 0);
                t->buf = nil;   /* setsnarf took it */
                free(t);
                c->aux = nil;
                break;
        }
}

static long
consread(Chan *c, void *buf, long n, vlong off)
{
        char ch;
        int     send;

        if(n <= 0)
                return n;
        switch((ulong)c->qid.path){
        case Qsnarf:
                qlock(&snarf);
                if(off < snarf.n){
                        if(off + n > snarf.n)
                                n = snarf.n - off;
                        memmove(buf, snarf.buf+off, n);
                }else
                        n = 0;
                qunlock(&snarf);
                return n;

        case Qdir:
                return devdirread(c, buf, n, consdir, nelem(consdir), devgen);

        case Qcons:
                qlock(&kbd);
                if(waserror()){
                        qunlock(&kbd);
                        nexterror();
                }
                while(!qcanread(lineq)){
                        qread(kbdq, &ch, 1);
                        send = 0;
                        if(ch == 0){
                                /* flush output on rawoff -> rawon */
                                if(kbd.x > 0)
                                        send = !qcanread(kbdq);
                        }else if(kbd.raw){
                                kbd.line[kbd.x++] = ch;
                                send = !qcanread(kbdq);
                        }else{
                                switch(ch){
                                case '\b':
                                        if(kbd.x > 0)
                                                kbd.x--;
                                        break;
                                case 0x15:      /* ^U */
                                        kbd.x = 0;
                                        break;
                                case '\n':
                                case 0x04:      /* ^D */
                                        send = 1;
                                default:
                                        if(ch != 0x04)
                                                kbd.line[kbd.x++] = ch;
                                        break;
                                }
                        }
                        if(send || kbd.x == sizeof kbd.line){
                                qwrite(lineq, kbd.line, kbd.x);
                                kbd.x = 0;
                        }
                }
                n = qread(lineq, buf, n);
                qunlock(&kbd);
                poperror();
                return n;

        default:
                print("consread 0x%llux\n", c->qid.path);
                error(Egreg);
        }
        return -1;              /* never reached */
}

static long
conswrite(Chan *c, void *va, long n, vlong)
{
        Snarf *t;
        char buf[256], *a;
        char ch;

        switch((ulong)c->qid.path){
        case Qcons:
                screenputs(va, n);
                break;

        case Qconsctl:
                if(n >= sizeof(buf))
                        n = sizeof(buf)-1;
                strncpy(buf, va, n);
                buf[n] = 0;
                for(a = buf; a;){
                        if(strncmp(a, "rawon", 5) == 0){
                                kbd.raw = 1;
                                /* clumsy hack - wake up reader */
                                ch = 0;
                                qwrite(kbdq, &ch, 1);                   
                        } else if(strncmp(a, "rawoff", 6) == 0){
                                kbd.raw = 0;
                        }
                        if(a = strchr(a, ' '))
                                a++;
                }
                break;

        case Qsnarf:
                t = c->aux;
                /* always append only */
                if(t->n > MAXSNARF)     /* avoid thrashing when people cut huge text */
                        error("snarf buffer too big");
                a = realloc(t->buf, t->n + n + 1);
                if(a == nil)
                        error("snarf buffer too big");
                t->buf = a;
                memmove(t->buf+t->n, va, n);
                t->n += n;
                t->buf[t->n] = '\0';
                break;
        default:
                print("conswrite: 0x%llux\n", c->qid.path);
                error(Egreg);
        }
        return n;
}

Dev consdevtab = {
        'c',
        "cons",

        devreset,
        consinit,
        consattach,
        conswalk,
        consstat,
        consopen,
        devcreate,
        consclose,
        consread,
        devbread,
        conswrite,
        devbwrite,
        devremove,
        devwstat,
};