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