1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <plumb.h>
5 #include <regexp.h>
6 #include <event.h> /* for support routines only */
7 #include <bio.h>
8 #include "faces.h"
9
10 int history = 0; /* use old interface, showing history of mailbox rather than current state */
11 int initload = 0; /* initialize program with contents of mail box */
12
13 enum
14 {
15 Facesep = 6, /* must be even to avoid damaging background stipple */
16 Infolines = 9,
17
18 HhmmTime = 18*60*60, /* max age of face to display hh:mm time */
19 };
20
21 enum
22 {
23 Mainp,
24 Timep,
25 Mousep,
26 NPROC
27 };
28
29 int pids[NPROC];
30 char *procnames[] = {
31 "main",
32 "time",
33 "mouse"
34 };
35
36 Rectangle leftright = {0, 0, 20, 15};
37
38 uchar leftdata[] = {
39 0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
40 0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
41 0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
42 0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
43 0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
44 0x80, 0x00, 0x00, 0x80, 0x00
45 };
46
47 uchar rightdata[] = {
48 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
49 0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
50 0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
51 0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
52 0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
53 0x18, 0x00, 0x00, 0x10, 0x00
54 };
55
56 Image *blue; /* full arrow */
57 Image *bgrnd; /* pale blue background color */
58 Image *left; /* left-pointing arrow mask */
59 Image *right; /* right-pointing arrow mask */
60 Font *tinyfont;
61 Font *mediumfont;
62 Font *datefont;
63 int first, last; /* first and last visible face; last is first invisible */
64 int nfaces;
65 int mousefd;
66 int nacross;
67 int ndown;
68
69 char date[64];
70 Face **faces;
71 char *maildir = "/mail/fs/mbox";
72 ulong now;
73
74 Point datep = { 8, 6 };
75 Point facep = { 8, 6+0+4 }; /* 0 updated to datefont->height in init() */
76 Point enddate; /* where date ends on display; used to place arrows */
77 Rectangle leftr; /* location of left arrow on display */
78 Rectangle rightr; /* location of right arrow on display */
79 void updatetimes(void);
80
81 void
setdate(void)82 setdate(void)
83 {
84 now = time(nil);
85 strcpy(date, ctime(now));
86 date[4+4+3+5] = '\0'; /* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
87 }
88
89 void
init(void)90 init(void)
91 {
92 mousefd = open("/dev/mouse", OREAD);
93 if(mousefd < 0){
94 fprint(2, "faces: can't open mouse: %r\n");
95 exits("mouse");
96 }
97 initplumb();
98
99 /* make background color */
100 bgrnd = allocimagemix(display, DPalebluegreen, DWhite);
101 blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x008888FF); /* blue-green */
102 left = allocimage(display, leftright, GREY1, 0, DWhite);
103 right = allocimage(display, leftright, GREY1, 0, DWhite);
104 if(bgrnd==nil || blue==nil || left==nil || right==nil){
105 fprint(2, "faces: can't create images: %r\n");
106 exits("image");
107 }
108
109 loadimage(left, leftright, leftdata, sizeof leftdata);
110 loadimage(right, leftright, rightdata, sizeof rightdata);
111
112 /* initialize little fonts */
113 tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
114 if(tinyfont == nil)
115 tinyfont = font;
116 mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
117 if(mediumfont == nil)
118 mediumfont = font;
119 datefont = font;
120
121 facep.y += datefont->height;
122 if(datefont->height & 1) /* stipple parity */
123 facep.y++;
124 faces = nil;
125 }
126
127 void
drawtime(void)128 drawtime(void)
129 {
130 Rectangle r;
131
132 r.min = addpt(screen->r.min, datep);
133 if(eqpt(enddate, ZP)){
134 enddate = r.min;
135 enddate.x += stringwidth(datefont, "Wed May 30 22:54"); /* nice wide string */
136 enddate.x += Facesep; /* for safety */
137 }
138 r.max.x = enddate.x;
139 r.max.y = enddate.y+datefont->height;
140 draw(screen, r, bgrnd, nil, ZP);
141 string(screen, r.min, display->black, ZP, datefont, date);
142 }
143
144 void
timeproc(void)145 timeproc(void)
146 {
147 for(;;){
148 lockdisplay(display);
149 drawtime();
150 updatetimes();
151 flushimage(display, 1);
152 unlockdisplay(display);
153 now = time(nil);
154 sleep(((60 - now%60) + 1)*1000); /* wait for minute to change */
155 setdate();
156 }
157 }
158
159 int
alreadyseen(char * digest)160 alreadyseen(char *digest)
161 {
162 int i;
163 Face *f;
164
165 if(!digest)
166 return 0;
167
168 /* can do accurate check */
169 for(i=0; i<nfaces; i++){
170 f = faces[i];
171 if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
172 return 1;
173 }
174 return 0;
175 }
176
177 int
torune(Rune * r,char * s,int nr)178 torune(Rune *r, char *s, int nr)
179 {
180 int i;
181
182 for(i=0; i<nr-1 && *s!='\0'; i++)
183 s += chartorune(r+i, s);
184 r[i] = L'\0';
185 return i;
186 }
187
188 void
center(Font * f,Point p,char * s,Image * color)189 center(Font *f, Point p, char *s, Image *color)
190 {
191 int i, n, dx;
192 Rune rbuf[32];
193 char sbuf[32*UTFmax+1];
194
195 dx = stringwidth(f, s);
196 if(dx > Facesize){
197 n = torune(rbuf, s, nelem(rbuf));
198 for(i=0; i<n; i++){
199 dx = runestringnwidth(f, rbuf, i+1);
200 if(dx > Facesize)
201 break;
202 }
203 sprint(sbuf, "%.*S", i, rbuf);
204 s = sbuf;
205 dx = stringwidth(f, s);
206 }
207 p.x += (Facesize-dx)/2;
208 string(screen, p, color, ZP, f, s);
209 }
210
211 Rectangle
facerect(int index)212 facerect(int index) /* index is geometric; 0 is always upper left face */
213 {
214 Rectangle r;
215 int x, y;
216
217 x = index % nacross;
218 y = index / nacross;
219 r.min = addpt(screen->r.min, facep);
220 r.min.x += x*(Facesize+Facesep);
221 r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
222 r.max = addpt(r.min, Pt(Facesize, Facesize));
223 r.max.y += 2*mediumfont->height;
224 /* simple fix to avoid drawing off screen, allowing customers to use position */
225 if(index<0 || index>=nacross*ndown)
226 r.max.x = r.min.x;
227 return r;
228 }
229
230 static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
231 char*
facetime(Face * f,int * recent)232 facetime(Face *f, int *recent)
233 {
234 static char buf[30];
235
236 if((long)(now - f->time) > HhmmTime){
237 *recent = 0;
238 sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
239 return buf;
240 }else{
241 *recent = 1;
242 sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
243 return buf;
244 }
245 }
246
247 void
drawface(Face * f,int i)248 drawface(Face *f, int i)
249 {
250 char *tstr;
251 Rectangle r;
252 Point p;
253
254 if(f == nil)
255 return;
256 if(i<first || i>=last)
257 return;
258 r = facerect(i-first);
259 draw(screen, r, bgrnd, nil, ZP);
260 draw(screen, r, f->bit, f->mask, ZP);
261 r.min.y += Facesize;
262 center(mediumfont, r.min, f->str[Suser], display->black);
263 r.min.y += mediumfont->height;
264 tstr = facetime(f, &f->recent);
265 center(mediumfont, r.min, tstr, display->black);
266 if(f->unknown){
267 r.min.y -= mediumfont->height + tinyfont->height + 2;
268 for(p.x=-1; p.x<=1; p.x++)
269 for(p.y=-1; p.y<=1; p.y++)
270 center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
271 center(tinyfont, r.min, f->str[Sdomain], display->black);
272 }
273 }
274
275 void
updatetimes(void)276 updatetimes(void)
277 {
278 int i;
279 Face *f;
280
281 for(i=0; i<nfaces; i++){
282 f = faces[i];
283 if(f == nil)
284 continue;
285 if(((long)(now - f->time) <= HhmmTime) != f->recent)
286 drawface(f, i);
287 }
288 }
289
290 void
setlast(void)291 setlast(void)
292 {
293 last = first+nacross*ndown;
294 if(last > nfaces)
295 last = nfaces;
296 }
297
298 void
drawarrows(void)299 drawarrows(void)
300 {
301 Point p;
302
303 p = enddate;
304 p.x += Facesep;
305 if(p.x & 1)
306 p.x++; /* align background texture */
307 leftr = rectaddpt(leftright, p);
308 p.x += Dx(leftright) + Facesep;
309 rightr = rectaddpt(leftright, p);
310 draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
311 draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
312 }
313
314 void
addface(Face * f)315 addface(Face *f) /* always adds at 0 */
316 {
317 Face **ofaces;
318 Rectangle r0, r1, r;
319 int y, nx, ny;
320
321 if(f == nil)
322 return;
323 lockdisplay(display);
324 if(first != 0){
325 first = 0;
326 resized();
327 }
328 findbit(f);
329
330 nx = nacross;
331 ny = (nfaces+(nx-1)) / nx;
332
333 for(y=ny; y>=0; y--){
334 /* move them along */
335 r0 = facerect(y*nx+0);
336 r1 = facerect(y*nx+1);
337 r = r1;
338 r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
339 draw(screen, r, screen, nil, r0.min);
340 /* copy one down from row above */
341 if(y != 0){
342 r = facerect((y-1)*nx+nx-1);
343 draw(screen, r0, screen, nil, r.min);
344 }
345 }
346
347 ofaces = faces;
348 faces = emalloc((nfaces+1)*sizeof(Face*));
349 memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
350 free(ofaces);
351 nfaces++;
352 setlast();
353 drawarrows();
354 faces[0] = f;
355 drawface(f, 0);
356 flushimage(display, 1);
357 unlockdisplay(display);
358 }
359
360 void
loadmboxfaces(char * maildir)361 loadmboxfaces(char *maildir)
362 {
363 int dirfd;
364 Dir *d;
365 int i, n;
366
367 dirfd = open(maildir, OREAD);
368 if(dirfd >= 0){
369 chdir(maildir);
370 while((n = dirread(dirfd, &d)) > 0){
371 for(i=0; i<n; i++)
372 addface(dirface(maildir, d[i].name));
373 free(d);
374 }
375 close(dirfd);
376 }
377 }
378
379 void
freeface(Face * f)380 freeface(Face *f)
381 {
382 int i;
383
384 if(f->file!=nil && f->bit!=f->file->image)
385 freeimage(f->bit);
386 freefacefile(f->file);
387 for(i=0; i<Nstring; i++)
388 free(f->str[i]);
389 free(f);
390 }
391
392 void
delface(int j)393 delface(int j)
394 {
395 Rectangle r0, r1, r;
396 int nx, ny, x, y;
397
398 if(j < first)
399 first--;
400 else if(j < last){
401 nx = nacross;
402 ny = (nfaces+(nx-1)) / nx;
403 x = (j-first)%nx;
404 for(y=(j-first)/nx; y<ny; y++){
405 if(x != nx-1){
406 /* move them along */
407 r0 = facerect(y*nx+x);
408 r1 = facerect(y*nx+x+1);
409 r = r0;
410 r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
411 draw(screen, r, screen, nil, r1.min);
412 }
413 if(y != ny-1){
414 /* copy one up from row below */
415 r = facerect((y+1)*nx);
416 draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
417 }
418 x = 0;
419 }
420 if(last < nfaces) /* first off-screen becomes visible */
421 drawface(faces[last], last-1);
422 else{
423 /* clear final spot */
424 r = facerect(last-first-1);
425 draw(screen, r, bgrnd, nil, r.min);
426 }
427 }
428 freeface(faces[j]);
429 memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
430 nfaces--;
431 setlast();
432 drawarrows();
433 }
434
435 void
dodelete(int i)436 dodelete(int i)
437 {
438 Face *f;
439
440 f = faces[i];
441 if(history){
442 free(f->str[Sshow]);
443 f->str[Sshow] = estrdup("");
444 }else{
445 delface(i);
446 flushimage(display, 1);
447 }
448 }
449
450 void
delete(char * s,char * digest)451 delete(char *s, char *digest)
452 {
453 int i;
454 Face *f;
455
456 lockdisplay(display);
457 for(i=0; i<nfaces; i++){
458 f = faces[i];
459 if(digest != nil){
460 if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
461 dodelete(i);
462 break;
463 }
464 }else{
465 if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
466 dodelete(i);
467 break;
468 }
469 }
470 }
471 unlockdisplay(display);
472 }
473
474 void
faceproc(void)475 faceproc(void)
476 {
477 for(;;)
478 addface(nextface());
479 }
480
481 void
resized(void)482 resized(void)
483 {
484 int i;
485
486 nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
487 for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
488 ;
489 setlast();
490 draw(screen, screen->r, bgrnd, nil, ZP);
491 enddate = ZP;
492 drawtime();
493 for(i=0; i<nfaces; i++)
494 drawface(faces[i], i);
495 drawarrows();
496 flushimage(display, 1);
497 }
498
499 void
eresized(int new)500 eresized(int new)
501 {
502 lockdisplay(display);
503 if(new && getwindow(display, Refnone) < 0) {
504 fprint(2, "can't reattach to window\n");
505 killall("reattach");
506 }
507 resized();
508 unlockdisplay(display);
509 }
510
511 int
getmouse(Mouse * m)512 getmouse(Mouse *m)
513 {
514 int n;
515 static int eof;
516 char buf[128];
517
518 if(eof)
519 return 0;
520 for(;;){
521 n = read(mousefd, buf, sizeof(buf));
522 if(n <= 0){
523 /* so callers needn't check return value every time */
524 eof = 1;
525 m->buttons = 0;
526 return 0;
527 }
528 n = eatomouse(m, buf, n);
529 if(n > 0)
530 return 1;
531 }
532 }
533
534 enum
535 {
536 Clicksize = 3, /* pixels */
537 };
538
539 int
scroll(int but,Point p)540 scroll(int but, Point p)
541 {
542 int delta;
543
544 delta = 0;
545 lockdisplay(display);
546 if(ptinrect(p, leftr) && first>0){
547 if(but == 2)
548 delta = -first;
549 else{
550 delta = nacross;
551 if(delta > first)
552 delta = first;
553 delta = -delta;
554 }
555 }else if(ptinrect(p, rightr) && last<nfaces){
556 if(but == 2)
557 delta = (nfaces-nacross*ndown) - first;
558 else{
559 delta = nacross;
560 if(delta > nfaces-last)
561 delta = nfaces-last;
562 }
563 }
564 first += delta;
565 last += delta;
566 unlockdisplay(display);
567 if(delta)
568 eresized(0);
569 return delta;
570 }
571
572 void
click(int button,Mouse * m)573 click(int button, Mouse *m)
574 {
575 Point p;
576 int i;
577
578 p = m->xy;
579 while(m->buttons == (1<<(button-1)))
580 getmouse(m);
581 if(m->buttons)
582 return;
583 if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
584 return;
585 switch(button){
586 case 1:
587 if(scroll(1, p))
588 break;
589 if(history){
590 /* click clears display */
591 lockdisplay(display);
592 for(i=0; i<nfaces; i++)
593 freeface(faces[i]);
594 free(faces);
595 faces=nil;
596 nfaces = 0;
597 unlockdisplay(display);
598 eresized(0);
599 return;
600 }else{
601 for(i=first; i<last; i++) /* clear vwhois faces */
602 if(ptinrect(p, facerect(i-first))
603 && strstr(faces[i]->str[Sshow], "/XXXvwhois")){
604 delface(i);
605 flushimage(display, 1);
606 }
607 }
608 break;
609 case 2:
610 scroll(2, p);
611 break;
612 case 3:
613 scroll(3, p);
614 lockdisplay(display);
615 for(i=first; i<last; i++)
616 if(ptinrect(p, facerect(i-first))){
617 showmail(faces[i]);
618 break;
619 }
620 unlockdisplay(display);
621 break;
622 }
623 }
624
625 void
mouseproc(void)626 mouseproc(void)
627 {
628 Mouse mouse;
629
630 while(getmouse(&mouse)){
631 if(mouse.buttons == 1)
632 click(1, &mouse);
633 else if(mouse.buttons == 2)
634 click(2, &mouse);
635 else if(mouse.buttons == 4)
636 click(3, &mouse);
637
638 while(mouse.buttons)
639 getmouse(&mouse);
640 }
641 }
642
643 void
killall(char * s)644 killall(char *s)
645 {
646 int i, pid;
647
648 pid = getpid();
649 for(i=0; i<NPROC; i++)
650 if(pids[i] && pids[i]!=pid)
651 postnote(PNPROC, pids[i], "kill");
652 exits(s);
653 }
654
655 void
startproc(void (* f)(void),int index)656 startproc(void (*f)(void), int index)
657 {
658 int pid;
659
660 switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
661 case -1:
662 fprint(2, "faces: fork failed: %r\n");
663 killall("fork failed");
664 case 0:
665 f();
666 fprint(2, "faces: %s process exits\n", procnames[index]);
667 if(index >= 0)
668 killall("process died");
669 exits(nil);
670 }
671 if(index >= 0)
672 pids[index] = pid;
673 }
674
675 void
usage(void)676 usage(void)
677 {
678 fprint(2, "usage: faces [-hi] [-m maildir]\n");
679 exits("usage");
680 }
681
682 void
main(int argc,char * argv[])683 main(int argc, char *argv[])
684 {
685 int i;
686
687 ARGBEGIN{
688 case 'h':
689 history++;
690 break;
691 case 'i':
692 initload++;
693 break;
694 case 'm':
695 addmaildir(EARGF(usage()));
696 maildir = nil;
697 break;
698 default:
699 usage();
700 }ARGEND
701
702 if(initdraw(nil, nil, "faces") < 0){
703 fprint(2, "faces: initdraw failed: %r\n");
704 exits("initdraw");
705 }
706 if(maildir)
707 addmaildir(maildir);
708 init();
709 unlockdisplay(display); /* initdraw leaves it locked */
710 display->locking = 1; /* tell library we're using the display lock */
711 setdate();
712 eresized(0);
713
714 pids[Mainp] = getpid();
715 startproc(timeproc, Timep);
716 startproc(mouseproc, Mousep);
717 if(initload)
718 for(i = 0; i < nmaildirs; i++)
719 loadmboxfaces(maildirs[i]);
720 faceproc();
721 fprint(2, "faces: %s process exits\n", procnames[Mainp]);
722 killall(nil);
723 }
724