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 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 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 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 145 timeproc(void) 146 { 147 for(;;){ 148 lockdisplay(display); 149 drawtime(); 150 updatetimes(); 151 flushimage(display, 1); 152 unlockdisplay(display); 153 sleep(60000); 154 setdate(); 155 } 156 } 157 158 int 159 alreadyseen(char *digest) 160 { 161 int i; 162 Face *f; 163 164 if(!digest) 165 return 0; 166 167 /* can do accurate check */ 168 for(i=0; i<nfaces; i++){ 169 f = faces[i]; 170 if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0) 171 return 1; 172 } 173 return 0; 174 } 175 176 int 177 torune(Rune *r, char *s, int nr) 178 { 179 int i; 180 181 for(i=0; i<nr-1 && *s!='\0'; i++) 182 s += chartorune(r+i, s); 183 r[i] = L'\0'; 184 return i; 185 } 186 187 void 188 center(Font *f, Point p, char *s, Image *color) 189 { 190 int i, n, dx; 191 Rune rbuf[32]; 192 char sbuf[32*UTFmax+1]; 193 194 dx = stringwidth(f, s); 195 if(dx > Facesize){ 196 n = torune(rbuf, s, nelem(rbuf)); 197 for(i=0; i<n; i++){ 198 dx = runestringnwidth(f, rbuf, i+1); 199 if(dx > Facesize) 200 break; 201 } 202 sprint(sbuf, "%.*S", i, rbuf); 203 s = sbuf; 204 dx = stringwidth(f, s); 205 } 206 p.x += (Facesize-dx)/2; 207 string(screen, p, color, ZP, f, s); 208 } 209 210 Rectangle 211 facerect(int index) /* index is geometric; 0 is always upper left face */ 212 { 213 Rectangle r; 214 int x, y; 215 216 x = index % nacross; 217 y = index / nacross; 218 r.min = addpt(screen->r.min, facep); 219 r.min.x += x*(Facesize+Facesep); 220 r.min.y += y*(Facesize+Facesep+2*mediumfont->height); 221 r.max = addpt(r.min, Pt(Facesize, Facesize)); 222 r.max.y += 2*mediumfont->height; 223 /* simple fix to avoid drawing off screen, allowing customers to use position */ 224 if(index<0 || index>=nacross*ndown) 225 r.max.x = r.min.x; 226 return r; 227 } 228 229 static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec"; 230 char* 231 facetime(Face *f, int *recent) 232 { 233 static char buf[30]; 234 235 if((long)(now - f->time) > HhmmTime){ 236 *recent = 0; 237 sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday); 238 return buf; 239 }else{ 240 *recent = 1; 241 sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min); 242 return buf; 243 } 244 } 245 246 void 247 drawface(Face *f, int i) 248 { 249 char *tstr; 250 Rectangle r; 251 Point p; 252 253 if(f == nil) 254 return; 255 if(i<first || i>=last) 256 return; 257 r = facerect(i-first); 258 draw(screen, r, bgrnd, nil, ZP); 259 draw(screen, r, f->bit, f->mask, ZP); 260 r.min.y += Facesize; 261 center(mediumfont, r.min, f->str[Suser], display->black); 262 r.min.y += mediumfont->height; 263 tstr = facetime(f, &f->recent); 264 center(mediumfont, r.min, tstr, display->black); 265 if(f->unknown){ 266 r.min.y -= mediumfont->height + tinyfont->height + 2; 267 for(p.x=-1; p.x<=1; p.x++) 268 for(p.y=-1; p.y<=1; p.y++) 269 center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white); 270 center(tinyfont, r.min, f->str[Sdomain], display->black); 271 } 272 } 273 274 void 275 updatetimes(void) 276 { 277 int i; 278 Face *f; 279 280 for(i=0; i<nfaces; i++){ 281 f = faces[i]; 282 if(f == nil) 283 continue; 284 if(((long)(now - f->time) <= HhmmTime) != f->recent) 285 drawface(f, i); 286 } 287 } 288 289 void 290 setlast(void) 291 { 292 last = first+nacross*ndown; 293 if(last > nfaces) 294 last = nfaces; 295 } 296 297 void 298 drawarrows(void) 299 { 300 Point p; 301 302 p = enddate; 303 p.x += Facesep; 304 if(p.x & 1) 305 p.x++; /* align background texture */ 306 leftr = rectaddpt(leftright, p); 307 p.x += Dx(leftright) + Facesep; 308 rightr = rectaddpt(leftright, p); 309 draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min); 310 draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min); 311 } 312 313 void 314 addface(Face *f) /* always adds at 0 */ 315 { 316 Face **ofaces; 317 Rectangle r0, r1, r; 318 int y, nx, ny; 319 320 if(f == nil) 321 return; 322 lockdisplay(display); 323 if(first != 0){ 324 first = 0; 325 resized(); 326 } 327 findbit(f); 328 329 nx = nacross; 330 ny = (nfaces+(nx-1)) / nx; 331 332 for(y=ny; y>=0; y--){ 333 /* move them along */ 334 r0 = facerect(y*nx+0); 335 r1 = facerect(y*nx+1); 336 r = r1; 337 r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep); 338 draw(screen, r, screen, nil, r0.min); 339 /* copy one down from row above */ 340 if(y != 0){ 341 r = facerect((y-1)*nx+nx-1); 342 draw(screen, r0, screen, nil, r.min); 343 } 344 } 345 346 ofaces = faces; 347 faces = emalloc((nfaces+1)*sizeof(Face*)); 348 memmove(faces+1, ofaces, nfaces*(sizeof(Face*))); 349 free(ofaces); 350 nfaces++; 351 setlast(); 352 drawarrows(); 353 faces[0] = f; 354 drawface(f, 0); 355 flushimage(display, 1); 356 unlockdisplay(display); 357 } 358 359 void 360 loadmboxfaces(char *maildir) 361 { 362 int dirfd; 363 Dir *d; 364 int i, n; 365 366 dirfd = open(maildir, OREAD); 367 if(dirfd >= 0){ 368 chdir(maildir); 369 while((n = dirread(dirfd, &d)) > 0){ 370 for(i=0; i<n; i++) 371 addface(dirface(maildir, d[i].name)); 372 free(d); 373 } 374 close(dirfd); 375 } 376 } 377 378 void 379 freeface(Face *f) 380 { 381 int i; 382 383 if(f->file!=nil && f->bit!=f->file->image) 384 freeimage(f->bit); 385 freefacefile(f->file); 386 for(i=0; i<Nstring; i++) 387 free(f->str[i]); 388 free(f); 389 } 390 391 void 392 delface(int j) 393 { 394 Rectangle r0, r1, r; 395 int nx, ny, x, y; 396 397 if(j < first) 398 first--; 399 else if(j < last){ 400 nx = nacross; 401 ny = (nfaces+(nx-1)) / nx; 402 x = (j-first)%nx; 403 for(y=(j-first)/nx; y<ny; y++){ 404 if(x != nx-1){ 405 /* move them along */ 406 r0 = facerect(y*nx+x); 407 r1 = facerect(y*nx+x+1); 408 r = r0; 409 r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep); 410 draw(screen, r, screen, nil, r1.min); 411 } 412 if(y != ny-1){ 413 /* copy one up from row below */ 414 r = facerect((y+1)*nx); 415 draw(screen, facerect(y*nx+nx-1), screen, nil, r.min); 416 } 417 x = 0; 418 } 419 if(last < nfaces) /* first off-screen becomes visible */ 420 drawface(faces[last], last-1); 421 else{ 422 /* clear final spot */ 423 r = facerect(last-first-1); 424 draw(screen, r, bgrnd, nil, r.min); 425 } 426 } 427 freeface(faces[j]); 428 memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*)); 429 nfaces--; 430 setlast(); 431 drawarrows(); 432 } 433 434 void 435 dodelete(int i) 436 { 437 Face *f; 438 439 f = faces[i]; 440 if(history){ 441 free(f->str[Sshow]); 442 f->str[Sshow] = estrdup(""); 443 }else{ 444 delface(i); 445 flushimage(display, 1); 446 } 447 } 448 449 void 450 delete(char *s, char *digest) 451 { 452 int i; 453 Face *f; 454 455 lockdisplay(display); 456 for(i=0; i<nfaces; i++){ 457 f = faces[i]; 458 if(digest != nil){ 459 if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){ 460 dodelete(i); 461 break; 462 } 463 }else{ 464 if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){ 465 dodelete(i); 466 break; 467 } 468 } 469 } 470 unlockdisplay(display); 471 } 472 473 void 474 faceproc(void) 475 { 476 for(;;) 477 addface(nextface()); 478 } 479 480 void 481 resized(void) 482 { 483 int i; 484 485 nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep); 486 for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++) 487 ; 488 setlast(); 489 draw(screen, screen->r, bgrnd, nil, ZP); 490 enddate = ZP; 491 drawtime(); 492 for(i=0; i<nfaces; i++) 493 drawface(faces[i], i); 494 drawarrows(); 495 flushimage(display, 1); 496 } 497 498 void 499 eresized(int new) 500 { 501 lockdisplay(display); 502 if(new && getwindow(display, Refnone) < 0) { 503 fprint(2, "can't reattach to window\n"); 504 killall("reattach"); 505 } 506 resized(); 507 unlockdisplay(display); 508 } 509 510 int 511 getmouse(Mouse *m) 512 { 513 int n; 514 static int eof; 515 char buf[128]; 516 517 if(eof) 518 return 0; 519 for(;;){ 520 n = read(mousefd, buf, sizeof(buf)); 521 if(n <= 0){ 522 /* so callers needn't check return value every time */ 523 eof = 1; 524 m->buttons = 0; 525 return 0; 526 } 527 n = eatomouse(m, buf, n); 528 if(n > 0) 529 return 1; 530 } 531 } 532 533 enum 534 { 535 Clicksize = 3, /* pixels */ 536 }; 537 538 int 539 scroll(int but, Point p) 540 { 541 int delta; 542 543 delta = 0; 544 lockdisplay(display); 545 if(ptinrect(p, leftr) && first>0){ 546 if(but == 2) 547 delta = -first; 548 else{ 549 delta = nacross; 550 if(delta > first) 551 delta = first; 552 delta = -delta; 553 } 554 }else if(ptinrect(p, rightr) && last<nfaces){ 555 if(but == 2) 556 delta = (nfaces-nacross*ndown) - first; 557 else{ 558 delta = nacross; 559 if(delta > nfaces-last) 560 delta = nfaces-last; 561 } 562 } 563 first += delta; 564 last += delta; 565 unlockdisplay(display); 566 if(delta) 567 eresized(0); 568 return delta; 569 } 570 571 void 572 click(int button, Mouse *m) 573 { 574 Point p; 575 int i; 576 577 p = m->xy; 578 while(m->buttons == (1<<(button-1))) 579 getmouse(m); 580 if(m->buttons) 581 return; 582 if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize) 583 return; 584 switch(button){ 585 case 1: 586 if(scroll(1, p)) 587 break; 588 if(history){ 589 /* click clears display */ 590 lockdisplay(display); 591 for(i=0; i<nfaces; i++) 592 freeface(faces[i]); 593 free(faces); 594 faces=nil; 595 nfaces = 0; 596 unlockdisplay(display); 597 eresized(0); 598 return; 599 }else{ 600 for(i=first; i<last; i++) /* clear vwhois faces */ 601 if(ptinrect(p, facerect(i-first)) 602 && strstr(faces[i]->str[Sshow], "/XXXvwhois")){ 603 delface(i); 604 flushimage(display, 1); 605 } 606 } 607 break; 608 case 2: 609 scroll(2, p); 610 break; 611 case 3: 612 scroll(3, p); 613 lockdisplay(display); 614 for(i=first; i<last; i++) 615 if(ptinrect(p, facerect(i-first))){ 616 showmail(faces[i]); 617 break; 618 } 619 unlockdisplay(display); 620 break; 621 } 622 } 623 624 void 625 mouseproc(void) 626 { 627 Mouse mouse; 628 629 while(getmouse(&mouse)){ 630 if(mouse.buttons == 1) 631 click(1, &mouse); 632 else if(mouse.buttons == 2) 633 click(2, &mouse); 634 else if(mouse.buttons == 4) 635 click(3, &mouse); 636 637 while(mouse.buttons) 638 getmouse(&mouse); 639 } 640 } 641 642 void 643 killall(char *s) 644 { 645 int i, pid; 646 647 pid = getpid(); 648 for(i=0; i<NPROC; i++) 649 if(pids[i] && pids[i]!=pid) 650 postnote(PNPROC, pids[i], "kill"); 651 exits(s); 652 } 653 654 void 655 startproc(void (*f)(void), int index) 656 { 657 int pid; 658 659 switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){ 660 case -1: 661 fprint(2, "faces: fork failed: %r\n"); 662 killall("fork failed"); 663 case 0: 664 f(); 665 fprint(2, "faces: %s process exits\n", procnames[index]); 666 if(index >= 0) 667 killall("process died"); 668 exits(nil); 669 } 670 if(index >= 0) 671 pids[index] = pid; 672 } 673 674 void 675 usage(void) 676 { 677 fprint(2, "usage: faces [-hi] [-m maildir]\n"); 678 exits("usage"); 679 } 680 681 void 682 main(int argc, char *argv[]) 683 { 684 int i; 685 686 ARGBEGIN{ 687 case 'h': 688 history++; 689 break; 690 case 'i': 691 initload++; 692 break; 693 case 'm': 694 addmaildir(EARGF(usage())); 695 maildir = nil; 696 break; 697 default: 698 usage(); 699 }ARGEND 700 701 if(initdraw(nil, nil, "faces") < 0){ 702 fprint(2, "faces: initdraw failed: %r\n"); 703 exits("initdraw"); 704 } 705 if(maildir) 706 addmaildir(maildir); 707 init(); 708 unlockdisplay(display); /* initdraw leaves it locked */ 709 display->locking = 1; /* tell library we're using the display lock */ 710 setdate(); 711 eresized(0); 712 713 pids[Mainp] = getpid(); 714 startproc(timeproc, Timep); 715 startproc(mouseproc, Mousep); 716 if(initload) 717 for(i = 0; i < nmaildirs; i++) 718 loadmboxfaces(maildirs[i]); 719 faceproc(); 720 fprint(2, "faces: %s process exits\n", procnames[Mainp]); 721 killall(nil); 722 } 723