xref: /plan9/sys/src/cmd/faces/main.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
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 		sleep(60000);
154 		setdate();
155 	}
156 }
157 
158 int
159 alreadyseen(char *digest)
160 {
161 	int i;
162 	Face *f;
163 
164 	if(!digest)
165 		return 0;
166 
167 	/* can do accurate check */
168 	for(i=0; i<nfaces; i++){
169 		f = faces[i];
170 		if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
171 			return 1;
172 	}
173 	return 0;
174 }
175 
176 int
177 torune(Rune *r, char *s, int nr)
178 {
179 	int i;
180 
181 	for(i=0; i<nr-1 && *s!='\0'; i++)
182 		s += chartorune(r+i, s);
183 	r[i] = L'\0';
184 	return i;
185 }
186 
187 void
188 center(Font *f, Point p, char *s, Image *color)
189 {
190 	int i, n, dx;
191 	Rune rbuf[32];
192 	char sbuf[32*UTFmax+1];
193 
194 	dx = stringwidth(f, s);
195 	if(dx > Facesize){
196 		n = torune(rbuf, s, nelem(rbuf));
197 		for(i=0; i<n; i++){
198 			dx = runestringnwidth(f, rbuf, i+1);
199 			if(dx > Facesize)
200 				break;
201 		}
202 		sprint(sbuf, "%.*S", i, rbuf);
203 		s = sbuf;
204 		dx = stringwidth(f, s);
205 	}
206 	p.x += (Facesize-dx)/2;
207 	string(screen, p, color, ZP, f, s);
208 }
209 
210 Rectangle
211 facerect(int index)	/* index is geometric; 0 is always upper left face */
212 {
213 	Rectangle r;
214 	int x, y;
215 
216 	x = index % nacross;
217 	y = index / nacross;
218 	r.min = addpt(screen->r.min, facep);
219 	r.min.x += x*(Facesize+Facesep);
220 	r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
221 	r.max = addpt(r.min, Pt(Facesize, Facesize));
222 	r.max.y += 2*mediumfont->height;
223 	/* simple fix to avoid drawing off screen, allowing customers to use position */
224 	if(index<0 || index>=nacross*ndown)
225 		r.max.x = r.min.x;
226 	return r;
227 }
228 
229 static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
230 char*
231 facetime(Face *f, int *recent)
232 {
233 	static char buf[30];
234 
235 	if((long)(now - f->time) > HhmmTime){
236 		*recent = 0;
237 		sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
238 		return buf;
239 	}else{
240 		*recent = 1;
241 		sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
242 		return buf;
243 	}
244 }
245 
246 void
247 drawface(Face *f, int i)
248 {
249 	char *tstr;
250 	Rectangle r;
251 	Point p;
252 
253 	if(f == nil)
254 		return;
255 	if(i<first || i>=last)
256 		return;
257 	r = facerect(i-first);
258 	draw(screen, r, bgrnd, nil, ZP);
259 	draw(screen, r, f->bit, f->mask, ZP);
260 	r.min.y += Facesize;
261 	center(mediumfont, r.min, f->str[Suser], display->black);
262 	r.min.y += mediumfont->height;
263 	tstr = facetime(f, &f->recent);
264 	center(mediumfont, r.min, tstr, display->black);
265 	if(f->unknown){
266 		r.min.y -= mediumfont->height + tinyfont->height + 2;
267 		for(p.x=-1; p.x<=1; p.x++)
268 			for(p.y=-1; p.y<=1; p.y++)
269 				center(tinyfont, addpt(r.min, p), f->str[Sdomain], display->white);
270 		center(tinyfont, r.min, f->str[Sdomain], display->black);
271 	}
272 }
273 
274 void
275 updatetimes(void)
276 {
277 	int i;
278 	Face *f;
279 
280 	for(i=0; i<nfaces; i++){
281 		f = faces[i];
282 		if(f == nil)
283 			continue;
284 		if(((long)(now - f->time) <= HhmmTime) != f->recent)
285 			drawface(f, i);
286 	}
287 }
288 
289 void
290 setlast(void)
291 {
292 	last = first+nacross*ndown;
293 	if(last > nfaces)
294 		last = nfaces;
295 }
296 
297 void
298 drawarrows(void)
299 {
300 	Point p;
301 
302 	p = enddate;
303 	p.x += Facesep;
304 	if(p.x & 1)
305 		p.x++;	/* align background texture */
306 	leftr = rectaddpt(leftright, p);
307 	p.x += Dx(leftright) + Facesep;
308 	rightr = rectaddpt(leftright, p);
309 	draw(screen, leftr, first>0? blue : bgrnd, left, leftright.min);
310 	draw(screen, rightr, last<nfaces? blue : bgrnd, right, leftright.min);
311 }
312 
313 void
314 addface(Face *f)	/* always adds at 0 */
315 {
316 	Face **ofaces;
317 	Rectangle r0, r1, r;
318 	int y, nx, ny;
319 
320 	if(f == nil)
321 		return;
322 	lockdisplay(display);
323 	if(first != 0){
324 		first = 0;
325 		resized();
326 	}
327 	findbit(f);
328 
329 	nx = nacross;
330 	ny = (nfaces+(nx-1)) / nx;
331 
332 	for(y=ny; y>=0; y--){
333 		/* move them along */
334 		r0 = facerect(y*nx+0);
335 		r1 = facerect(y*nx+1);
336 		r = r1;
337 		r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
338 		draw(screen, r, screen, nil, r0.min);
339 		/* copy one down from row above */
340 		if(y != 0){
341 			r = facerect((y-1)*nx+nx-1);
342 			draw(screen, r0, screen, nil, r.min);
343 		}
344 	}
345 
346 	ofaces = faces;
347 	faces = emalloc((nfaces+1)*sizeof(Face*));
348 	memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
349 	free(ofaces);
350 	nfaces++;
351 	setlast();
352 	drawarrows();
353 	faces[0] = f;
354 	drawface(f, 0);
355 	flushimage(display, 1);
356 	unlockdisplay(display);
357 }
358 
359 void
360 loadmboxfaces(char *maildir)
361 {
362 	int dirfd;
363 	Dir *d;
364 	int i, n;
365 
366 	dirfd = open(maildir, OREAD);
367 	if(dirfd >= 0){
368 		chdir(maildir);
369 		while((n = dirread(dirfd, &d)) > 0){
370 			for(i=0; i<n; i++)
371 				addface(dirface(maildir, d[i].name));
372 			free(d);
373 		}
374 		close(dirfd);
375 	}
376 }
377 
378 void
379 freeface(Face *f)
380 {
381 	int i;
382 
383 	if(f->file!=nil && f->bit!=f->file->image)
384 		freeimage(f->bit);
385 	freefacefile(f->file);
386 	for(i=0; i<Nstring; i++)
387 		free(f->str[i]);
388 	free(f);
389 }
390 
391 void
392 delface(int j)
393 {
394 	Rectangle r0, r1, r;
395 	int nx, ny, x, y;
396 
397 	if(j < first)
398 		first--;
399 	else if(j < last){
400 		nx = nacross;
401 		ny = (nfaces+(nx-1)) / nx;
402 		x = (j-first)%nx;
403 		for(y=(j-first)/nx; y<ny; y++){
404 			if(x != nx-1){
405 				/* move them along */
406 				r0 = facerect(y*nx+x);
407 				r1 = facerect(y*nx+x+1);
408 				r = r0;
409 				r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
410 				draw(screen, r, screen, nil, r1.min);
411 			}
412 			if(y != ny-1){
413 				/* copy one up from row below */
414 				r = facerect((y+1)*nx);
415 				draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
416 			}
417 			x = 0;
418 		}
419 		if(last < nfaces)	/* first off-screen becomes visible */
420 			drawface(faces[last], last-1);
421 		else{
422 			/* clear final spot */
423 			r = facerect(last-first-1);
424 			draw(screen, r, bgrnd, nil, r.min);
425 		}
426 	}
427 	freeface(faces[j]);
428 	memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
429 	nfaces--;
430 	setlast();
431 	drawarrows();
432 }
433 
434 void
435 dodelete(int i)
436 {
437 	Face *f;
438 
439 	f = faces[i];
440 	if(history){
441 		free(f->str[Sshow]);
442 		f->str[Sshow] = estrdup("");
443 	}else{
444 		delface(i);
445 		flushimage(display, 1);
446 	}
447 }
448 
449 void
450 delete(char *s, char *digest)
451 {
452 	int i;
453 	Face *f;
454 
455 	lockdisplay(display);
456 	for(i=0; i<nfaces; i++){
457 		f = faces[i];
458 		if(digest != nil){
459 			if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
460 				dodelete(i);
461 				break;
462 			}
463 		}else{
464 			if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
465 				dodelete(i);
466 				break;
467 			}
468 		}
469 	}
470 	unlockdisplay(display);
471 }
472 
473 void
474 faceproc(void)
475 {
476 	for(;;)
477 		addface(nextface());
478 }
479 
480 void
481 resized(void)
482 {
483 	int i;
484 
485 	nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
486 	for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
487 		;
488 	setlast();
489 	draw(screen, screen->r, bgrnd, nil, ZP);
490 	enddate = ZP;
491 	drawtime();
492 	for(i=0; i<nfaces; i++)
493 		drawface(faces[i], i);
494 	drawarrows();
495 	flushimage(display, 1);
496 }
497 
498 void
499 eresized(int new)
500 {
501 	lockdisplay(display);
502 	if(new && getwindow(display, Refnone) < 0) {
503 		fprint(2, "can't reattach to window\n");
504 		killall("reattach");
505 	}
506 	resized();
507 	unlockdisplay(display);
508 }
509 
510 int
511 getmouse(Mouse *m)
512 {
513 	int n;
514 	static int eof;
515 	char buf[128];
516 
517 	if(eof)
518 		return 0;
519 	for(;;){
520 		n = read(mousefd, buf, sizeof(buf));
521 		if(n <= 0){
522 			/* so callers needn't check return value every time */
523 			eof = 1;
524 			m->buttons = 0;
525 			return 0;
526 		}
527 		n = eatomouse(m, buf, n);
528 		if(n > 0)
529 			return 1;
530 	}
531 }
532 
533 enum
534 {
535 	Clicksize	= 3,		/* pixels */
536 };
537 
538 int
539 scroll(int but, Point p)
540 {
541 	int delta;
542 
543 	delta = 0;
544 	lockdisplay(display);
545 	if(ptinrect(p, leftr) && first>0){
546 		if(but == 2)
547 			delta = -first;
548 		else{
549 			delta = nacross;
550 			if(delta > first)
551 				delta = first;
552 			delta = -delta;
553 		}
554 	}else if(ptinrect(p, rightr) && last<nfaces){
555 		if(but == 2)
556 			delta = (nfaces-nacross*ndown) - first;
557 		else{
558 			delta = nacross;
559 			if(delta > nfaces-last)
560 				delta = nfaces-last;
561 		}
562 	}
563 	first += delta;
564 	last += delta;
565 	unlockdisplay(display);
566 	if(delta)
567 		eresized(0);
568 	return delta;
569 }
570 
571 void
572 click(int button, Mouse *m)
573 {
574 	Point p;
575 	int i;
576 
577 	p = m->xy;
578 	while(m->buttons == (1<<(button-1)))
579 		getmouse(m);
580 	if(m->buttons)
581 		return;
582 	if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
583 		return;
584 	switch(button){
585 	case 1:
586 		if(scroll(1, p))
587 			break;
588 		if(history){
589 			/* click clears display */
590 			lockdisplay(display);
591 			for(i=0; i<nfaces; i++)
592 				freeface(faces[i]);
593 			free(faces);
594 			faces=nil;
595 			nfaces = 0;
596 			unlockdisplay(display);
597 			eresized(0);
598 			return;
599 		}else{
600 			for(i=first; i<last; i++)	/* clear vwhois faces */
601 				if(ptinrect(p, facerect(i-first))
602 				&& strstr(faces[i]->str[Sshow], "/XXXvwhois")){
603 					delface(i);
604 					flushimage(display, 1);
605 				}
606 		}
607 		break;
608 	case 2:
609 		scroll(2, p);
610 		break;
611 	case 3:
612 		scroll(3, p);
613 		lockdisplay(display);
614 		for(i=first; i<last; i++)
615 			if(ptinrect(p, facerect(i-first))){
616 				showmail(faces[i]);
617 				break;
618 			}
619 		unlockdisplay(display);
620 		break;
621 	}
622 }
623 
624 void
625 mouseproc(void)
626 {
627 	Mouse mouse;
628 
629 	while(getmouse(&mouse)){
630 		if(mouse.buttons == 1)
631 			click(1, &mouse);
632 		else if(mouse.buttons == 2)
633 			click(2, &mouse);
634 		else if(mouse.buttons == 4)
635 			click(3, &mouse);
636 
637 		while(mouse.buttons)
638 			getmouse(&mouse);
639 	}
640 }
641 
642 void
643 killall(char *s)
644 {
645 	int i, pid;
646 
647 	pid = getpid();
648 	for(i=0; i<NPROC; i++)
649 		if(pids[i] && pids[i]!=pid)
650 			postnote(PNPROC, pids[i], "kill");
651 	exits(s);
652 }
653 
654 void
655 startproc(void (*f)(void), int index)
656 {
657 	int pid;
658 
659 	switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
660 	case -1:
661 		fprint(2, "faces: fork failed: %r\n");
662 		killall("fork failed");
663 	case 0:
664 		f();
665 		fprint(2, "faces: %s process exits\n", procnames[index]);
666 		if(index >= 0)
667 			killall("process died");
668 		exits(nil);
669 	}
670 	if(index >= 0)
671 		pids[index] = pid;
672 }
673 
674 void
675 usage(void)
676 {
677 	fprint(2, "usage: faces [-hi] [-m maildir]\n");
678 	exits("usage");
679 }
680 
681 void
682 main(int argc, char *argv[])
683 {
684 	int i;
685 
686 	ARGBEGIN{
687 	case 'h':
688 		history++;
689 		break;
690 	case 'i':
691 		initload++;
692 		break;
693 	case 'm':
694 		addmaildir(EARGF(usage()));
695 		maildir = nil;
696 		break;
697 	default:
698 		usage();
699 	}ARGEND
700 
701 	if(initdraw(nil, nil, "faces") < 0){
702 		fprint(2, "faces: initdraw failed: %r\n");
703 		exits("initdraw");
704 	}
705 	if(maildir)
706 		addmaildir(maildir);
707 	init();
708 	unlockdisplay(display);	/* initdraw leaves it locked */
709 	display->locking = 1;	/* tell library we're using the display lock */
710 	setdate();
711 	eresized(0);
712 
713 	pids[Mainp] = getpid();
714 	startproc(timeproc, Timep);
715 	startproc(mouseproc, Mousep);
716 	if(initload)
717 		for(i = 0; i < nmaildirs; i++)
718 		 loadmboxfaces(maildirs[i]);
719 	faceproc();
720 	fprint(2, "faces: %s process exits\n", procnames[Mainp]);
721 	killall(nil);
722 }
723