#include #include #include #include #include #include /* for support routines only */ #include #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, Kbdp, Infop, NPROC }; int pids[NPROC]; char *procnames[] = { "main", "time", "mouse", "info", "keyboard", }; 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; /* background color */ Image *red; /* red mask */ 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 kbdfd; int pfd[2]; 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 */ Rectangle infor; /* location of information */ Rectangle delrect; /* locatation of Del text */ char selstr[128]; /* selection string */ int selected = -1; /* selected face */ void updatetimes(void); Rectangle facerect(int index); void setdate(void) { Tm *tm; now = time(nil); tm=localtime(now); /* strcpy(date, smprint("%.4d-%.2d-%.2d:%.2d:%.2d", tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min)); */ strcpy(date, smprint("%.2d:%.2d", tm->hour, tm->min)); } void init(void) { int fd; kbdfd = open("/dev/cons", ORDWR|OCEXEC); fd = open("/dev/consctl", OWRITE|OCEXEC); if(kbdfd < 0 || fd < 0 || write(fd, "rawon", 5) < 0){ fprint(2, "faces: can't open keyboard: %r\n"); exits("keyboard"); } if(pipe(pfd) < 0){ fprint(2, "faces: pipe: %r\n"); exits("pipe"); } 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, 0xBBBBBBBB, DWhite); blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF); /* blue-green */ red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xff00007f); left = allocimage(display, leftright, GREY1, 0, DWhite); right = allocimage(display, leftright, GREY1, 0, DWhite); if(bgrnd==nil || blue==nil || red == 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/lucidasans/unicode.6.font"); if(mediumfont == nil) mediumfont = font; datefont = mediumfont; /* facep.y = facep.y*2+datefont->height*2; /* anothy; two rows */ facep.x += leftright.max.x; 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, "88:88"); 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; istr[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 Facesize){ n = torune(rbuf, s, nelem(rbuf)); for(i=0; i 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.min.y += y*(Facesize+Facesep+mediumfont->height); r.min.y += y*(Facesize+Facesep); r.max = addpt(r.min, Pt(Facesize, Facesize)); // r.max.y += 2*mediumfont->height; // r.max.y += 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, Image *bg) { char *tstr; Rectangle r, r0; Point p; if(f == nil) return; if(i=last) return; r = facerect(i-first); r0 = r; draw(screen, r, bg, 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; r.min.y -= 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, addpt(r.min, p), f->str[Suser], display->white); // center(tinyfont, r.min, f->str[Sdomain], display->black); center(tinyfont, r.min, f->str[Suser], display->black); } if(i == selected){ border(screen, insetrect(r0, -2), -2, blue, ZP); stringbg(screen, infor.min, display->black, ZP, datefont, selstr, bgrnd, ZP); } } void updatetimes(void) { int i; Face *f; for(i=0; itime) <= HhmmTime) != f->recent) drawface(f, i, bgrnd); } } void setlast(void) { last = first+nacross*ndown; if(last > nfaces) last = nfaces; } void drawarrows(void) { Point p; p = screen->r.min; p.x += Facesep; p.y += 6+datefont->height+4; if(p.x & 1) p.x++; /* align background texture */ leftr = rectaddpt(leftright, p); /* p.x += Dx(leftright) + Facesep; */ p.y += Dy(leftright) + Facesep; rightr = rectaddpt(leftright, p); draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min); draw(screen, rightr, last=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, bgrnd); selected = sel; if(selected != -1) drawface(faces[selected], selected, bgrnd); 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; ifile!=nil && f->bit!=f->file->image) freeimage(f->bit); freefacefile(f->file); for(i=0; istr[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; ystr[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; istr[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); */ nacross = (Dx(screen->r)-facep.x+Facesep)/(Facesize+Facesep); if(nacross==0) nacross=1; for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++) ; setlast(); draw(screen, screen->r, bgrnd, nil, ZP); enddate = ZP; drawtime(); for(i=0; ibuttons = 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*ndown; if(delta > first) delta = first; delta = -delta; } }else if(ptinrect(p, rightr) && last nfaces-last) delta = nfaces-last; } } first += delta; last += delta; unlockdisplay(display); if(delta) eresized(0); return delta; } enum {Fields = 4}; void infodel(char **f, int) { Point p; if(selected == -1) return; p = Pt(atoi(f[2]), atoi(f[3])); if(!ptinrect(p, delrect)) return; drawface(faces[selected], selected, red); flushimage(display, 1); remove(faces[selected]->str[Sshow]); } void infoclear(void) { if(selected != -1){ draw(screen, infor, bgrnd, nil, ZP); border(screen, insetrect(facerect(selected-first), -2), -2, bgrnd, ZP); // error!? } selected = -1; } char* infoget(char *s, int l, int i, char *field) { char buf[128]; int fd, n; snprint(buf, sizeof buf, "%s/%s", faces[i]->str[Sshow], field); fd = open(buf, OREAD); if(fd < 0){ fail: snprint(s, l, "%d", i); return s; } n = read(fd, s, l-1); close(fd); if(n < 0) goto fail; s[n] = 0; return s; } void infoset(char **f) { Point p, q; char buf[80]; int i; // hasn't been set yet. if(leftr.max.y == 0) return; i = atoi(f[1]); infodel(f, i); infoclear(); if(i == -1) return; infoget(buf, sizeof buf, i, "subject"); snprint(selstr, sizeof selstr, "Del | %-.80s", buf); p = addpt(Pt(0, screen->r.min.y), Pt(leftr.max.x, datep.y)); p = addpt(p, Pt(datefont->height*6, 0)); delrect = Rpt(p, addpt(p, Pt(stringwidth(datefont, "Del"), datefont->height))); q = addpt(p, Pt(stringwidth(datefont, selstr), datefont->height)); infor = Rpt(p, q); stringbg(screen, p, display->black, ZP, datefont, selstr, bgrnd, ZP); border(screen, insetrect(facerect(i-first), -2), -2, blue, ZP); // error!? selected = i; } void infoproc(void) { int n; char buf[128], *f[Fields]; // close(pfd[0]); while((n = read(pfd[1], buf, sizeof buf-1)) > 0){ buf[n] = 0; if(getfields(buf, f, Fields, 0, "\001") != Fields) continue; lockdisplay(display); infoset(f); flushimage(display, 1); unlockdisplay(display); } } void infosend(int i, Point p) { char buf[50]; if(i == -1 && selected == -1) return; snprint(buf, sizeof buf, "set\001%d\001%d\001%d\n", i, p.x, p.y); write(pfd[0], buf, strlen(buf)); } void click(int button, Mouse *m) { Point p; int i; static int lasti; 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; infosend(-1, ZP); if(history){ /* click clears display */ lockdisplay(display); for(i=0; istr[Sshow], "/XXXvwhois")){ delface(i); flushimage(display, 1); } } break; case 2: scroll(2, p); for(i=first; i 0) for(i = 0; i < n; i++) if(strchr("q", buf[i])) return; } void killall(char *s) { int i, pid; pid = getpid(); for(i=0; i= 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); startproc(kbdproc, Kbdp); startproc(infoproc, Infop); // close(pfd[1]); if(initload) for(i = 0; i < nmaildirs; i++) loadmboxfaces(maildirs[i]); faceproc(); fprint(2, "faces: %s process exits\n", procnames[Mainp]); killall(nil); }