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 now = time(nil); 154 sleep(((60 - now%60) + 1)*1000); /* wait for minute to change */ 155 setdate(); 156 } 157 } 158 159 int 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 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 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 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* 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 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 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 291 setlast(void) 292 { 293 last = first+nacross*ndown; 294 if(last > nfaces) 295 last = nfaces; 296 } 297 298 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 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 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 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 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 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 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 475 faceproc(void) 476 { 477 for(;;) 478 addface(nextface()); 479 } 480 481 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 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 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 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 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 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 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 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 676 usage(void) 677 { 678 fprint(2, "usage: faces [-hi] [-m maildir]\n"); 679 exits("usage"); 680 } 681 682 void 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