Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <plumb.h>
#include <regexp.h>
#include <event.h>      /* for support routines only */
#include <bio.h>
#include "faces.h"

int     history = 0;    /* use old interface, showing history of mailbox rather than current state */
int     initload = 0;   /* initialize program with contents of mail box */

enum
{
        Facesep = 6,    /* must be even to avoid damaging background stipple */
        Infolines = 9,

        HhmmTime = 18*60*60,    /* max age of face to display hh:mm time */
};

enum
{
        Mainp,
        Timep,
        Mousep,
        NPROC
};

int pids[NPROC];
char *procnames[] = {
        "main",
        "time",
        "mouse"
};

Rectangle leftright = {0, 0, 20, 15};

uchar leftdata[] = {
        0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
        0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
        0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
        0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
        0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
        0x80, 0x00, 0x00, 0x80, 0x00
};

uchar rightdata[] = {
        0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
        0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
        0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
        0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
        0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
        0x18, 0x00, 0x00, 0x10, 0x00
};

Image   *blue;          /* full arrow */
Image   *bgrnd;         /* pale blue background color */
Image   *left;          /* left-pointing arrow mask */
Image   *right;         /* right-pointing arrow mask */
Font    *tinyfont;
Font    *mediumfont;
Font    *datefont;
int     first, last;    /* first and last visible face; last is first invisible */
int     nfaces;
int     mousefd;
int     nacross;
int     ndown;

char    date[64];
Face    **faces;
char    *maildir = "/mail/fs/mbox";
ulong   now;

Point   datep = { 8, 6 };
Point   facep = { 8, 6+0+4 };   /* 0 updated to datefont->height in init() */
Point   enddate;                        /* where date ends on display; used to place arrows */
Rectangle       leftr;                  /* location of left arrow on display */
Rectangle       rightr;         /* location of right arrow on display */
void updatetimes(void);

void
setdate(void)
{
        now = time(nil);
        strcpy(date, ctime(now));
        date[4+4+3+5] = '\0';   /* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
}

void
init(void)
{
        mousefd = open("/dev/mouse", OREAD);
        if(mousefd < 0){
                fprint(2, "faces: can't open mouse: %r\n");
                exits("mouse");
        }
        initplumb();

        /* make background color */
        bgrnd = allocimagemix(display, DPalebluegreen, DWhite);
        blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF); /* blue-green */
        left = allocimage(display, leftright, GREY1, 0, DWhite);
        right = allocimage(display, leftright, GREY1, 0, DWhite);
        if(bgrnd==nil || blue==nil || left==nil || right==nil){
                fprint(2, "faces: can't create images: %r\n");
                exits("image");
        }

        loadimage(left, leftright, leftdata, sizeof leftdata);
        loadimage(right, leftright, rightdata, sizeof rightdata);

        /* initialize little fonts */
        tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
        if(tinyfont == nil)
                tinyfont = font;
        mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
        if(mediumfont == nil)
                mediumfont = font;
        datefont = font;

        facep.y += datefont->height;
        if(datefont->height & 1)        /* stipple parity */
                facep.y++;
        faces = nil;
}

void
drawtime(void)
{
        Rectangle r;

        r.min = addpt(screen->r.min, datep);
        if(eqpt(enddate, ZP)){
                enddate = r.min;
                enddate.x += stringwidth(datefont, "Wed May 30 22:54"); /* nice wide string */
                enddate.x += Facesep;   /* for safety */
        }
        r.max.x = enddate.x;
        r.max.y = enddate.y+datefont->height;
        draw(screen, r, bgrnd, nil, ZP);
        string(screen, r.min, display->black, ZP, datefont, date);
}

void
timeproc(void)
{
        for(;;){
                lockdisplay(display);
                drawtime();
                updatetimes();
                flushimage(display, 1);
                unlockdisplay(display);
                now = time(nil);
                sleep(((60 - now%60) + 1)*1000); /* wait for minute to change */
                setdate();
        }
}

int
alreadyseen(char *digest)
{
        int i;
        Face *f;

        if(!digest)
                return 0;

        /* can do accurate check */
        for(i=0; i<nfaces; i++){
                f = faces[i];
                if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
                        return 1;
        }
        return 0;
}

int
torune(Rune *r, char *s, int nr)
{
        int i;

        for(i=0; i<nr-1 && *s!='\0'; i++)
                s += chartorune(r+i, s);
        r[i] = L'\0';
        return i;
}

void
center(Font *f, Point p, char *s, Image *color)
{
        int i, n, dx;
        Rune rbuf[32];
        char sbuf[32*UTFmax+1];

        dx = stringwidth(f, s);
        if(dx > Facesize){
                n = torune(rbuf, s, nelem(rbuf));
                for(i=0; i<n; i++){
                        dx = runestringnwidth(f, rbuf, i+1);
                        if(dx > Facesize)
                                break;
                }
                sprint(sbuf, "%.*S", i, rbuf);
                s = sbuf;
                dx = stringwidth(f, s);
        }
        p.x += (Facesize-dx)/2;
        string(screen, p, color, ZP, f, s);
}

Rectangle
facerect(int index)     /* index is geometric; 0 is always upper left face */
{
        Rectangle r;
        int x, y;

        x = index % nacross;
        y = index / nacross;
        r.min = addpt(screen->r.min, facep);
        r.min.x += x*(Facesize+Facesep);
        r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
        r.max = addpt(r.min, Pt(Facesize, Facesize));
        r.max.y += 2*mediumfont->height;
        /* simple fix to avoid drawing off screen, allowing customers to use position */
        if(index<0 || index>=nacross*ndown)
                r.max.x = r.min.x;
        return r;
}

static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
char*
facetime(Face *f, int *recent)
{
        static char buf[30];

        if((long)(now - f->time) > HhmmTime){
                *recent = 0;
                sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
                return buf;
        }else{
                *recent = 1;
                sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
                return buf;
        }
}

void
drawface(Face *f, int i)
{
        char *tstr;
        Rectangle r;
        Point p;

        if(f == nil)
                return;
        if(i<first || i>=last)
                return;
        r = facerect(i-first);
        draw(screen, r, bgrnd, nil, ZP);
        draw(screen, r, f->bit, f->mask, ZP);
        r.min.y += Facesize;
        center(mediumfont, r.min, f->str[Suser], display->black);
        r.min.y += mediumfont->height;
        tstr = facetime(f, &f->recent);
        center(mediumfont, r.min, tstr, display->black);
        if(f->unknown){
                r.min.y -= mediumfont->height + tinyfont->height + 2;
                for(p.x=-1; p.x<=1; p.x++)
                        for(p.y=-1; p.y<=1; p.y++)
                                center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
                center(tinyfont, r.min, f->str[Sdomain], display->black);
        }
}

void
updatetimes(void)
{
        int i;
        Face *f;

        for(i=0; i<nfaces; i++){
                f = faces[i];
                if(f == nil)
                        continue;
                if(((long)(now - f->time) <= HhmmTime) != f->recent)
                        drawface(f, i);
        }       
}

void
setlast(void)
{
        last = first+nacross*ndown;
        if(last > nfaces)
                last = nfaces;
}

void
drawarrows(void)
{
        Point p;

        p = enddate;
        p.x += Facesep;
        if(p.x & 1)
                p.x++;  /* align background texture */
        leftr = rectaddpt(leftright, p);
        p.x += Dx(leftright) + Facesep;
        rightr = rectaddpt(leftright, p);
        draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
        draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
}

void
addface(Face *f)        /* always adds at 0 */
{
        Face **ofaces;
        Rectangle r0, r1, r;
        int y, nx, ny;

        if(f == nil)
                return;
        lockdisplay(display);
        if(first != 0){
                first = 0;
                resized();
        }
        findbit(f);

        nx = nacross;
        ny = (nfaces+(nx-1)) / nx;

        for(y=ny; y>=0; y--){
                /* move them along */
                r0 = facerect(y*nx+0);
                r1 = facerect(y*nx+1);
                r = r1;
                r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
                draw(screen, r, screen, nil, r0.min);
                /* copy one down from row above */
                if(y != 0){
                        r = facerect((y-1)*nx+nx-1);
                        draw(screen, r0, screen, nil, r.min);
                }
        }

        ofaces = faces;
        faces = emalloc((nfaces+1)*sizeof(Face*));
        memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
        free(ofaces);
        nfaces++;
        setlast();
        drawarrows();
        faces[0] = f;
        drawface(f, 0);
        flushimage(display, 1);
        unlockdisplay(display);
}

void
loadmboxfaces(char *maildir)
{
        int dirfd;
        Dir *d;
        int i, n;

        dirfd = open(maildir, OREAD);
        if(dirfd >= 0){
                chdir(maildir);
                while((n = dirread(dirfd, &d)) > 0){
                        for(i=0; i<n; i++)
                                addface(dirface(maildir, d[i].name));
                        free(d);
                }
                close(dirfd);
        }
}

void
freeface(Face *f)
{
        int i;

        if(f->file!=nil && f->bit!=f->file->image)
                freeimage(f->bit);
        freefacefile(f->file);
        for(i=0; i<Nstring; i++)
                free(f->str[i]);
        free(f);
}

void
delface(int j)
{
        Rectangle r0, r1, r;
        int nx, ny, x, y;

        if(j < first)
                first--;
        else if(j < last){
                nx = nacross;
                ny = (nfaces+(nx-1)) / nx;
                x = (j-first)%nx;
                for(y=(j-first)/nx; y<ny; y++){
                        if(x != nx-1){
                                /* move them along */
                                r0 = facerect(y*nx+x);
                                r1 = facerect(y*nx+x+1);
                                r = r0;
                                r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
                                draw(screen, r, screen, nil, r1.min);
                        }
                        if(y != ny-1){
                                /* copy one up from row below */
                                r = facerect((y+1)*nx);
                                draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
                        }
                        x = 0;
                }
                if(last < nfaces)       /* first off-screen becomes visible */
                        drawface(faces[last], last-1);
                else{
                        /* clear final spot */
                        r = facerect(last-first-1);
                        draw(screen, r, bgrnd, nil, r.min);
                }
        }
        freeface(faces[j]);
        memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
        nfaces--;
        setlast();
        drawarrows();
}

void
dodelete(int i)
{
        Face *f;

        f = faces[i];
        if(history){
                free(f->str[Sshow]);
                f->str[Sshow] = estrdup("");
        }else{
                delface(i);
                flushimage(display, 1);
        }
}

void
delete(char *s, char *digest)
{
        int i;
        Face *f;

        lockdisplay(display);
        for(i=0; i<nfaces; i++){
                f = faces[i];
                if(digest != nil){
                        if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
                                dodelete(i);
                                break;
                        }
                }else{
                        if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
                                dodelete(i);
                                break;
                        }
                }
        }
        unlockdisplay(display);
}

void
faceproc(void)
{
        for(;;)
                addface(nextface());
}

void
resized(void)
{
        int i;

        nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
        for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
                ;
        setlast();
        draw(screen, screen->r, bgrnd, nil, ZP);
        enddate = ZP;
        drawtime();
        for(i=0; i<nfaces; i++)
                drawface(faces[i], i);
        drawarrows();
        flushimage(display, 1);
}

void
eresized(int new)
{
        lockdisplay(display);
        if(new && getwindow(display, Refnone) < 0) {
                fprint(2, "can't reattach to window\n");
                killall("reattach");
        }
        resized();
        unlockdisplay(display);
}

int
getmouse(Mouse *m)
{
        int n;
        static int eof;
        char buf[128];

        if(eof)
                return 0;
        for(;;){
                n = read(mousefd, buf, sizeof(buf));
                if(n <= 0){
                        /* so callers needn't check return value every time */
                        eof = 1;
                        m->buttons = 0;
                        return 0;
                }
                n = eatomouse(m, buf, n);
                if(n > 0)
                        return 1;
        }
}

enum
{
        Clicksize       = 3,            /* pixels */
};

int
scroll(int but, Point p)
{
        int delta;

        delta = 0;
        lockdisplay(display);
        if(ptinrect(p, leftr) && first>0){
                if(but == 2)
                        delta = -first;
                else{
                        delta = nacross;
                        if(delta > first)
                                delta = first;
                        delta = -delta;
                }
        }else if(ptinrect(p, rightr) && last<nfaces){
                if(but == 2)
                        delta = (nfaces-nacross*ndown) - first;
                else{
                        delta = nacross;
                        if(delta > nfaces-last)
                                delta = nfaces-last;
                }
        }
        first += delta;
        last += delta;
        unlockdisplay(display);
        if(delta)
                eresized(0);
        return delta;
}

void
click(int button, Mouse *m)
{
        Point p;
        int i;

        p = m->xy;
        while(m->buttons == (1<<(button-1)))
                getmouse(m);
        if(m->buttons)
                return;
        if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
                return;
        switch(button){
        case 1:
                if(scroll(1, p))
                        break;
                if(history){
                        /* click clears display */
                        lockdisplay(display);
                        for(i=0; i<nfaces; i++)
                                freeface(faces[i]);
                        free(faces);
                        faces=nil;
                        nfaces = 0;
                        unlockdisplay(display);
                        eresized(0);
                        return;
                }else{
                        for(i=first; i<last; i++)       /* clear vwhois faces */
                                if(ptinrect(p, facerect(i-first)) 
                                && strstr(faces[i]->str[Sshow], "/XXXvwhois")){
                                        delface(i);
                                        flushimage(display, 1);
                                }
                }
                break;
        case 2:
                scroll(2, p);
                break;
        case 3:
                scroll(3, p);
                lockdisplay(display);
                for(i=first; i<last; i++)
                        if(ptinrect(p, facerect(i-first))){
                                showmail(faces[i]);
                                break;
                        }
                unlockdisplay(display);
                break;
        }
}

void
mouseproc(void)
{
        Mouse mouse;

        while(getmouse(&mouse)){
                if(mouse.buttons == 1)
                        click(1, &mouse);
                else if(mouse.buttons == 2)
                        click(2, &mouse);
                else if(mouse.buttons == 4)
                        click(3, &mouse);

                while(mouse.buttons)
                        getmouse(&mouse);
        }
}

void
killall(char *s)
{
        int i, pid;

        pid = getpid();
        for(i=0; i<NPROC; i++)
                if(pids[i] && pids[i]!=pid)
                        postnote(PNPROC, pids[i], "kill");
        exits(s);
}

void
startproc(void (*f)(void), int index)
{
        int pid;

        switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
        case -1:
                fprint(2, "faces: fork failed: %r\n");
                killall("fork failed");
        case 0:
                f();
                fprint(2, "faces: %s process exits\n", procnames[index]);
                if(index >= 0)
                        killall("process died");
                exits(nil);
        }
        if(index >= 0)
                pids[index] = pid;
}

void
usage(void)
{
        fprint(2, "usage: faces [-hi] [-m maildir]\n");
        exits("usage");
}

void
main(int argc, char *argv[])
{
        int i;

        ARGBEGIN{
        case 'h':
                history++;
                break;
        case 'i':
                initload++;
                break;
        case 'm':
                addmaildir(EARGF(usage()));
                maildir = nil;
                break;
        default:
                usage();
        }ARGEND

        if(initdraw(nil, nil, "faces") < 0){
                fprint(2, "faces: initdraw failed: %r\n");
                exits("initdraw");
        }
        if(maildir)
                addmaildir(maildir);
        init();
        unlockdisplay(display); /* initdraw leaves it locked */
        display->locking = 1;   /* tell library we're using the display lock */
        setdate();
        eresized(0);

        pids[Mainp] = getpid();
        startproc(timeproc, Timep);
        startproc(mouseproc, Mousep);
        if(initload)
                for(i = 0; i < nmaildirs; i++)
                 loadmboxfaces(maildirs[i]);
        faceproc();
        fprint(2, "faces: %s process exits\n", procnames[Mainp]);
        killall(nil);
}