Blob


1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <plumb.h>
5 #include <regexp.h>
6 #include <bio.h>
7 #include <9pclient.h>
8 #include "faces.h"
10 enum /* number of deleted faces to cache */
11 {
12 Nsave = 20
13 };
15 static Facefile *facefiles;
16 static int nsaved;
17 static char *facedom;
18 static char *libface;
19 static char *homeface;
21 /*
22 * Loading the files is slow enough on a dial-up line to be worth this trouble
23 */
24 typedef struct Readcache Readcache;
25 struct Readcache {
26 char *file;
27 char *data;
28 long mtime;
29 long rdtime;
30 Readcache *next;
31 };
33 static Readcache *rcache;
35 ulong
36 dirlen(char *s)
37 {
38 Dir *d;
39 ulong len;
41 d = dirstat(s);
42 if(d == nil)
43 return 0;
44 len = d->length;
45 free(d);
46 return len;
47 }
49 ulong
50 fsdirlen(CFsys *fs,char *s)
51 {
52 Dir *d;
53 ulong len;
55 d = fsdirstat(fs,s);
56 if(d == nil)
57 return 0;
58 len = d->length;
59 free(d);
60 return len;
61 }
63 ulong
64 dirmtime(char *s)
65 {
66 Dir *d;
67 ulong t;
69 d = dirstat(s);
70 if(d == nil)
71 return 0;
72 t = d->mtime;
73 free(d);
74 return t;
75 }
77 static char*
78 doreadfile(char *s)
79 {
80 char *p;
81 int fd, n;
82 ulong len;
84 len = dirlen(s);
85 if(len == 0)
86 return nil;
88 p = malloc(len+1);
89 if(p == nil)
90 return nil;
92 if((fd = open(s, OREAD)) < 0
93 || (n = readn(fd, p, len)) < 0) {
94 close(fd);
95 free(p);
96 return nil;
97 }
99 p[n] = '\0';
100 return p;
103 static char*
104 readfile(char *s)
106 Readcache *r, **l;
107 char *p;
108 ulong mtime;
110 for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
111 if(strcmp(r->file, s) != 0)
112 continue;
114 /*
115 * if it's less than 30 seconds since we read it, or it
116 * hasn't changed, send back our copy
117 */
118 if(time(0) - r->rdtime < 30)
119 return strdup(r->data);
120 if(dirmtime(s) == r->mtime) {
121 r->rdtime = time(0);
122 return strdup(r->data);
125 /* out of date, remove this and fall out of loop */
126 *l = r->next;
127 free(r->file);
128 free(r->data);
129 free(r);
130 break;
133 /* add to cache */
134 mtime = dirmtime(s);
135 if(mtime == 0)
136 return nil;
138 if((p = doreadfile(s)) == nil)
139 return nil;
141 r = malloc(sizeof(*r));
142 if(r == nil)
143 return nil;
144 r->mtime = mtime;
145 r->file = estrdup(s);
146 r->data = p;
147 r->rdtime = time(0);
148 r->next = rcache;
149 rcache = r;
150 return strdup(r->data);
153 static char*
154 translatedomain(char *dom, char *list)
156 static char buf[200];
157 char *p, *ep, *q, *nextp, *file;
158 char *bbuf, *ebuf;
159 Reprog *exp;
161 if(dom == nil || *dom == 0)
162 return nil;
164 if(list == nil || (file = readfile(list)) == nil)
165 return dom;
167 for(p=file; p; p=nextp) {
168 if(nextp = strchr(p, '\n'))
169 *nextp++ = '\0';
171 if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
172 continue;
174 bbuf = buf+1;
175 ebuf = buf+(1+(q-p));
176 strncpy(bbuf, p, ebuf-bbuf);
177 *ebuf = 0;
178 if(*bbuf != '^')
179 *--bbuf = '^';
180 if(ebuf[-1] != '$') {
181 *ebuf++ = '$';
182 *ebuf = 0;
185 if((exp = regcomp(bbuf)) == nil){
186 fprint(2, "bad regexp in machinelist: %s\n", bbuf);
187 killall("regexp");
190 if(regexec(exp, dom, 0, 0)){
191 free(exp);
192 ep = p+strlen(p);
193 q += strspn(q, " \t");
194 if(ep-q+2 > sizeof buf) {
195 fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
196 exits("bad big replacement");
198 strncpy(buf, q, ep-q);
199 ebuf = buf+(ep-q);
200 *ebuf = 0;
201 while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
202 *--ebuf = 0;
203 free(file);
204 return buf;
206 free(exp);
208 free(file);
210 return dom;
213 static char*
214 tryfindpicture(char *dom, char *user, char *dir, char *dict)
216 static char buf[1024];
217 char *file, *p, *nextp, *q;
219 if((file = readfile(dict)) == nil)
220 return nil;
222 snprint(buf, sizeof buf, "%s/%s", dom, user);
224 for(p=file; p; p=nextp){
225 if(nextp = strchr(p, '\n'))
226 *nextp++ = '\0';
228 if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
229 continue;
230 *q++ = 0;
232 if(strcmp(buf, p) == 0){
233 q += strspn(q, " \t");
234 snprint(buf, sizeof buf, "%s/%s", dir, q);
235 q = buf+strlen(buf);
236 while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
237 *--q = 0;
238 free(file);
239 return estrdup(buf);
242 free(file);
243 return nil;
246 static char*
247 estrstrdup(char *a, char *b)
249 char *t;
251 t = emalloc(strlen(a)+strlen(b)+1);
252 strcpy(t, a);
253 strcat(t, b);
254 return t;
257 static char*
258 tryfindfiledir(char *dom, char *user, char *dir)
260 char *dict, *ndir, *x, *odom;
261 int fd;
262 int i, n;
263 Dir *d;
265 /*
266 * If this directory has a .machinelist, use it.
267 */
268 x = estrstrdup(dir, "/.machinelist");
269 dom = estrdup(translatedomain(dom, x));
270 free(x);
271 /*
272 * If this directory has a .dict, use it.
273 */
274 dict = estrstrdup(dir, "/.dict");
275 if(access(dict, AEXIST) >= 0){
276 x = tryfindpicture(dom, user, dir, dict);
277 free(dict);
278 free(dom);
279 return x;
281 free(dict);
283 /*
284 * If not, recurse into subdirectories.
285 * Ignore 48x48xN directories for now.
286 */
287 if((fd = open(dir, OREAD)) < 0)
288 return nil;
289 while((n = dirread(fd, &d)) > 0){
290 for(i=0; i<n; i++){
291 if((d[i].mode&DMDIR)&& strncmp(d[i].name, "48x48x", 6) != 0){
292 ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1);
293 strcpy(ndir, dir);
294 strcat(ndir, "/");
295 strcat(ndir, d[i].name);
296 if((x = tryfindfiledir(dom, user, ndir)) != nil){
297 free(ndir);
298 free(d);
299 close(fd);
300 free(dom);
301 return x;
305 free(d);
307 close(fd);
309 /*
310 * Handle 48x48xN directories in the right order.
311 */
312 ndir = estrstrdup(dir, "/48x48x8");
313 for(i=8; i>0; i>>=1){
314 ndir[strlen(ndir)-1] = i+'0';
315 if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){
316 free(ndir);
317 free(dom);
318 return x;
321 free(ndir);
322 free(dom);
323 return nil;
326 static char*
327 tryfindfile(char *dom, char *user)
329 char *p;
331 while(dom && *dom){
332 if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil)
333 return p;
334 if((p = tryfindfiledir(dom, user, libface)) != nil)
335 return p;
336 if((dom = strchr(dom, '.')) == nil)
337 break;
338 dom++;
340 return nil;
343 char*
344 findfile(Face *f, char *dom, char *user)
346 char *p;
348 if(facedom == nil){
349 facedom = getenv("facedom");
350 if(facedom == nil)
351 facedom = DEFAULT;
353 if(libface == nil)
354 libface = unsharp("#9/face");
355 if(homeface == nil)
356 homeface = smprint("%s/lib/face", getenv("HOME"));
358 if(dom == nil)
359 dom = facedom;
361 f->unknown = 0;
362 if((p = tryfindfile(dom, user)) != nil)
363 return p;
364 f->unknown = 1;
365 p = tryfindfile(dom, "unknown");
366 if(p != nil || strcmp(dom, facedom) == 0)
367 return p;
368 return tryfindfile("unknown", "unknown");
371 static
372 void
373 clearsaved(void)
375 Facefile *f, *next, **lf;
377 lf = &facefiles;
378 for(f=facefiles; f!=nil; f=next){
379 next = f->next;
380 if(f->ref > 0){
381 *lf = f;
382 lf = &(f->next);
383 continue;
385 if(f->image != display->black && f->image != display->white)
386 freeimage(f->image);
387 free(f->file);
388 free(f);
390 *lf = nil;
391 nsaved = 0;
394 void
395 freefacefile(Facefile *f)
397 if(f==nil || f->ref-->1)
398 return;
399 if(++nsaved > Nsave)
400 clearsaved();
403 static Image*
404 myallocimage(ulong chan)
406 Image *img;
407 img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
408 if(img == nil){
409 clearsaved();
410 img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
411 if(img == nil)
412 return nil;
414 return img;
418 static Image*
419 readbit(int fd, ulong chan)
421 char buf[4096], hx[4], *p;
422 uchar data[Facesize*Facesize]; /* more than enough */
423 int nhx, i, n, ndata, nbit;
424 Image *img;
426 n = readn(fd, buf, sizeof buf);
427 if(n <= 0)
428 return nil;
429 if(n >= sizeof buf)
430 n = sizeof(buf)-1;
431 buf[n] = '\0';
433 n = 0;
434 nhx = 0;
435 nbit = chantodepth(chan);
436 ndata = (Facesize*Facesize*nbit)/8;
437 p = buf;
438 while(n < ndata) {
439 p = strpbrk(p+1, "0123456789abcdefABCDEF");
440 if(p == nil)
441 break;
442 if(p[0] == '0' && p[1] == 'x')
443 continue;
445 hx[nhx] = *p;
446 if(++nhx == 2) {
447 hx[nhx] = 0;
448 i = strtoul(hx, 0, 16);
449 data[n++] = i;
450 nhx = 0;
453 if(n < ndata)
454 return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
456 img = myallocimage(chan);
457 if(img == nil)
458 return nil;
459 loadimage(img, img->r, data, ndata);
460 return img;
463 static Facefile*
464 readface(char *fn)
466 int x, y, fd;
467 uchar bits;
468 uchar *p;
469 Image *mask;
470 Image *face;
471 char buf[16];
472 uchar data[Facesize*Facesize];
473 uchar mdata[(Facesize*Facesize)/8];
474 Facefile *f;
475 Dir *d;
477 for(f=facefiles; f!=nil; f=f->next){
478 if(strcmp(fn, f->file) == 0){
479 if(f->image == nil)
480 break;
481 if(time(0) - f->rdtime >= 30) {
482 if(dirmtime(fn) != f->mtime){
483 f = nil;
484 break;
486 f->rdtime = time(0);
488 f->ref++;
489 return f;
493 if((fd = open(fn, OREAD)) < 0)
494 return nil;
496 if(readn(fd, buf, sizeof buf) != sizeof buf){
497 close(fd);
498 return nil;
501 seek(fd, 0, 0);
503 mask = nil;
504 if(buf[0] == '0' && buf[1] == 'x'){
505 /* greyscale faces are just masks that we draw black through! */
506 if(buf[2+8] == ',') /* ldepth 1 */
507 mask = readbit(fd, GREY2);
508 else
509 mask = readbit(fd, GREY1);
510 face = display->black;
511 }else{
512 face = readimage(display, fd, 0);
513 if(face == nil)
514 goto Done;
515 else if(face->chan == GREY4 || face->chan == GREY8){ /* greyscale: use inversion as mask */
516 mask = myallocimage(face->chan);
517 /* okay if mask is nil: that will copy the image white background and all */
518 if(mask == nil)
519 goto Done;
521 /* invert greyscale image */
522 draw(mask, mask->r, display->white, nil, ZP);
523 gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
524 freeimage(face);
525 face = display->black;
526 }else if(face->depth == 8){ /* snarf the bytes back and do a fill. */
527 mask = myallocimage(GREY1);
528 if(mask == nil)
529 goto Done;
530 if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){
531 freeimage(mask);
532 goto Done;
534 bits = 0;
535 p = mdata;
536 for(y=0; y<Facesize; y++){
537 for(x=0; x<Facesize; x++){
538 bits <<= 1;
539 if(data[Facesize*y+x] != 0xFF)
540 bits |= 1;
541 if((x&7) == 7)
542 *p++ = bits&0xFF;
545 if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
546 freeimage(mask);
547 goto Done;
552 Done:
553 /* always add at beginning of list, so updated files don't collide in cache */
554 if(f == nil){
555 f = emalloc(sizeof(Facefile));
556 f->file = estrdup(fn);
557 d = dirfstat(fd);
558 if(d != nil){
559 f->mtime = d->mtime;
560 free(d);
562 f->next = facefiles;
563 facefiles = f;
565 f->ref++;
566 f->image = face;
567 f->mask = mask;
568 f->rdtime = time(0);
569 close(fd);
570 return f;
573 void
574 findbit(Face *f)
576 char *fn;
578 fn = findfile(f, f->str[Sdomain], f->str[Suser]);
579 if(fn) {
580 if(strstr(fn, "unknown"))
581 f->unknown = 1;
582 f->file = readface(fn);
584 if(f->file){
585 f->bit = f->file->image;
586 f->mask = f->file->mask;
587 }else{
588 /* if returns nil, this is still ok: draw(nil) works */
589 f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
590 replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
591 f->mask = nil;