xref: /plan9/sys/src/cmd/faces/main.c (revision 53b9a8487798c7301d999d284e212f6e6cb40d59)
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
setdate(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
init(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
drawtime(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
timeproc(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
alreadyseen(char * digest)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
torune(Rune * r,char * s,int nr)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
center(Font * f,Point p,char * s,Image * color)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
facerect(int index)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*
facetime(Face * f,int * recent)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
drawface(Face * f,int i)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
updatetimes(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
setlast(void)291 setlast(void)
292 {
293 	last = first+nacross*ndown;
294 	if(last > nfaces)
295 		last = nfaces;
296 }
297 
298 void
drawarrows(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
addface(Face * f)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
loadmboxfaces(char * maildir)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
freeface(Face * f)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
delface(int j)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
dodelete(int i)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
delete(char * s,char * digest)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
faceproc(void)475 faceproc(void)
476 {
477 	for(;;)
478 		addface(nextface());
479 }
480 
481 void
resized(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
eresized(int new)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
getmouse(Mouse * m)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
scroll(int but,Point p)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
click(int button,Mouse * m)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
mouseproc(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
killall(char * s)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
startproc(void (* f)(void),int index)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
usage(void)676 usage(void)
677 {
678 	fprint(2, "usage: faces [-hi] [-m maildir]\n");
679 	exits("usage");
680 }
681 
682 void
main(int argc,char * argv[])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