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