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;
19 /*
20 * Loading the files is slow enough on a dial-up line to be worth this trouble
21 */
22 typedef struct Readcache Readcache;
23 struct Readcache {
24 char *file;
25 char *data;
26 long mtime;
27 long rdtime;
28 Readcache *next;
29 };
31 static Readcache *rcache;
33 ulong
34 dirlen(char *s)
35 {
36 Dir *d;
37 ulong len;
39 d = dirstat(s);
40 if(d == nil)
41 return 0;
42 len = d->length;
43 free(d);
44 return len;
45 }
47 ulong
48 fsdirlen(CFsys *fs,char *s)
49 {
50 Dir *d;
51 ulong len;
53 d = fsdirstat(fs,s);
54 if(d == nil)
55 return 0;
56 len = d->length;
57 free(d);
58 return len;
59 }
61 ulong
62 dirmtime(char *s)
63 {
64 Dir *d;
65 ulong t;
67 d = dirstat(s);
68 if(d == nil)
69 return 0;
70 t = d->mtime;
71 free(d);
72 return t;
73 }
75 static char*
76 doreadfile(char *s)
77 {
78 char *p;
79 int fd, n;
80 ulong len;
82 len = dirlen(s);
83 if(len == 0)
84 return nil;
86 p = malloc(len+1);
87 if(p == nil)
88 return nil;
90 if((fd = open(s, OREAD)) < 0
91 || (n = readn(fd, p, len)) < 0) {
92 close(fd);
93 free(p);
94 return nil;
95 }
97 p[n] = '\0';
98 return p;
99 }
101 static char*
102 readfile(char *s)
104 Readcache *r, **l;
105 char *p;
106 ulong mtime;
108 for(l=&rcache, r=*l; r; l=&r->next, r=*l) {
109 if(strcmp(r->file, s) != 0)
110 continue;
112 /*
113 * if it's less than 30 seconds since we read it, or it
114 * hasn't changed, send back our copy
115 */
116 if(time(0) - r->rdtime < 30)
117 return strdup(r->data);
118 if(dirmtime(s) == r->mtime) {
119 r->rdtime = time(0);
120 return strdup(r->data);
123 /* out of date, remove this and fall out of loop */
124 *l = r->next;
125 free(r->file);
126 free(r->data);
127 free(r);
128 break;
131 /* add to cache */
132 mtime = dirmtime(s);
133 if(mtime == 0)
134 return nil;
136 if((p = doreadfile(s)) == nil)
137 return nil;
139 r = malloc(sizeof(*r));
140 if(r == nil)
141 return nil;
142 r->mtime = mtime;
143 r->file = estrdup(s);
144 r->data = p;
145 r->rdtime = time(0);
146 r->next = rcache;
147 rcache = r;
148 return strdup(r->data);
152 static char*
153 translatedomain(char *dom)
155 static char buf[200];
156 char *p, *ep, *q, *nextp, *file;
157 char *bbuf, *ebuf;
158 Reprog *exp;
160 if(dom == nil || *dom == 0)
161 return nil;
163 if((file = readfile(unsharp("#9/face/.machinelist"))) == nil)
164 return dom;
166 for(p=file; p; p=nextp) {
167 if(nextp = strchr(p, '\n'))
168 *nextp++ = '\0';
170 if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2)
171 continue;
173 bbuf = buf+1;
174 ebuf = buf+(1+(q-p));
175 strncpy(bbuf, p, ebuf-bbuf);
176 *ebuf = 0;
177 if(*bbuf != '^')
178 *--bbuf = '^';
179 if(ebuf[-1] != '$') {
180 *ebuf++ = '$';
181 *ebuf = 0;
184 if((exp = regcomp(bbuf)) == nil){
185 fprint(2, "bad regexp in machinelist: %s\n", bbuf);
186 killall("regexp");
189 if(regexec(exp, dom, 0, 0)){
190 free(exp);
191 ep = p+strlen(p);
192 q += strspn(q, " \t");
193 if(ep-q+2 > sizeof buf) {
194 fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q);
195 exits("bad big replacement");
197 strncpy(buf, q, ep-q);
198 ebuf = buf+(ep-q);
199 *ebuf = 0;
200 while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t'))
201 *--ebuf = 0;
202 free(file);
203 return buf;
205 free(exp);
207 free(file);
209 return dom;
212 static char*
213 tryfindpicture_user(char *dom, char *user, int depth)
215 static char buf[200];
216 char *p, *q, *nextp, *file;
217 static char *home;
219 if(home == nil)
220 home = getenv("home");
221 if(home == nil)
222 home = getenv("HOME");
223 if(home == nil)
224 return nil;
226 sprint(buf, "%s/lib/face/48x48x%d/.dict", home, depth);
227 if((file = readfile(buf)) == nil)
228 return nil;
230 snprint(buf, sizeof buf, "%s/%s", dom, user);
232 for(p=file; p; p=nextp) {
233 if(nextp = strchr(p, '\n'))
234 *nextp++ = '\0';
236 if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
237 continue;
238 *q++ = 0;
240 if(strcmp(buf, p) == 0) {
241 q += strspn(q, " \t");
242 q = buf+snprint(buf, sizeof buf, "%s/lib/face/48x48x%d/%s", home, depth, q);
243 while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
244 *--q = 0;
245 free(file);
246 return buf;
249 free(file);
250 return nil;
253 static char*
254 tryfindpicture_global(char *dom, char *user, int depth)
256 static char buf[200];
257 char *p, *q, *nextp, *file;
259 sprint(buf, "#9/face/48x48x%d/.dict", depth);
260 if((file = readfile(unsharp(buf))) == nil)
261 return nil;
263 snprint(buf, sizeof buf, "%s/%s", dom, user);
265 for(p=file; p; p=nextp) {
266 if(nextp = strchr(p, '\n'))
267 *nextp++ = '\0';
269 if(*p == '#' || (q = strpbrk(p, " \t")) == nil)
270 continue;
271 *q++ = 0;
273 if(strcmp(buf, p) == 0) {
274 q += strspn(q, " \t");
275 q = buf+snprint(buf, sizeof buf, "#9/face/48x48x%d/%s", depth, q);
276 while(q > buf && (q[-1] == ' ' || q[-1] == '\t'))
277 *--q = 0;
278 free(file);
279 return unsharp(buf);
282 free(file);
283 return nil;
286 static char*
287 tryfindpicture(char *dom, char *user, int depth)
289 char* result;
291 if((result = tryfindpicture_user(dom, user, depth)) != nil)
292 return result;
294 return tryfindpicture_global(dom, user, depth);
297 static char*
298 tryfindfile(char *dom, char *user, int depth)
300 char *p, *q;
302 for(;;){
303 for(p=dom; p; (p=strchr(p, '.')) && p++)
304 if(q = tryfindpicture(p, user, depth))
305 return q;
306 depth >>= 1;
307 if(depth == 0)
308 break;
310 return nil;
313 char*
314 findfile(Face *f, char *dom, char *user)
316 char *p;
317 int depth;
319 if(facedom == nil){
320 facedom = getenv("facedom");
321 if(facedom == nil)
322 facedom = DEFAULT;
325 dom = translatedomain(dom);
326 if(dom == nil)
327 dom = facedom;
329 if(screen == nil)
330 depth = 8;
331 else
332 depth = screen->depth;
334 if(depth > 8)
335 depth = 8;
337 f->unknown = 0;
338 if(p = tryfindfile(dom, user, depth))
339 return p;
340 f->unknown = 1;
341 p = tryfindfile(dom, "unknown", depth);
342 if(p != nil || strcmp(dom, facedom)==0)
343 return p;
344 return tryfindfile("unknown", "unknown", depth);
347 static
348 void
349 clearsaved(void)
351 Facefile *f, *next, **lf;
353 lf = &facefiles;
354 for(f=facefiles; f!=nil; f=next){
355 next = f->next;
356 if(f->ref > 0){
357 *lf = f;
358 lf = &(f->next);
359 continue;
361 if(f->image != display->black && f->image != display->white)
362 freeimage(f->image);
363 free(f->file);
364 free(f);
366 *lf = nil;
367 nsaved = 0;
370 void
371 freefacefile(Facefile *f)
373 if(f==nil || f->ref-->1)
374 return;
375 if(++nsaved > Nsave)
376 clearsaved();
379 static Image*
380 myallocimage(ulong chan)
382 Image *img;
383 img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
384 if(img == nil){
385 clearsaved();
386 img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill);
387 if(img == nil)
388 return nil;
390 return img;
394 static Image*
395 readbit(int fd, ulong chan)
397 char buf[4096], hx[4], *p;
398 uchar data[Facesize*Facesize]; /* more than enough */
399 int nhx, i, n, ndata, nbit;
400 Image *img;
402 n = readn(fd, buf, sizeof buf);
403 if(n <= 0)
404 return nil;
405 if(n >= sizeof buf)
406 n = sizeof(buf)-1;
407 buf[n] = '\0';
409 n = 0;
410 nhx = 0;
411 nbit = chantodepth(chan);
412 ndata = (Facesize*Facesize*nbit)/8;
413 p = buf;
414 while(n < ndata) {
415 p = strpbrk(p+1, "0123456789abcdefABCDEF");
416 if(p == nil)
417 break;
418 if(p[0] == '0' && p[1] == 'x')
419 continue;
421 hx[nhx] = *p;
422 if(++nhx == 2) {
423 hx[nhx] = 0;
424 i = strtoul(hx, 0, 16);
425 data[n++] = i;
426 nhx = 0;
429 if(n < ndata)
430 return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888);
432 img = myallocimage(chan);
433 if(img == nil)
434 return nil;
435 loadimage(img, img->r, data, ndata);
436 return img;
439 static Facefile*
440 readface(char *fn)
442 int x, y, fd;
443 uchar bits;
444 uchar *p;
445 Image *mask;
446 Image *face;
447 char buf[16];
448 uchar data[Facesize*Facesize];
449 uchar mdata[(Facesize*Facesize)/8];
450 Facefile *f;
451 Dir *d;
453 for(f=facefiles; f!=nil; f=f->next){
454 if(strcmp(fn, f->file) == 0){
455 if(f->image == nil)
456 break;
457 if(time(0) - f->rdtime >= 30) {
458 if(dirmtime(fn) != f->mtime){
459 f = nil;
460 break;
462 f->rdtime = time(0);
464 f->ref++;
465 return f;
469 if((fd = open(fn, OREAD)) < 0)
470 return nil;
472 if(readn(fd, buf, sizeof buf) != sizeof buf){
473 close(fd);
474 return nil;
477 seek(fd, 0, 0);
479 mask = nil;
480 if(buf[0] == '0' && buf[1] == 'x'){
481 /* greyscale faces are just masks that we draw black through! */
482 if(buf[2+8] == ',') /* ldepth 1 */
483 mask = readbit(fd, GREY2);
484 else
485 mask = readbit(fd, GREY1);
486 face = display->black;
487 }else{
488 face = readimage(display, fd, 0);
489 if(face == nil)
490 goto Done;
491 else if(face->chan == GREY4 || face->chan == GREY8){ /* greyscale: use inversion as mask */
492 mask = myallocimage(face->chan);
493 /* okay if mask is nil: that will copy the image white background and all */
494 if(mask == nil)
495 goto Done;
497 /* invert greyscale image */
498 draw(mask, mask->r, display->white, nil, ZP);
499 gendraw(mask, mask->r, display->black, ZP, face, face->r.min);
500 freeimage(face);
501 face = display->black;
502 }else if(face->depth == 8){ /* snarf the bytes back and do a fill. */
503 mask = myallocimage(GREY1);
504 if(mask == nil)
505 goto Done;
506 if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){
507 freeimage(mask);
508 goto Done;
510 bits = 0;
511 p = mdata;
512 for(y=0; y<Facesize; y++){
513 for(x=0; x<Facesize; x++){
514 bits <<= 1;
515 if(data[Facesize*y+x] != 0xFF)
516 bits |= 1;
517 if((x&7) == 7)
518 *p++ = bits&0xFF;
521 if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){
522 freeimage(mask);
523 goto Done;
528 Done:
529 /* always add at beginning of list, so updated files don't collide in cache */
530 if(f == nil){
531 f = emalloc(sizeof(Facefile));
532 f->file = estrdup(fn);
533 d = dirfstat(fd);
534 if(d != nil){
535 f->mtime = d->mtime;
536 free(d);
538 f->next = facefiles;
539 facefiles = f;
541 f->ref++;
542 f->image = face;
543 f->mask = mask;
544 f->rdtime = time(0);
545 close(fd);
546 return f;
549 void
550 findbit(Face *f)
552 char *fn;
554 fn = findfile(f, f->str[Sdomain], f->str[Suser]);
555 if(fn) {
556 if(strstr(fn, "unknown"))
557 f->unknown = 1;
558 f->file = readface(fn);
560 if(f->file){
561 f->bit = f->file->image;
562 f->mask = f->file->mask;
563 }else{
564 /* if returns nil, this is still ok: draw(nil) works */
565 f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow);
566 replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize));
567 f->mask = nil;