| #include <u.h> |
| #include <libc.h> |
| #include <draw.h> |
| #include <plumb.h> |
| #include <regexp.h> |
| #include <bio.h> |
| #include <9pclient.h> |
| #include "faces.h" |
| |
| enum /* number of deleted faces to cache */ |
| { |
| Nsave = 20 |
| }; |
| |
| static Facefile *facefiles; |
| static int nsaved; |
| static char *facedom; |
| static char *libface; |
| static char *homeface; |
| |
| /* |
| * Loading the files is slow enough on a dial-up line to be worth this trouble |
| */ |
| typedef struct Readcache Readcache; |
| struct Readcache { |
| char *file; |
| char *data; |
| long mtime; |
| long rdtime; |
| Readcache *next; |
| }; |
| |
| static Readcache *rcache; |
| |
| ulong |
| dirlen(char *s) |
| { |
| Dir *d; |
| ulong len; |
| |
| d = dirstat(s); |
| if(d == nil) |
| return 0; |
| len = d->length; |
| free(d); |
| return len; |
| } |
| |
| ulong |
| fsdirlen(CFsys *fs,char *s) |
| { |
| Dir *d; |
| ulong len; |
| |
| d = fsdirstat(fs,s); |
| if(d == nil) |
| return 0; |
| len = d->length; |
| free(d); |
| return len; |
| } |
| |
| ulong |
| dirmtime(char *s) |
| { |
| Dir *d; |
| ulong t; |
| |
| d = dirstat(s); |
| if(d == nil) |
| return 0; |
| t = d->mtime; |
| free(d); |
| return t; |
| } |
| |
| static char* |
| doreadfile(char *s) |
| { |
| char *p; |
| int fd, n; |
| ulong len; |
| |
| len = dirlen(s); |
| if(len == 0) |
| return nil; |
| |
| p = malloc(len+1); |
| if(p == nil) |
| return nil; |
| |
| if((fd = open(s, OREAD)) < 0 |
| || (n = readn(fd, p, len)) < 0) { |
| close(fd); |
| free(p); |
| return nil; |
| } |
| |
| p[n] = '\0'; |
| return p; |
| } |
| |
| static char* |
| readfile(char *s) |
| { |
| Readcache *r, **l; |
| char *p; |
| ulong mtime; |
| |
| for(l=&rcache, r=*l; r; l=&r->next, r=*l) { |
| if(strcmp(r->file, s) != 0) |
| continue; |
| |
| /* |
| * if it's less than 30 seconds since we read it, or it |
| * hasn't changed, send back our copy |
| */ |
| if(time(0) - r->rdtime < 30) |
| return strdup(r->data); |
| if(dirmtime(s) == r->mtime) { |
| r->rdtime = time(0); |
| return strdup(r->data); |
| } |
| |
| /* out of date, remove this and fall out of loop */ |
| *l = r->next; |
| free(r->file); |
| free(r->data); |
| free(r); |
| break; |
| } |
| |
| /* add to cache */ |
| mtime = dirmtime(s); |
| if(mtime == 0) |
| return nil; |
| |
| if((p = doreadfile(s)) == nil) |
| return nil; |
| |
| r = malloc(sizeof(*r)); |
| if(r == nil) |
| return nil; |
| r->mtime = mtime; |
| r->file = estrdup(s); |
| r->data = p; |
| r->rdtime = time(0); |
| r->next = rcache; |
| rcache = r; |
| return strdup(r->data); |
| } |
| |
| static char* |
| translatedomain(char *dom, char *list) |
| { |
| static char buf[200]; |
| char *p, *ep, *q, *nextp, *file; |
| char *bbuf, *ebuf; |
| Reprog *exp; |
| |
| if(dom == nil || *dom == 0) |
| return nil; |
| |
| if(list == nil || (file = readfile(list)) == nil) |
| return dom; |
| |
| for(p=file; p; p=nextp) { |
| if(nextp = strchr(p, '\n')) |
| *nextp++ = '\0'; |
| |
| if(*p == '#' || (q = strpbrk(p, " \t")) == nil || q-p > sizeof(buf)-2) |
| continue; |
| |
| bbuf = buf+1; |
| ebuf = buf+(1+(q-p)); |
| strncpy(bbuf, p, ebuf-bbuf); |
| *ebuf = 0; |
| if(*bbuf != '^') |
| *--bbuf = '^'; |
| if(ebuf[-1] != '$') { |
| *ebuf++ = '$'; |
| *ebuf = 0; |
| } |
| |
| if((exp = regcomp(bbuf)) == nil){ |
| fprint(2, "bad regexp in machinelist: %s\n", bbuf); |
| killall("regexp"); |
| } |
| |
| if(regexec(exp, dom, 0, 0)){ |
| free(exp); |
| ep = p+strlen(p); |
| q += strspn(q, " \t"); |
| if(ep-q+2 > sizeof buf) { |
| fprint(2, "huge replacement in machinelist: %.*s\n", utfnlen(q, ep-q), q); |
| exits("bad big replacement"); |
| } |
| strncpy(buf, q, ep-q); |
| ebuf = buf+(ep-q); |
| *ebuf = 0; |
| while(ebuf > buf && (ebuf[-1] == ' ' || ebuf[-1] == '\t')) |
| *--ebuf = 0; |
| free(file); |
| return buf; |
| } |
| free(exp); |
| } |
| free(file); |
| |
| return dom; |
| } |
| |
| static char* |
| tryfindpicture(char *dom, char *user, char *dir, char *dict) |
| { |
| static char buf[1024]; |
| char *file, *p, *nextp, *q; |
| |
| if((file = readfile(dict)) == nil) |
| return nil; |
| |
| snprint(buf, sizeof buf, "%s/%s", dom, user); |
| |
| for(p=file; p; p=nextp){ |
| if(nextp = strchr(p, '\n')) |
| *nextp++ = '\0'; |
| |
| if(*p == '#' || (q = strpbrk(p, " \t")) == nil) |
| continue; |
| *q++ = 0; |
| |
| if(strcmp(buf, p) == 0){ |
| q += strspn(q, " \t"); |
| snprint(buf, sizeof buf, "%s/%s", dir, q); |
| q = buf+strlen(buf); |
| while(q > buf && (q[-1] == ' ' || q[-1] == '\t')) |
| *--q = 0; |
| free(file); |
| return estrdup(buf); |
| } |
| } |
| free(file); |
| return nil; |
| } |
| |
| static char* |
| estrstrdup(char *a, char *b) |
| { |
| char *t; |
| |
| t = emalloc(strlen(a)+strlen(b)+1); |
| strcpy(t, a); |
| strcat(t, b); |
| return t; |
| } |
| |
| static char* |
| tryfindfiledir(char *dom, char *user, char *dir) |
| { |
| char *dict, *ndir, *x, *odom; |
| int fd; |
| int i, n; |
| Dir *d; |
| |
| /* |
| * If this directory has a .machinelist, use it. |
| */ |
| x = estrstrdup(dir, "/.machinelist"); |
| dom = estrdup(translatedomain(dom, x)); |
| free(x); |
| /* |
| * If this directory has a .dict, use it. |
| */ |
| dict = estrstrdup(dir, "/.dict"); |
| if(access(dict, AEXIST) >= 0){ |
| x = tryfindpicture(dom, user, dir, dict); |
| free(dict); |
| free(dom); |
| return x; |
| } |
| free(dict); |
| |
| /* |
| * If not, recurse into subdirectories. |
| * Ignore 48x48xN directories for now. |
| */ |
| if((fd = open(dir, OREAD)) < 0) |
| return nil; |
| while((n = dirread(fd, &d)) > 0){ |
| for(i=0; i<n; i++){ |
| if((d[i].mode&DMDIR)&& strncmp(d[i].name, "48x48x", 6) != 0){ |
| ndir = emalloc(strlen(dir)+1+strlen(d[i].name)+1); |
| strcpy(ndir, dir); |
| strcat(ndir, "/"); |
| strcat(ndir, d[i].name); |
| if((x = tryfindfiledir(dom, user, ndir)) != nil){ |
| free(ndir); |
| free(d); |
| close(fd); |
| free(dom); |
| return x; |
| } |
| } |
| } |
| free(d); |
| } |
| close(fd); |
| |
| /* |
| * Handle 48x48xN directories in the right order. |
| */ |
| ndir = estrstrdup(dir, "/48x48x8"); |
| for(i=8; i>0; i>>=1){ |
| ndir[strlen(ndir)-1] = i+'0'; |
| if(access(ndir, AEXIST) >= 0 && (x = tryfindfiledir(dom, user, ndir)) != nil){ |
| free(ndir); |
| free(dom); |
| return x; |
| } |
| } |
| free(ndir); |
| free(dom); |
| return nil; |
| } |
| |
| static char* |
| tryfindfile(char *dom, char *user) |
| { |
| char *p; |
| |
| while(dom && *dom){ |
| if(homeface && (p = tryfindfiledir(dom, user, homeface)) != nil) |
| return p; |
| if((p = tryfindfiledir(dom, user, libface)) != nil) |
| return p; |
| if((dom = strchr(dom, '.')) == nil) |
| break; |
| dom++; |
| } |
| return nil; |
| } |
| |
| char* |
| findfile(Face *f, char *dom, char *user) |
| { |
| char *p; |
| |
| if(facedom == nil){ |
| facedom = getenv("facedom"); |
| if(facedom == nil) |
| facedom = DEFAULT; |
| } |
| if(libface == nil) |
| libface = unsharp("#9/face"); |
| if(homeface == nil) |
| homeface = smprint("%s/lib/face", getenv("HOME")); |
| |
| if(dom == nil) |
| dom = facedom; |
| |
| f->unknown = 0; |
| if((p = tryfindfile(dom, user)) != nil) |
| return p; |
| f->unknown = 1; |
| p = tryfindfile(dom, "unknown"); |
| if(p != nil || strcmp(dom, facedom) == 0) |
| return p; |
| return tryfindfile("unknown", "unknown"); |
| } |
| |
| static |
| void |
| clearsaved(void) |
| { |
| Facefile *f, *next, **lf; |
| |
| lf = &facefiles; |
| for(f=facefiles; f!=nil; f=next){ |
| next = f->next; |
| if(f->ref > 0){ |
| *lf = f; |
| lf = &(f->next); |
| continue; |
| } |
| if(f->image != display->black && f->image != display->white) |
| freeimage(f->image); |
| free(f->file); |
| free(f); |
| } |
| *lf = nil; |
| nsaved = 0; |
| } |
| |
| void |
| freefacefile(Facefile *f) |
| { |
| if(f==nil || f->ref-->1) |
| return; |
| if(++nsaved > Nsave) |
| clearsaved(); |
| } |
| |
| static Image* |
| myallocimage(ulong chan) |
| { |
| Image *img; |
| img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill); |
| if(img == nil){ |
| clearsaved(); |
| img = allocimage(display, Rect(0,0,Facesize,Facesize), chan, 0, DNofill); |
| if(img == nil) |
| return nil; |
| } |
| return img; |
| } |
| |
| |
| static Image* |
| readbit(int fd, ulong chan) |
| { |
| char buf[4096], hx[4], *p; |
| uchar data[Facesize*Facesize]; /* more than enough */ |
| int nhx, i, n, ndata, nbit; |
| Image *img; |
| |
| n = readn(fd, buf, sizeof buf); |
| if(n <= 0) |
| return nil; |
| if(n >= sizeof buf) |
| n = sizeof(buf)-1; |
| buf[n] = '\0'; |
| |
| n = 0; |
| nhx = 0; |
| nbit = chantodepth(chan); |
| ndata = (Facesize*Facesize*nbit)/8; |
| p = buf; |
| while(n < ndata) { |
| p = strpbrk(p+1, "0123456789abcdefABCDEF"); |
| if(p == nil) |
| break; |
| if(p[0] == '0' && p[1] == 'x') |
| continue; |
| |
| hx[nhx] = *p; |
| if(++nhx == 2) { |
| hx[nhx] = 0; |
| i = strtoul(hx, 0, 16); |
| data[n++] = i; |
| nhx = 0; |
| } |
| } |
| if(n < ndata) |
| return allocimage(display, Rect(0,0,Facesize,Facesize), CMAP8, 0, 0x88888888); |
| |
| img = myallocimage(chan); |
| if(img == nil) |
| return nil; |
| loadimage(img, img->r, data, ndata); |
| return img; |
| } |
| |
| static Facefile* |
| readface(char *fn) |
| { |
| int x, y, fd; |
| uchar bits; |
| uchar *p; |
| Image *mask; |
| Image *face; |
| char buf[16]; |
| uchar data[Facesize*Facesize]; |
| uchar mdata[(Facesize*Facesize)/8]; |
| Facefile *f; |
| Dir *d; |
| |
| for(f=facefiles; f!=nil; f=f->next){ |
| if(strcmp(fn, f->file) == 0){ |
| if(f->image == nil) |
| break; |
| if(time(0) - f->rdtime >= 30) { |
| if(dirmtime(fn) != f->mtime){ |
| f = nil; |
| break; |
| } |
| f->rdtime = time(0); |
| } |
| f->ref++; |
| return f; |
| } |
| } |
| |
| if((fd = open(fn, OREAD)) < 0) |
| return nil; |
| |
| if(readn(fd, buf, sizeof buf) != sizeof buf){ |
| close(fd); |
| return nil; |
| } |
| |
| seek(fd, 0, 0); |
| |
| mask = nil; |
| if(buf[0] == '0' && buf[1] == 'x'){ |
| /* greyscale faces are just masks that we draw black through! */ |
| if(buf[2+8] == ',') /* ldepth 1 */ |
| mask = readbit(fd, GREY2); |
| else |
| mask = readbit(fd, GREY1); |
| face = display->black; |
| }else{ |
| face = readimage(display, fd, 0); |
| if(face == nil) |
| goto Done; |
| else if(face->chan == GREY4 || face->chan == GREY8){ /* greyscale: use inversion as mask */ |
| mask = myallocimage(face->chan); |
| /* okay if mask is nil: that will copy the image white background and all */ |
| if(mask == nil) |
| goto Done; |
| |
| /* invert greyscale image */ |
| draw(mask, mask->r, display->white, nil, ZP); |
| gendraw(mask, mask->r, display->black, ZP, face, face->r.min); |
| freeimage(face); |
| face = display->black; |
| }else if(face->depth == 8){ /* snarf the bytes back and do a fill. */ |
| mask = myallocimage(GREY1); |
| if(mask == nil) |
| goto Done; |
| if(unloadimage(face, face->r, data, Facesize*Facesize) != Facesize*Facesize){ |
| freeimage(mask); |
| goto Done; |
| } |
| bits = 0; |
| p = mdata; |
| for(y=0; y<Facesize; y++){ |
| for(x=0; x<Facesize; x++){ |
| bits <<= 1; |
| if(data[Facesize*y+x] != 0xFF) |
| bits |= 1; |
| if((x&7) == 7) |
| *p++ = bits&0xFF; |
| } |
| } |
| if(loadimage(mask, mask->r, mdata, sizeof mdata) != sizeof mdata){ |
| freeimage(mask); |
| goto Done; |
| } |
| } |
| } |
| |
| Done: |
| /* always add at beginning of list, so updated files don't collide in cache */ |
| if(f == nil){ |
| f = emalloc(sizeof(Facefile)); |
| f->file = estrdup(fn); |
| d = dirfstat(fd); |
| if(d != nil){ |
| f->mtime = d->mtime; |
| free(d); |
| } |
| f->next = facefiles; |
| facefiles = f; |
| } |
| f->ref++; |
| f->image = face; |
| f->mask = mask; |
| f->rdtime = time(0); |
| close(fd); |
| return f; |
| } |
| |
| void |
| findbit(Face *f) |
| { |
| char *fn; |
| |
| fn = findfile(f, f->str[Sdomain], f->str[Suser]); |
| if(fn) { |
| if(strstr(fn, "unknown")) |
| f->unknown = 1; |
| f->file = readface(fn); |
| } |
| if(f->file){ |
| f->bit = f->file->image; |
| f->mask = f->file->mask; |
| }else{ |
| /* if returns nil, this is still ok: draw(nil) works */ |
| f->bit = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DYellow); |
| replclipr(f->bit, 1, Rect(0, 0, Facesize, Facesize)); |
| f->mask = nil; |
| } |
| } |