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