1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <plumb.h>
5 #include <regexp.h>
6 #include <bio.h>
7 #include <thread.h>
8 #include <mouse.h>
9 #include <cursor.h>
10 #include <9pclient.h>
11 #include "faces.h"
12 
13 int	history = 0;	/* use old interface, showing history of mailbox rather than current state */
14 int	initload = 0;	/* initialize program with contents of mail box */
15 
16 enum
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 */
22 
23 	STACK = 32768
24 };
25 
26 enum
27 {
28 	Mainp,
29 	Timep,
30 	Mousep,
31 	Resizep,
32 	NPROC
33 };
34 
35 char *procnames[] = {
36 	"main",
37 	"time",
38 	"mouse",
39 	"resize"
40 };
41 
42 Rectangle leftright = {0, 0, 20, 15};
43 
44 uchar 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 
53 uchar 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 
62 CFsys	*mailfs;
63 Mousectl	*mousectl;
64 Image	*blue;		/* full arrow */
65 Image	*bgrnd;		/* pale blue background color */
66 Image	*left;		/* left-pointing arrow mask */
67 Image	*right;		/* right-pointing arrow mask */
68 Font	*tinyfont;
69 Font	*mediumfont;
70 Font	*datefont;
71 int	first, last;	/* first and last visible face; last is first invisible */
72 int	nfaces;
73 int	mousefd;
74 int	nacross;
75 int	ndown;
76 
77 char	date[64];
78 Face	**faces;
79 char	*maildir = "mbox";
80 ulong	now;
81 
82 Point	datep = { 8, 6 };
83 Point	facep = { 8, 6+0+4 };	/* 0 updated to datefont->height in init() */
84 Point	enddate;			/* where date ends on display; used to place arrows */
85 Rectangle	leftr;			/* location of left arrow on display */
86 Rectangle	rightr;		/* location of right arrow on display */
87 void updatetimes(void);
88 void eresized(int);
89 
90 void
setdate(void)91 setdate(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 
98 void
init(void)99 init(void)
100 {
101 	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");
107 	initplumb();
108 
109 	/* make background color */
110 	bgrnd = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DWhite);
111 	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 
137 void
drawtime(void)138 drawtime(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 
154 void
timeproc(void * dummy)155 timeproc(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 
168 int
alreadyseen(char * digest)169 alreadyseen(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 
186 int
torune(Rune * r,char * s,int nr)187 torune(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 
197 void
center(Font * f,Point p,char * s,Image * color)198 center(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 
220 Rectangle
facerect(int index)221 facerect(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 
239 static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
240 char*
facetime(Face * f,int * recent)241 facetime(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 
256 void
drawface(Face * f,int i)257 drawface(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 
284 void
updatetimes(void)285 updatetimes(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 
299 void
setlast(void)300 setlast(void)
301 {
302 	last = first+nacross*ndown;
303 	if(last > nfaces)
304 		last = nfaces;
305 }
306 
307 void
drawarrows(void)308 drawarrows(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 
323 void
addface(Face * f)324 addface(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;
332 	if(first != 0){
333 		first = 0;
334 		eresized(0);
335 	}
336 	findbit(f);
337 
338 	nx = nacross;
339 	ny = (nfaces+(nx-1)) / nx;
340 
341 	lockdisplay(display);
342 	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 
369 void
loadmboxfaces(char * maildir)370 loadmboxfaces(char *maildir)
371 {
372 	CFid *dirfd;
373 	Dir *d;
374 	int i, n;
375 
376 	dirfd = fsopen(mailfs, maildir, OREAD);
377 	if(dirfd != nil){
378 		while((n = fsdirread(dirfd, &d)) > 0){
379 			for(i=0; i<n; i++)
380 				addface(dirface(maildir, d[i].name));
381 			free(d);
382 		}
383 		fsclose(dirfd);
384 	}else
385 		sysfatal("open %s: %r", maildir);
386 }
387 
388 void
freeface(Face * f)389 freeface(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 
401 void
delface(int j)402 delface(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 
444 void
dodelete(int i)445 dodelete(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 
459 void
delete(char * s,char * digest)460 delete(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 
483 void
faceproc(void)484 faceproc(void)
485 {
486 	for(;;)
487 		addface(nextface());
488 }
489 
490 void
resized(void)491 resized(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 
508 void
eresized(int new)509 eresized(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 
520 void
resizeproc(void * v)521 resizeproc(void *v)
522 {
523 	USED(v);
524 
525 	while(recv(mousectl->resizec, 0) == 1)
526 		eresized(1);
527 }
528 
529 int
getmouse(Mouse * m)530 getmouse(Mouse *m)
531 {
532 	static int eof;
533 
534 	if(eof)
535 		return 0;
536 	if(readmouse(mousectl) < 0){
537 		eof = 1;
538 		m->buttons = 0;
539 		return 0;
540 	}
541 	*m = mousectl->m;
542 	return 1;
543 }
544 
545 enum
546 {
547 	Clicksize	= 3,		/* pixels */
548 };
549 
550 int
scroll(int but,Point p)551 scroll(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 
583 void
click(int button,Mouse * m)584 click(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")){
615 					lockdisplay(display);
616 					delface(i);
617 					flushimage(display, 1);
618 					unlockdisplay(display);
619 					break;
620 				}
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 
639 void
mouseproc(void * v)640 mouseproc(void *v)
641 {
642 	Mouse mouse;
643 	USED(v);
644 
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 
658 void
killall(char * s)659 killall(char *s)
660 {
661 	threadexitsall(s);
662 }
663 
664 void
usage(void)665 usage(void)
666 {
667 	fprint(2, "usage: faces [-hi] [-m maildir] -W winsize\n");
668 	threadexitsall("usage");
669 }
670 
671 void
threadmain(int argc,char * argv[])672 threadmain(int argc, char *argv[])
673 {
674 	int i;
675 
676 	rfork(RFNOTEG);
677 
678 	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 
708 	proccreate(timeproc, nil, STACK);
709 	proccreate(mouseproc, nil, STACK);
710 	proccreate(resizeproc, nil, STACK);
711 	if(initload)
712 		for(i = 0; i < nmaildirs; i++)
713 			loadmboxfaces(maildirs[i]);
714 	faceproc();
715 	killall(nil);
716 }
717