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