diff options
author | rsc <devnull@localhost> | 2005-10-31 14:47:39 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-10-31 14:47:39 +0000 |
commit | b330c942b468ab82fd8853590145187e859258cb (patch) | |
tree | cb66b302b657bf0e966695fa0ab1d9596da234a0 /src/cmd/faces | |
parent | 663ddde9d07417ab51239c0c4305708a1a319c62 (diff) | |
download | plan9port-b330c942b468ab82fd8853590145187e859258cb.tar.gz plan9port-b330c942b468ab82fd8853590145187e859258cb.tar.bz2 plan9port-b330c942b468ab82fd8853590145187e859258cb.zip |
initial faces (John Cummings)
Diffstat (limited to 'src/cmd/faces')
-rw-r--r-- | src/cmd/faces/dblook.c | 30 | ||||
-rw-r--r-- | src/cmd/faces/facedb.c | 562 | ||||
-rw-r--r-- | src/cmd/faces/faces.h | 68 | ||||
-rw-r--r-- | src/cmd/faces/main.c | 783 | ||||
-rw-r--r-- | src/cmd/faces/mkfile | 26 | ||||
-rw-r--r-- | src/cmd/faces/plumb.c | 398 | ||||
-rw-r--r-- | src/cmd/faces/util.c | 42 |
7 files changed, 1909 insertions, 0 deletions
diff --git a/src/cmd/faces/dblook.c b/src/cmd/faces/dblook.c new file mode 100644 index 00000000..b5c023f5 --- /dev/null +++ b/src/cmd/faces/dblook.c @@ -0,0 +1,30 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <plumb.h> +#include <regexp.h> +#include <bio.h> +#include <9pclient.h> +#include <thread.h> +#include "faces.h" + +void +threadmain(int argc, char **argv) +{ + Face f; + char *q; + + if(argc != 3){ + fprint(2, "usage: dblook name domain\n"); + threadexitsall("usage"); + } + + q = findfile(&f, argv[2], argv[1]); + print("%s\n", q); +} + +void +killall(char *s) +{ + USED(s); +} diff --git a/src/cmd/faces/facedb.c b/src/cmd/faces/facedb.c new file mode 100644 index 00000000..0c4f2d79 --- /dev/null +++ b/src/cmd/faces/facedb.c @@ -0,0 +1,562 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <plumb.h> +#include <regexp.h> +#include <bio.h> +#include <9pclient.h> +#include "faces.h" + +enum /* number of deleted faces to cache */ +{ + Nsave = 20, +}; + +static Facefile *facefiles; +static int nsaved; +static char *facedom; + +/* + * Loading the files is slow enough on a dial-up line to be worth this trouble + */ +typedef struct Readcache Readcache; +struct Readcache { + char *file; + char *data; + long mtime; + long rdtime; + Readcache *next; +}; + +static Readcache *rcache; + +ulong +dirlen(char *s) +{ + Dir *d; + ulong len; + + d = dirstat(s); + if(d == nil) + return 0; + len = d->length; + free(d); + return len; +} + +ulong +fsdirlen(CFsys *fs,char *s) +{ + Dir *d; + ulong len; + + d = fsdirstat(fs,s); + if(d == nil) + return 0; + len = d->length; + free(d); + return len; +} + +ulong +dirmtime(char *s) +{ + Dir *d; + ulong t; + + d = dirstat(s); + if(d == nil) + return 0; + t = d->mtime; + free(d); + return t; +} + +static char* +doreadfile(char *s) +{ + char *p; + int fd, n; + ulong len; + + len = dirlen(s); + if(len == 0) + return nil; + + p = malloc(len+1); + if(p == nil) + return nil; + + if((fd = open(s, OREAD)) < 0 + || (n = readn(fd, p, len)) < 0) { + close(fd); + free(p); + return nil; + } + + p[n] = '\0'; + return p; +} + +static char* +readfile(char *s) +{ + Readcache *r, **l; + char *p; + ulong mtime; + + for(l=&rcache, r=*l; r; l=&r->next, r=*l) { + if(strcmp(r->file, s) != 0) + continue; + + /* + * if it's less than 30 seconds since we read it, or it + * hasn't changed, send back our copy + */ + if(time(0) - r->rdtime < 30) + return strdup(r->data); + if(dirmtime(s) == r->mtime) { + r->rdtime = time(0); + return strdup(r->data); + } + + /* out of date, remove this and fall out of loop */ + *l = r->next; + free(r->file); + free(r->data); + free(r); + break; + } + + /* add to cache */ + mtime = dirmtime(s); + if(mtime == 0) + return nil; + + if((p = doreadfile(s)) == nil) + return nil; + + r = malloc(sizeof(*r)); + if(r == nil) + return nil; + r->mtime = mtime; + r->file = estrdup(s); + r->data = p; + r->rdtime = time(0); + r->next = rcache; + rcache = r; + return strdup(r->data); +} + + +static char* +translatedomain(char *dom) +{ + static char buf[200]; + char *p, *ep, *q, *nextp, *file; + char *bbuf, *ebuf; + Reprog *exp; + + if(dom == nil || *dom == 0) + return nil; + + if((file = readfile(unsharp("#9/lib/face/.machinelist"))) == nil) + return dom; + + for(p=file; p; p=nextp) { + if(nextp = strchr(p, '\n')) + *nextp++ = '\0'; + + if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2) + continue; + + bbuf = buf+1; + ebuf = buf+(1+(q-p)); + strncpy(bbuf, p, ebuf-bbuf); + *ebuf = 0; + if(*bbuf != '^') + *--bbuf = '^'; + if(ebuf[-1] != '$') { + *ebuf++ = '$'; + *ebuf = 0; + } + + if((exp = regcomp(bbuf)) == nil){ + fprint(2, "bad regexp in machinelist: %s\n", bbuf); + killall("regexp"); + } + + if(regexec(exp, dom, 0, 0)){ + free(exp); + ep = p+strlen(p); + q += strspn(q, " \t"); + if(ep-q+2 > sizeof buf) { + fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q); + exits("bad big replacement"); + } + strncpy(buf, q, ep-q); + ebuf = buf+(ep-q); + *ebuf = 0; + while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t')) + *--ebuf = 0; + free(file); + return buf; + } + free(exp); + } + free(file); + + return dom; +} + +static char* +tryfindpicture_user(char *dom, char *user, int depth) +{ + static char buf[200]; + char *p, *q, *nextp, *file, *usr; + usr = getuser(); + + sprint(buf, "/usr/%s/lib/face/48x48x%d/.dict", usr, depth); + if((file = readfile(buf)) == nil) + return nil; + + snprint(buf, sizeof buf, "%s/%s", dom, user); + + for(p=file; p; p=nextp) { + if(nextp = strchr(p, '\n')) + *nextp++ = '\0'; + + if(*p == '#' || (q = strpbrk(p, " \t")) == nil) + continue; + *q++ = 0; + + if(strcmp(buf, p) == 0) { + q += strspn(q, " \t"); + q = buf+snprint(buf, sizeof buf, "/usr/%s/lib/face/48x48x%d/%s", usr, depth, q); + while(q > buf && (q[-1] == ' ' || q[-1] == '\t')) + *--q = 0; + free(file); + return buf; + } + } + free(file); + return nil; +} + +static char* +tryfindpicture_global(char *dom, char *user, int depth) +{ + static char buf[200]; + char *p, *q, *nextp, *file; + + sprint(buf, "#9/lib/face/48x48x%d/.dict", depth); + if((file = readfile(unsharp(buf))) == nil) + return nil; + + snprint(buf, sizeof buf, "%s/%s", dom, user); + + for(p=file; p; p=nextp) { + if(nextp = strchr(p, '\n')) + *nextp++ = '\0'; + + if(*p == '#' || (q = strpbrk(p, " \t")) == nil) + continue; + *q++ = 0; + + if(strcmp(buf, p) == 0) { + q += strspn(q, " \t"); + q = buf+snprint(buf, sizeof buf, "#9/lib/face/48x48x%d/%s", depth, q); + while(q > buf && (q[-1] == ' ' || q[-1] == '\t')) + *--q = 0; + free(file); + return unsharp(buf); + } + } + free(file); + return nil; +} + +static char* +tryfindpicture(char *dom, char *user, int depth) +{ + char* result; + + if((result = tryfindpicture_user(dom, user, depth)) != nil) + return result; + + return tryfindpicture_global(dom, user, depth); +} + +static char* +tryfindfile(char *dom, char *user, int depth) +{ + char *p, *q; + + for(;;){ + for(p=dom; p; (p=strchr(p, '.')) && p++) + if(q = tryfindpicture(p, user, depth)) + return q; + depth >>= 1; + if(depth == 0) + break; + } + return nil; +} + +char* +findfile(Face *f, char *dom, char *user) +{ + char *p; + int depth; + + if(facedom == nil){ + facedom = getenv("facedom"); + if(facedom == nil) + facedom = DEFAULT; + } + + dom = translatedomain(dom); + if(dom == nil) + dom = facedom; + + if(screen == nil) + depth = 8; + else + depth = screen->depth; + + if(depth > 8) + depth = 8; + + f->unknown = 0; + if(p = tryfindfile(dom, user, depth)) + return p; + f->unknown = 1; + p = tryfindfile(dom, "unknown", depth); + if(p != nil || strcmp(dom, facedom)==0) + return p; + return tryfindfile("unknown", "unknown", depth); +} + +static +void +clearsaved(void) +{ + Facefile *f, *next, **lf; + + lf = &facefiles; + for(f=facefiles; f!=nil; f=next){ + next = f->next; + if(f->ref > 0){ + *lf = f; + lf = &(f->next); + continue; + } + if(f->image != display->black && f->image != display->white) + freeimage(f->image); + free(f->file); + free(f); + } + *lf = nil; + nsaved = 0; +} + +void +freefacefile(Facefile *f) +{ + if(f==nil || f->ref-->1) + return; + if(++nsaved > Nsave) + clearsaved(); +} + +static Image* +myallocimage(ulong chan) +{ + Image *img; + img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill); + if(img == nil){ + clearsaved(); + img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill); + if(img == nil) + return nil; + } + return img; +} + + +static Image* +readbit(int fd, ulong chan) +{ + char buf[4096], hx[4], *p; + uchar data[Facesize*Facesize]; /* more than enough */ + int nhx, i, n, ndata, nbit; + Image *img; + + n = readn(fd, buf, sizeof buf); + if(n <= 0) + return nil; + if(n >= sizeof buf) + n = sizeof(buf)-1; + buf[n] = '\0'; + + n = 0; + nhx = 0; + nbit = chantodepth(chan); + ndata = (Facesize*Facesize*nbit)/8; + p = buf; + while(n < ndata) { + p = strpbrk(p+1, "0123456789abcdefABCDEF"); + if(p == nil) + break; + if(p[0] == '0' && p[1] == 'x') + continue; + + hx[nhx] = *p; + if(++nhx == 2) { + hx[nhx] = 0; + i = strtoul(hx, 0, 16); + data[n++] = i; + nhx = 0; + } + } + if(n < ndata) + return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888); + + img = myallocimage(chan); + if(img == nil) + return nil; + loadimage(img, img->r, data, ndata); + return img; +} + +static Facefile* +readface(char *fn) +{ + int x, y, fd; + uchar bits; + uchar *p; + Image *mask; + Image *face; + char buf[16]; + uchar data[Facesize*Facesize]; + uchar mdata[(Facesize*Facesize)/8]; + Facefile *f; + Dir *d; + + for(f=facefiles; f!=nil; f=f->next){ + if(strcmp(fn, f->file) == 0){ + if(f->image == nil) + break; + if(time(0) - f->rdtime >= 30) { + if(dirmtime(fn) != f->mtime){ + f = nil; + break; + } + f->rdtime = time(0); + } + f->ref++; + return f; + } + } + + if((fd = open(fn, OREAD)) < 0) + return nil; + + if(readn(fd, buf, sizeof buf) != sizeof buf){ + close(fd); + return nil; + } + + seek(fd, 0, 0); + + mask = nil; + if(buf[0] == '0' && buf[1] == 'x'){ + /* greyscale faces are just masks that we draw black through! */ + if(buf[2+8] == ',') /* ldepth 1 */ + mask = readbit(fd, GREY2); + else + mask = readbit(fd, GREY1); + face = display->black; + }else{ + face = readimage(display, fd, 0); + if(face == nil) + goto Done; + else if(face->chan == GREY4 || face->chan == GREY8){ /* greyscale: use inversion as mask */ + mask = myallocimage(face->chan); + /* okay if mask is nil: that will copy the image white background and all */ + if(mask == nil) + goto Done; + + /* invert greyscale image */ + draw(mask, mask->r, display->white, nil, ZP); + gendraw(mask, mask->r, display->black, ZP, face, face->r.min); + freeimage(face); + face = display->black; + }else if(face->depth == 8){ /* snarf the bytes back and do a fill. */ + mask = myallocimage(GREY1); + if(mask == nil) + goto Done; + if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){ + freeimage(mask); + goto Done; + } + bits = 0; + p = mdata; + for(y=0; y<Facesize; y++){ + for(x=0; x<Facesize; x++){ + bits <<= 1; + if(data[Facesize*y+x] != 0xFF) + bits |= 1; + if((x&7) == 7) + *p++ = bits&0xFF; + } + } + if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){ + freeimage(mask); + goto Done; + } + } + } + +Done: + /* always add at beginning of list, so updated files don't collide in cache */ + if(f == nil){ + f = emalloc(sizeof(Facefile)); + f->file = estrdup(fn); + d = dirfstat(fd); + if(d != nil){ + f->mtime = d->mtime; + free(d); + } + f->next = facefiles; + facefiles = f; + } + f->ref++; + f->image = face; + f->mask = mask; + f->rdtime = time(0); + close(fd); + return f; +} + +void +findbit(Face *f) +{ + char *fn; + + fn = findfile(f, f->str[Sdomain], f->str[Suser]); + if(fn) { + if(strstr(fn, "unknown")) + f->unknown = 1; + f->file = readface(fn); + } + if(f->file){ + f->bit = f->file->image; + f->mask = f->file->mask; + }else{ + /* if returns nil, this is still ok: draw(nil) works */ + f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow); + replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize)); + f->mask = nil; + } +} diff --git a/src/cmd/faces/faces.h b/src/cmd/faces/faces.h new file mode 100644 index 00000000..7974a8b3 --- /dev/null +++ b/src/cmd/faces/faces.h @@ -0,0 +1,68 @@ +enum /* face strings */ +{ + Suser, + Sdomain, + Sshow, + Sdigest, + Nstring +}; + +enum +{ + Facesize = 48, +}; + +typedef struct Face Face; +typedef struct Facefile Facefile; + +struct Face +{ + Image *bit; /* unless there's an error, this is file->image */ + Image *mask; /* unless there's an error, this is file->mask */ + char *str[Nstring]; + int recent; + ulong time; + Tm tm; + int unknown; + Facefile *file; +}; + +/* + * Loading the files is slow enough on a dial-up line to be worth this trouble + */ +struct Facefile +{ + Image *image; + Image *mask; + ulong mtime; + ulong rdtime; + int ref; + char *file; + Facefile *next; +}; + +extern char date[]; +extern char *maildir; +extern char **maildirs; +extern int nmaildirs; +extern CFsys *upasfs; + +Face* nextface(void); +void findbit(Face*); +void freeface(Face*); +void initplumb(void); +void killall(char*); +void showmail(Face*); +void delete(char*, char*); +void freefacefile(Facefile*); +Face* dirface(char*, char*); +void resized(void); +int alreadyseen(char*); +ulong dirlen(char*); +ulong fsdirlen(CFsys*, char*); + +void *emalloc(ulong); +void *erealloc(void*, ulong); +char *estrdup(char*); +char *findfile(Face*, char*, char*); +void addmaildir(char*); diff --git a/src/cmd/faces/main.c b/src/cmd/faces/main.c new file mode 100644 index 00000000..4be56230 --- /dev/null +++ b/src/cmd/faces/main.c @@ -0,0 +1,783 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <plumb.h> +#include <regexp.h> +//jpc #include <event.h> /* for support routines only */ +#include <bio.h> +#include <thread.h> +#include <mouse.h> +#include <cursor.h> +#include <9pclient.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 +}; + +CFsys *upasfs; +Mousectl *mousectl; +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) +{ +#if 0 + mousefd = open("/dev/mouse", OREAD); + if(mousefd < 0){ + fprint(2, "faces: can't open mouse: %r\n"); + threadexitsall("mouse"); + } +#endif + upasfs = nsmount("upasfs",nil); + mousectl = initmouse(nil,screen); + 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"); + threadexitsall("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 *dummy) +{ + for(;;){ + lockdisplay(display); + drawtime(); + updatetimes(); + flushimage(display, 1); + unlockdisplay(display); + sleep(60000); + 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); +} + +#if 0 +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); + } +} +#endif + +void +loadmboxfaces(char *maildir) +{ + CFid *dirfd; + Dir *d; + int i, n; + + dirfd = fsopen(upasfs,maildir, OREAD); + if(dirfd != nil){ + //jpc chdir(maildir); + while((n = fsdirread(dirfd, &d)) > 0){ + for(i=0; i<n; i++) { + addface(dirface(maildir, d[i].name)); + } + free(d); + } + fsclose(dirfd); + } + else { + error("cannot open %s: %r",maildir); + } +} + +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); +} + +#if 0 +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; + } + //jpc n = eatomouse(m, buf, n); + if(n > 0) + return 1; + } +} +#endif +int +getmouse(Mouse *m) +{ + static int eof; + + if(eof) + return 0; + if( readmouse(mousectl) < 0 ) { + eof = 1; + m->buttons = 0; + return 0; + } + else { + *m = mousectl->m; +/* m->buttons = mousectl->m.buttons; + m->xy.x = mousectl->m.xy.x; + m->xy.y = mousectl->m.xy.y; + m->msec = mousectl->m.msec; */ + 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 *dummy) +{ + 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"); + threadexitsall(s); +} + +void +startproc(void (*f)(void), int index) +{ + int pid; + + switch(pid = rfork(RFPROC|RFNOWAIT)){ //jpc removed |RFMEM + 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"); + threadexitsall(nil); + } + if(index >= 0) + pids[index] = pid; +} + +void +usage(void) +{ + fprint(2, "usage: faces [-hi] [-m maildir] -W winsize\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + int i; + + ARGBEGIN{ + case 'h': + history++; + break; + case 'i': + initload++; + break; + case 'm': + addmaildir(EARGF(usage())); + maildir = nil; + break; + case 'W': + winsize = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if(initdraw(nil, nil, "faces") < 0){ + fprint(2, "faces: initdraw failed: %r\n"); + threadexitsall("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(); + pids[Timep] = proccreate(timeproc, nil, 16000); + pids[Mousep] = proccreate(mouseproc, nil, 16000); + if(initload) + for(i = 0; i < nmaildirs; i++) + loadmboxfaces(maildirs[i]); + faceproc(); + fprint(2, "faces: %s process exits\n", procnames[Mainp]); + killall(nil); +} diff --git a/src/cmd/faces/mkfile b/src/cmd/faces/mkfile new file mode 100644 index 00000000..58cc4d25 --- /dev/null +++ b/src/cmd/faces/mkfile @@ -0,0 +1,26 @@ +<$PLAN9/src/mkhdr + +# default domain for faces, overridden by $facedom +DEFAULT=\"astro\" + +TARG=faces + +OFILES=main.$O\ + facedb.$O\ + plumb.$O\ + util.$O\ + +HFILES=faces.h\ + +BIN=$PLAN9/bin + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +<$PLAN9/src/mkone +CFLAGS=$CFLAGS '-DDEFAULT='$DEFAULT + +$O.dblook: dblook.$O facedb.$O util.$O + $LD -o $target $prereq diff --git a/src/cmd/faces/plumb.c b/src/cmd/faces/plumb.c new file mode 100644 index 00000000..a61e2d81 --- /dev/null +++ b/src/cmd/faces/plumb.c @@ -0,0 +1,398 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <plumb.h> +#include <regexp.h> +#include <bio.h> +#include <9pclient.h> +#include "faces.h" + +static int showfd = -1; +static int seefd = -1; +static int logfd = -1; +static char *user; +static char *logtag; + +char **maildirs; +int nmaildirs; + +void +initplumb(void) +{ + showfd = plumbopen("send", OWRITE); + seefd = plumbopen("seemail", OREAD); + + if(seefd < 0){ + logfd = open(unsharp("#9/log/mail"), OREAD); + seek(logfd, 0LL, 2); + user = getenv("user"); + if(user == nil){ + fprint(2, "faces: can't find user name: %r\n"); + exits("$user"); + } + logtag = emalloc(32+strlen(user)+1); + sprint(logtag, " delivered %s From ", user); + } +} + +void +addmaildir(char *dir) +{ + maildirs = erealloc(maildirs, (nmaildirs+1)*sizeof(char*)); + maildirs[nmaildirs++] = dir; +} + +char* +attr(Face *f) +{ + static char buf[128]; + + if(f->str[Sdigest]){ + snprint(buf, sizeof buf, "digest=%s", f->str[Sdigest]); + return buf; + } + return nil; +} + +void +showmail(Face *f) +{ + Plumbmsg pm; + Plumbattr a; + char *s; + + if(showfd<0 || f->str[Sshow]==nil || f->str[Sshow][0]=='\0') + return; + s = emalloc(strlen("/mail/fs")+1+strlen(f->str[Sshow])); + sprint(s,"/mail/fs/%s",f->str[Sshow]); + pm.src = "faces"; + pm.dst = "showmail"; + pm.wdir = "/mail/fs"; + pm.type = "text"; + a.name = "digest"; + a.value = f->str[Sdigest]; + a.next = nil; + pm.attr = &a; + pm.ndata = strlen(s); + pm.data = s; + plumbsend(showfd,&pm); +} + +char* +value(Plumbattr *attr, char *key, char *def) +{ + char *v; + + v = plumblookup(attr, key); + if(v) + return v; + return def; +} + +void +setname(Face *f, char *sender) +{ + char *at, *bang; + char *p; + + /* works with UTF-8, although it's written as ASCII */ + for(p=sender; *p!='\0'; p++) + *p = tolower(*p); + f->str[Suser] = sender; + at = strchr(sender, '@'); + if(at){ + *at++ = '\0'; + f->str[Sdomain] = estrdup(at); + return; + } + bang = strchr(sender, '!'); + if(bang){ + *bang++ = '\0'; + f->str[Suser] = estrdup(bang); + f->str[Sdomain] = sender; + return; + } +} + +int +getc(void) +{ + static uchar buf[512]; + static int nbuf = 0; + static int i = 0; + + while(i == nbuf){ + i = 0; + nbuf = read(logfd, buf, sizeof buf); + if(nbuf == 0){ + sleep(15000); + continue; + } + if(nbuf < 0) + return -1; + } + return buf[i++]; +} + +char* +getline(char *buf, int n) +{ + int i, c; + + for(i=0; i<n-1; i++){ + c = getc(); + if(c <= 0) + return nil; + if(c == '\n') + break; + buf[i] = c; + } + buf[i] = '\0'; + return buf; +} + +static char* months[] = { + "jan", "feb", "mar", "apr", + "may", "jun", "jul", "aug", + "sep", "oct", "nov", "dec" +}; + +static int +getmon(char *s) +{ + int i; + + for(i=0; i<nelem(months); i++) + if(cistrcmp(months[i], s) == 0) + return i; + return -1; +} + +/* Fri Jul 23 14:05:14 EDT 1999 */ +ulong +parsedatev(char **a) +{ + char *p; + Tm tm; + + memset(&tm, 0, sizeof tm); + if((tm.mon=getmon(a[1])) == -1) + goto Err; + tm.mday = strtol(a[2], &p, 10); + if(*p != '\0') + goto Err; + tm.hour = strtol(a[3], &p, 10); + if(*p != ':') + goto Err; + tm.min = strtol(p+1, &p, 10); + if(*p != ':') + goto Err; + tm.sec = strtol(p+1, &p, 10); + if(*p != '\0') + goto Err; + if(strlen(a[4]) != 3) + goto Err; + strcpy(tm.zone, a[4]); + if(strlen(a[5]) != 4) + goto Err; + tm.year = strtol(a[5], &p, 10); + if(*p != '\0') + goto Err; + tm.year -= 1900; + return tm2sec(&tm); +Err: + return time(0); +} + +ulong +parsedate(char *s) +{ + char *f[10]; + int nf; + + nf = getfields(s, f, nelem(f), 1, " "); + if(nf < 6) + return time(0); + return parsedatev(f); +} + +/* achille Jul 23 14:05:15 delivered jmk From ms.com!bub Fri Jul 23 14:05:14 EDT 1999 (plan9.bell-labs.com!jmk) 1352 */ +/* achille Oct 26 13:45:42 remote local!rsc From rsc Sat Oct 26 13:45:41 EDT 2002 (rsc) 170 */ +int +parselog(char *s, char **sender, ulong *xtime) +{ + char *f[20]; + int nf; + + nf = getfields(s, f, nelem(f), 1, " "); + if(nf < 14) + return 0; + if(strcmp(f[4], "delivered") == 0 && strcmp(f[5], user) == 0) + goto Found; + if(strcmp(f[4], "remote") == 0 && strncmp(f[5], "local!", 6) == 0 && strcmp(f[5]+6, user) == 0) + goto Found; + return 0; + +Found: + *sender = estrdup(f[7]); + *xtime = parsedatev(&f[8]); + return 1; +} + +int +logrecv(char **sender, ulong *xtime) +{ + char buf[4096]; + + for(;;){ + if(getline(buf, sizeof buf) == nil) + return 0; + if(parselog(buf, sender, xtime)) + return 1; + } + return -1; +} + +char* +tweakdate(char *d) +{ + char e[8]; + + /* d, date = "Mon Aug 2 23:46:55 EDT 1999" */ + + if(strlen(d) < strlen("Mon Aug 2 23:46:55 EDT 1999")) + return estrdup(""); + if(strncmp(date, d, 4+4+3) == 0) + snprint(e, sizeof e, "%.5s", d+4+4+3); /* 23:46 */ + else + snprint(e, sizeof e, "%.6s", d+4); /* Aug 2 */ + return estrdup(e); +} + +Face* +nextface(void) +{ + int i; + Face *f; + Plumbmsg *m; + char *t, *senderp, *showmailp, *digestp; + ulong xtime; + + f = emalloc(sizeof(Face)); + for(;;){ + if(seefd >= 0){ + m = plumbrecv(seefd); + if(m == nil) + killall("error on seemail plumb port"); + t = value(m->attr, "mailtype", ""); + if(strcmp(t, "delete") == 0) + delete(m->data, value(m->attr, "digest", nil)); + else if(strcmp(t, "new") != 0) + fprint(2, "faces: unknown plumb message type %s\n", t); + else for(i=0; i<nmaildirs; i++) { + if(strncmp(m->data,"/mail/fs/",strlen("/mail/fs/")) == 0) + m->data += strlen("/mail/fs/"); + if(strncmp(m->data, maildirs[i], strlen(maildirs[i])) == 0) + goto Found; + } + plumbfree(m); + continue; + + Found: + xtime = parsedate(value(m->attr, "date", date)); + digestp = value(m->attr, "digest", nil); + if(alreadyseen(digestp)){ + /* duplicate upas/fs can send duplicate messages */ + plumbfree(m); + continue; + } + senderp = estrdup(value(m->attr, "sender", "???")); + showmailp = estrdup(m->data); + if(digestp) + digestp = estrdup(digestp); + plumbfree(m); + }else{ + if(logrecv(&senderp, &xtime) <= 0) + killall("error reading log file"); + showmailp = estrdup(""); + digestp = nil; + } + setname(f, senderp); + f->time = xtime; + f->tm = *localtime(xtime); + f->str[Sshow] = showmailp; + f->str[Sdigest] = digestp; + return f; + } + return nil; +} + +char* +iline(char *data, char **pp) +{ + char *p; + + for(p=data; *p!='\0' && *p!='\n'; p++) + ; + if(*p == '\n') + *p++ = '\0'; + *pp = p; + return data; +} + +Face* +dirface(char *dir, char *num) +{ + Face *f; + char *from, *date; + char buf[1024], *info, *p, *digest; + int n; + ulong len; + CFid *fid; + +#if 0 + /* + * loadmbox leaves us in maildir, so we needn't + * walk /mail/fs/mbox for each face; this makes startup + * a fair bit quicker. + */ + if(getwd(pwd, sizeof pwd) != nil && strcmp(pwd, dir) == 0) + sprint(buf, "%s/info", num); + else + sprint(buf, "%s/%s/info", dir, num); +#endif + sprint(buf, "%s/%s/info", dir, num); + len = fsdirlen(upasfs, buf); + if(len <= 0) + return nil; + fid = fsopen(upasfs,buf, OREAD); + if(fid == nil) + return nil; + info = emalloc(len+1); + n = fsreadn(fid, info, len); + fsclose(fid); + if(n < 0){ + free(info); + return nil; + } + info[n] = '\0'; + f = emalloc(sizeof(Face)); + from = iline(info, &p); /* from */ + iline(p, &p); /* to */ + iline(p, &p); /* cc */ + iline(p, &p); /* replyto */ + date = iline(p, &p); /* date */ + setname(f, estrdup(from)); + f->time = parsedate(date); + f->tm = *localtime(f->time); + sprint(buf, "%s/%s", dir, num); + f->str[Sshow] = estrdup(buf); + iline(p, &p); /* subject */ + iline(p, &p); /* mime content type */ + iline(p, &p); /* mime disposition */ + iline(p, &p); /* filename */ + digest = iline(p, &p); /* digest */ + f->str[Sdigest] = estrdup(digest); + free(info); + return f; +} diff --git a/src/cmd/faces/util.c b/src/cmd/faces/util.c new file mode 100644 index 00000000..22f57549 --- /dev/null +++ b/src/cmd/faces/util.c @@ -0,0 +1,42 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <plumb.h> +#include <9pclient.h> +#include "faces.h" + +void* +emalloc(ulong sz) +{ + void *v; + v = malloc(sz); + if(v == nil) { + fprint(2, "out of memory allocating %ld\n", sz); + exits("mem"); + } + memset(v, 0, sz); + return v; +} + +void* +erealloc(void *v, ulong sz) +{ + v = realloc(v, sz); + if(v == nil) { + fprint(2, "out of memory allocating %ld\n", sz); + exits("mem"); + } + return v; +} + +char* +estrdup(char *s) +{ + char *t; + if((t = strdup(s)) == nil) { + fprint(2, "out of memory in strdup(%.10s)\n", s); + exits("mem"); + } + return t; +} + |