#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"
enum
{
Facesep = 6, /* must be even to avoid damaging background stipple */
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 updown = {0, 0, 16, 16};
uchar updata[] = {
0x01, 0x80,
0x03, 0xc0,
0x07, 0xe0,
0x0f, 0xf0,
0x1f, 0xf8,
0x3f, 0xfc,
0x7f, 0xfe,
0xff, 0xff,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0
};
uchar downdata[] = {
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0x03, 0xc0,
0xff, 0xff,
0x7f, 0xfe,
0x3f, 0xfc,
0x1f, 0xf8,
0x0f, 0xf0,
0x07, 0xe0,
0x03, 0xc0,
0x01, 0x80
};
Image *blue; /* full arrow */
Image *bgrnd; /* background color */
Image *red; /* red mask */
Image *up; /* up-pointing arrow mask */
Image *down; /* down-pointing arrow mask */
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 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 upr; /* location of up arrow on display */
Rectangle downr; /* location of down arrow on display */
Rectangle infor; /* location of information */
Rectangle delrect; /* locatation of Del text */
char selstr[128]; /* selection string */
int selected = -1; /* selected face */
Rectangle facerect(int index);
char* infoget(char *, int, int, char *);
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, "inbox: can't open keyboard: %r\n");
exits("keyboard");
}
if(pipe(pfd) < 0){
fprint(2, "inbox: pipe: %r\n");
exits("pipe");
}
mousefd = open("/dev/mouse", OREAD);
if(mousefd < 0){
fprint(2, "inbox: 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);
up = allocimage(display, updown, GREY1, 0, DWhite);
down = allocimage(display, updown, GREY1, 0, DWhite);
if(bgrnd==nil || blue==nil || red == nil || up==nil || down==nil){
fprint(2, "inbox: can't create images: %r\n");
exits("image");
}
loadimage(up, updown, updata, sizeof updata);
loadimage(down, updown, downdata, sizeof downdata);
/* initialize little fonts */
// mediumfont = openfont(display, "/lib/font/bit/dejavu/DejaVuSans/DejaVuSans.13.font");
mediumfont = openfont(display, "/lib/font/bit/lucida/unicode.7.font");
if(mediumfont == nil)
mediumfont = font;
datefont = mediumfont;
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();
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;
}
Rectangle
facerect(int index) /* index is geometric; 0 is always upper top face */
{
Rectangle r;
r.min = addpt(screen->r.min, facep);
r.min.y += index*(Facesize+Facesep);
r.max = addpt(r.min, Pt(Facesize, Facesize));
r.max.x += (Dx(screen->r)-facep.x+Facesep);
/* simple fix to avoid drawing off screen, allowing customers to use position */
if(index<0 || index>=ndown)
r.max.x = r.min.x;
return r;
}
static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
char*
facetime(Face *f)
{
static char buf[30];
sprint(buf, "%04d-%02d-%02d %02d:%02d",
f->tm.year+1900, f->tm.mon, f->tm.mday, f->tm.hour, f->tm.min);
return buf;
}
void
drawface(Face *f, int i, Image *bg)
{
char *tstr;
Rectangle r, r0;
char buf[80];
if(f == nil)
return;
if(i<first || 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.x += Facesize+Facesep;
// r.min.y -= 4; /* hack; manual adjustment for the font height/ascent */
r.min.y -= (mediumfont->height - mediumfont->ascent); /* also wrong. */
stringbg(screen, r.min, display->black, ZP, mediumfont, f->str[Suser], bgrnd, ZP);
r.min.y += mediumfont->height;
tstr = facetime(f);
stringbg(screen, r.min, display->black, ZP, mediumfont, tstr, bgrnd, ZP);
r.min.y += mediumfont->height;
stringbg(screen, r.min, display->black, ZP, mediumfont,
infoget(buf, sizeof buf, i, "subject"), bgrnd, ZP);
if(i == selected){
border(screen, insetrect(r0, -2), -2, blue, ZP);
stringbg(screen, infor.min, display->black, ZP, datefont, selstr, bgrnd, ZP);
}
}
void
setlast(void)
{
last = first+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 */
upr = rectaddpt(updown, p);
p.x += Dy(updown) + Facesep;
downr = rectaddpt(updown, p);
draw(screen, upr, first>0? blue : bgrnd, up, updown.min);
draw(screen, downr, last<nfaces? blue : bgrnd, down, updown.min);
}
void
addface(Face *f) /* always adds at 0 */
{
Face **ofaces;
Rectangle r0, r1, r;
int y, ny, sel;
if(f == nil)
return;
lockdisplay(display);
if(first != 0){
first = 0;
resized();
}
sel = -1;
if(selected != -1){
sel = selected+1;
draw(screen, infor, bgrnd, nil, ZP);
border(screen, insetrect(facerect(selected-first), -2), -2, bgrnd, ZP);
selected = -1;
}
findbit(f);
ny = nfaces;
for(y=ny; y>=0; y--){
/* move them along */
r0 = facerect(y+0);
r1 = facerect(y+1);
r = r1;
r.max.x = r.min.x;
draw(screen, r, screen, nil, r0.min);
/* copy one down from row above */
if(y != 0){
r = facerect(y-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; 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 ny, x, y;
if(j < first)
first--;
else if(j < last){
ny = nfaces;
x = 0;
for(y=j-first; y<ny; y++){
if(x != 0){
/* move them along */
r0 = facerect(y+x);
r1 = facerect(y+x+1);
r = r0;
r.max.x = r.min.x;
draw(screen, r, screen, nil, r1.min);
}
if(y != ny-1){
/* copy one up from row below */
r = facerect(y+1);
draw(screen, facerect(y), screen, nil, r.min);
}
x = 0;
}
if(last < nfaces) /* first off-screen becomes visible */
drawface(faces[last], last-1, bgrnd);
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)
{
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;
for(ndown=1; rectinrect(facerect(ndown), screen->r); ndown++)
;
setlast();
draw(screen, screen->r, bgrnd, nil, ZP);
enddate = ZP;
drawtime();
for(i=0; i<nfaces; i++)
drawface(faces[i], i, bgrnd);
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, upr) && first>0){
if(but == 2)
delta = -first;
else{
delta = ndown;
if(delta > first)
delta = first;
delta = -delta;
}
}else if(ptinrect(p, downr) && last<nfaces){
if(but == 2)
delta = (nfaces-ndown) - first;
else{
delta = ndown;
if(delta > 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(upr.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(upr.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);
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);
for(i=first; i<last; i++)
if(ptinrect(p, facerect(i-first)))
break;
if(i == last || lasti == i)
i = -1;
infosend(i, p);
lasti = i;
break;
case 3:
infosend(-1, ZP);
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
kbdproc(void)
{
int n, i;
char buf[20];
while((n = read(kbdfd, buf, sizeof buf)) > 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<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, "inbox: fork failed: %r\n");
killall("fork failed");
case 0:
f();
if(index != Kbdp)
fprint(2, "inbox: %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: inbox [-m maildir]\n");
exits("usage");
}
void
main(int argc, char *argv[])
{
int i;
ARGBEGIN{
case 'm':
addmaildir(EARGF(usage()));
maildir = nil;
break;
default:
usage();
}ARGEND
if(initdraw(nil, nil, "inbox") < 0){
fprint(2, "inbox: 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]);
for(i = 0; i < nmaildirs; i++)
loadmboxfaces(maildirs[i]);
faceproc();
fprint(2, "inbox: %s process exits\n", procnames[Mainp]);
killall(nil);
}
|