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