]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/acme/acme.c
stats: show amount of reclaimable pages (add -r flag)
[plan9front.git] / sys / src / cmd / acme / acme.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <cursor.h>
6 #include <mouse.h>
7 #include <keyboard.h>
8 #include <frame.h>
9 #include <fcall.h>
10 #include <plumb.h>
11 #include "dat.h"
12 #include "fns.h"
13         /* for generating syms in mkfile only: */
14         #include <bio.h>
15         #include "edit.h"
16
17 void    mousethread(void*);
18 void    keyboardthread(void*);
19 void    waitthread(void*);
20 void    xfidallocthread(void*);
21 void    newwindowthread(void*);
22 void plumbproc(void*);
23
24 Reffont **fontcache;
25 int             nfontcache;
26 char            wdir[512] = ".";
27 Reffont *reffonts[2];
28 int             snarffd = -1;
29 int             mainpid;
30 int             plumbsendfd;
31 int             plumbeditfd;
32
33 enum{
34         NSnarf = 1000   /* less than 1024, I/O buffer size */
35 };
36 Rune    snarfrune[NSnarf+1];
37
38 char            *fontnames[2];
39
40 Command *command;
41
42 void    acmeerrorinit(void);
43 void    readfile(Column*, char*);
44 int     shutdown(void*, char*);
45
46 void
47 derror(Display*, char *errorstr)
48 {
49         error(errorstr);
50 }
51
52 void
53 threadmain(int argc, char *argv[])
54 {
55         int i;
56         char *p, *loadfile;
57         char buf[256];
58         Column *c;
59         int ncol;
60         Display *d;
61
62         rfork(RFENVG|RFNAMEG);
63
64         ncol = -1;
65
66         loadfile = nil;
67         ARGBEGIN{
68         case 'a':
69                 globalautoindent = TRUE;
70                 break;
71         case 'b':
72                 bartflag = TRUE;
73                 break;
74         case 'c':
75                 p = ARGF();
76                 if(p == nil)
77                         goto Usage;
78                 ncol = atoi(p);
79                 if(ncol <= 0)
80                         goto Usage;
81                 break;
82         case 'f':
83                 fontnames[0] = ARGF();
84                 if(fontnames[0] == nil)
85                         goto Usage;
86                 break;
87         case 'F':
88                 fontnames[1] = ARGF();
89                 if(fontnames[1] == nil)
90                         goto Usage;
91                 break;
92         case 'l':
93                 loadfile = ARGF();
94                 if(loadfile == nil)
95                         goto Usage;
96                 break;
97         default:
98         Usage:
99                 fprint(2, "usage: acme [-ab] [-c ncol] [-f font] [-F fixedfont] [-l loadfile | file...]\n");
100                 exits("usage");
101         }ARGEND
102
103         if(fontnames[0] == nil)
104                 fontnames[0] = getenv("font");
105         if(fontnames[0] == nil)
106                 fontnames[0] = "/lib/font/bit/vga/unicode.font";
107         if(access(fontnames[0], 0) < 0){
108                 fprint(2, "acme: can't access %s: %r\n", fontnames[0]);
109                 exits("font open");
110         }
111         if(fontnames[1] == nil)
112                 fontnames[1] = fontnames[0];
113         fontnames[0] = estrdup(fontnames[0]);
114         fontnames[1] = estrdup(fontnames[1]);
115
116         quotefmtinstall();
117         cputype = getenv("cputype");
118         objtype = getenv("objtype");
119         home = getenv("home");
120         p = getenv("tabstop");
121         if(p != nil){
122                 maxtab = strtoul(p, nil, 0);
123                 free(p);
124         }
125         if(maxtab == 0)
126                 maxtab = 4; 
127         if(loadfile)
128                 rowloadfonts(loadfile);
129         putenv("font", fontnames[0]);
130         snarffd = open("/dev/snarf", OREAD|OCEXEC);
131         if(cputype){
132                 sprint(buf, "/acme/bin/%s", cputype);
133                 bind(buf, "/bin", MBEFORE);
134         }
135         bind("/acme/bin", "/bin", MBEFORE);
136         getwd(wdir, sizeof wdir);
137
138         if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
139                 fprint(2, "acme: can't open display: %r\n");
140                 exits("geninitdraw");
141         }
142         d = display;
143         font = d->defaultfont;
144
145         reffont.f = font;
146         reffonts[0] = &reffont;
147         incref(&reffont);       /* one to hold up 'font' variable */
148         incref(&reffont);       /* one to hold up reffonts[0] */
149         fontcache = emalloc(sizeof(Reffont*));
150         nfontcache = 1;
151         fontcache[0] = &reffont;
152
153         iconinit();
154         timerinit();
155         rxinit();
156
157         cwait = threadwaitchan();
158         ccommand = chancreate(sizeof(Command**), 0);
159         ckill = chancreate(sizeof(Rune*), 0);
160         cxfidalloc = chancreate(sizeof(Xfid*), 0);
161         cxfidfree = chancreate(sizeof(Xfid*), 0);
162         cnewwindow = chancreate(sizeof(Channel*), 0);
163         cerr = chancreate(sizeof(char*), 0);
164         cedit = chancreate(sizeof(int), 0);
165         cexit = chancreate(sizeof(int), 0);
166         cwarn = chancreate(sizeof(void*), 1);
167         if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
168                 fprint(2, "acme: can't create initial channels: %r\n");
169                 threadexitsall("channels");
170         }
171
172         mousectl = initmouse(nil, screen);
173         if(mousectl == nil){
174                 fprint(2, "acme: can't initialize mouse: %r\n");
175                 threadexitsall("mouse");
176         }
177         mouse = mousectl;
178         keyboardctl = initkeyboard(nil);
179         if(keyboardctl == nil){
180                 fprint(2, "acme: can't initialize keyboard: %r\n");
181                 threadexitsall("keyboard");
182         }
183         mainpid = getpid();
184         plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
185         if(plumbeditfd >= 0){
186                 cplumb = chancreate(sizeof(Plumbmsg*), 0);
187                 proccreate(plumbproc, nil, STACK);
188         }
189         plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
190
191         fsysinit();
192
193         #define WPERCOL 8
194         disk = diskinit();
195         if(!loadfile || !rowload(&row, loadfile, TRUE)){
196                 rowinit(&row, screen->clipr);
197                 if(ncol < 0){
198                         if(argc == 0)
199                                 ncol = 2;
200                         else{
201                                 ncol = (argc+(WPERCOL-1))/WPERCOL;
202                                 if(ncol < 2)
203                                         ncol = 2;
204                         }
205                 }
206                 if(ncol == 0)
207                         ncol = 2;
208                 for(i=0; i<ncol; i++){
209                         c = rowadd(&row, nil, -1);
210                         if(c==nil && i==0)
211                                 error("initializing columns");
212                 }
213                 c = row.col[row.ncol-1];
214                 if(argc == 0)
215                         readfile(c, wdir);
216                 else
217                         for(i=0; i<argc; i++){
218                                 p = utfrrune(argv[i], '/');
219                                 if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
220                                         readfile(c, argv[i]);
221                                 else
222                                         readfile(row.col[i/WPERCOL], argv[i]);
223                         }
224         }
225         flushimage(display, 1);
226
227         acmeerrorinit();
228         threadcreate(keyboardthread, nil, STACK);
229         threadcreate(mousethread, nil, STACK);
230         threadcreate(waitthread, nil, STACK);
231         threadcreate(xfidallocthread, nil, STACK);
232         threadcreate(newwindowthread, nil, STACK);
233
234         threadnotify(shutdown, 1);
235         recvul(cexit);
236         killprocs();
237         threadexitsall(nil);
238 }
239
240 void
241 readfile(Column *c, char *s)
242 {
243         Window *w;
244         Rune rb[256];
245         int nb, nr;
246         Runestr rs;
247
248         w = coladd(c, nil, nil, -1);
249         cvttorunes(s, strlen(s), rb, &nb, &nr, nil);
250         rs = cleanrname((Runestr){rb, nr});
251         winsetname(w, rs.r, rs.nr);
252         textload(&w->body, 0, s, 1);
253         w->body.file->mod = FALSE;
254         w->dirty = FALSE;
255         winsettag(w);
256         textscrdraw(&w->body);
257         textsetselect(&w->tag, w->tag.file->nc, w->tag.file->nc);
258 }
259
260 char *oknotes[] ={
261         "delete",
262         "hangup",
263         "kill",
264         "exit",
265         nil
266 };
267
268 int     dumping;
269
270 int
271 shutdown(void*, char *msg)
272 {
273         int i;
274
275         killprocs();
276         if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
277                 dumping = TRUE;
278                 rowdump(&row, nil);
279         }
280         for(i=0; oknotes[i]; i++)
281                 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
282                         threadexitsall(msg);
283         print("acme: %s\n", msg);
284         abort();
285         return 0;
286 }
287
288 void
289 killprocs(void)
290 {
291         Command *c;
292
293         fsysclose();
294 //      if(display)
295 //              flushimage(display, 1);
296
297         for(c=command; c; c=c->next)
298                 postnote(PNGROUP, c->pid, "hangup");
299         remove(acmeerrorfile);
300 }
301
302 static int errorfd;
303
304 void
305 acmeerrorproc(void *)
306 {
307         char *buf;
308         int n;
309
310         threadsetname("acmeerrorproc");
311         buf = emalloc(8192+1);
312         while((n=read(errorfd, buf, 8192)) >= 0){
313                 buf[n] = '\0';
314                 sendp(cerr, estrdup(buf));
315         }
316 }
317
318 void
319 acmeerrorinit(void)
320 {
321         int fd, pfd[2];
322         char buf[64];
323
324         if(pipe(pfd) < 0)
325                 error("can't create pipe");
326         sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
327         fd = create(acmeerrorfile, OWRITE, 0666);
328         if(fd < 0){
329                 remove(acmeerrorfile);
330                 fd = create(acmeerrorfile, OWRITE, 0666);
331                 if(fd < 0)
332                         error("can't create acmeerror file");
333         }
334         sprint(buf, "%d", pfd[0]);
335         write(fd, buf, strlen(buf));
336         close(fd);
337         /* reopen pfd[1] close on exec */
338         sprint(buf, "/fd/%d", pfd[1]);
339         errorfd = open(buf, OREAD|OCEXEC);
340         if(errorfd < 0)
341                 error("can't re-open acmeerror file");
342         close(pfd[1]);
343         close(pfd[0]);
344         proccreate(acmeerrorproc, nil, STACK);
345 }
346
347 void
348 plumbproc(void *)
349 {
350         Plumbmsg *m;
351
352         threadsetname("plumbproc");
353         for(;;){
354                 m = plumbrecv(plumbeditfd);
355                 if(m == nil)
356                         threadexits(nil);
357                 sendp(cplumb, m);
358         }
359 }
360
361 void
362 keyboardthread(void *)
363 {
364         Rune r;
365         Timer *timer;
366         Text *t;
367         enum { KTimer, KKey, NKALT };
368         static Alt alts[NKALT+1];
369
370         alts[KTimer].c = nil;
371         alts[KTimer].v = nil;
372         alts[KTimer].op = CHANNOP;
373         alts[KKey].c = keyboardctl->c;
374         alts[KKey].v = &r;
375         alts[KKey].op = CHANRCV;
376         alts[NKALT].op = CHANEND;
377
378         timer = nil;
379         typetext = nil;
380         threadsetname("keyboardthread");
381         for(;;){
382                 switch(alt(alts)){
383                 case KTimer:
384                         timerstop(timer);
385                         t = typetext;
386                         if(t!=nil && t->what==Tag){
387                                 winlock(t->w, 'K');
388                                 wincommit(t->w, t);
389                                 winunlock(t->w);
390                                 flushimage(display, 1);
391                         }
392                         alts[KTimer].c = nil;
393                         alts[KTimer].op = CHANNOP;
394                         break;
395                 case KKey:
396                 casekeyboard:
397                         typetext = rowtype(&row, r, mouse->xy);
398                         t = typetext;
399                         if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright))       /* scrolling doesn't change activecol */
400                                 activecol = t->col;
401                         if(t!=nil && t->w!=nil)
402                                 t->w->body.file->curtext = &t->w->body;
403                         if(timer != nil)
404                                 timercancel(timer);
405                         if(t!=nil && t->what==Tag) {
406                                 timer = timerstart(500);
407                                 alts[KTimer].c = timer->c;
408                                 alts[KTimer].op = CHANRCV;
409                         }else{
410                                 timer = nil;
411                                 alts[KTimer].c = nil;
412                                 alts[KTimer].op = CHANNOP;
413                         }
414                         if(nbrecv(keyboardctl->c, &r) > 0)
415                                 goto casekeyboard;
416                         flushimage(display, 1);
417                         break;
418                 }
419         }
420 }
421
422 void
423 mousethread(void *)
424 {
425         Text *t, *argt;
426         int but;
427         uint q0, q1;
428         Window *w;
429         Plumbmsg *pm;
430         Mouse m;
431         char *act;
432         enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
433         static Alt alts[NMALT+1];
434
435         threadsetname("mousethread");
436         alts[MResize].c = mousectl->resizec;
437         alts[MResize].v = nil;
438         alts[MResize].op = CHANRCV;
439         alts[MMouse].c = mousectl->c;
440         alts[MMouse].v = &mousectl->Mouse;
441         alts[MMouse].op = CHANRCV;
442         alts[MPlumb].c = cplumb;
443         alts[MPlumb].v = &pm;
444         alts[MPlumb].op = CHANRCV;
445         alts[MWarnings].c = cwarn;
446         alts[MWarnings].v = nil;
447         alts[MWarnings].op = CHANRCV;
448         if(cplumb == nil)
449                 alts[MPlumb].op = CHANNOP;
450         alts[NMALT].op = CHANEND;
451         
452         for(;;){
453                 qlock(&row);
454                 flushwarnings();
455                 qunlock(&row);
456                 flushimage(display, 1);
457                 switch(alt(alts)){
458                 case MResize:
459                         if(getwindow(display, Refnone) < 0)
460                                 error("attach to window");
461                         scrlresize();
462                         rowresize(&row, screen->clipr);
463                         break;
464                 case MPlumb:
465                         if(strcmp(pm->type, "text") == 0){
466                                 act = plumblookup(pm->attr, "action");
467                                 if(act==nil || strcmp(act, "showfile")==0)
468                                         plumblook(pm);
469                                 else if(strcmp(act, "showdata")==0)
470                                         plumbshow(pm);
471                         }
472                         plumbfree(pm);
473                         break;
474                 case MWarnings:
475                         break;
476                 case MMouse:
477                         /*
478                          * Make a copy so decisions are consistent; mousectl changes
479                          * underfoot.  Can't just receive into m because this introduces
480                          * another race; see /sys/src/libdraw/mouse.c.
481                          */
482                         m = mousectl->Mouse;
483                         qlock(&row);
484                         t = rowwhich(&row, m.xy);
485                         if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
486                                 winlock(mousetext->w, 'M');
487                                 mousetext->eq0 = ~0;
488                                 wincommit(mousetext->w, mousetext);
489                                 winunlock(mousetext->w);
490                         }
491                         mousetext = t;
492                         if(t == nil)
493                                 goto Continue;
494                         w = t->w;
495                         if(t==nil || m.buttons==0)
496                                 goto Continue;
497                         but = 0;
498                         if(m.buttons == 1)
499                                 but = 1;
500                         else if(m.buttons == 2)
501                                 but = 2;
502                         else if(m.buttons == 4)
503                                 but = 3;
504                         barttext = t;
505                         if(t->what==Body && ptinrect(m.xy, t->scrollr)){
506                                 if(but){
507                                         winlock(w, 'M');
508                                         t->eq0 = ~0;
509                                         textscroll(t, but);
510                                         winunlock(w);
511                                 }
512                                 goto Continue;
513                         }
514                         /* scroll buttons, wheels, etc. */
515                         if(t->what==Body && w != nil && (m.buttons & (8|16))){
516                                 if(m.buttons & 8)
517                                         but = Kscrolloneup;
518                                 else
519                                         but = Kscrollonedown;
520                                 winlock(w, 'M');
521                                 t->eq0 = ~0;
522                                 texttype(t, but);
523                                 winunlock(w);
524                                 goto Continue;
525                         }
526                         if(ptinrect(m.xy, t->scrollr)){
527                                 if(but){
528                                         if(t->what == Columntag)
529                                                 rowdragcol(&row, t->col, but);
530                                         else if(t->what == Tag){
531                                                 coldragwin(t->col, t->w, but);
532                                                 if(t->w)
533                                                         barttext = &t->w->body;
534                                         }
535                                         if(t->col)
536                                                 activecol = t->col;
537                                 }
538                                 goto Continue;
539                         }
540                         if(m.buttons){
541                                 if(w)
542                                         winlock(w, 'M');
543                                 t->eq0 = ~0;
544                                 if(w)
545                                         wincommit(w, t);
546                                 else
547                                         textcommit(t, TRUE);
548                                 if(m.buttons & 1){
549                                         textselect(t);
550                                         if(w)
551                                                 winsettag(w);
552                                         argtext = t;
553                                         seltext = t;
554                                         if(t->col)
555                                                 activecol = t->col;     /* button 1 only */
556                                         if(t->w!=nil && t==&t->w->body)
557                                                 activewin = t->w;
558                                 }else if(m.buttons & 2){
559                                         if(textselect2(t, &q0, &q1, &argt))
560                                                 execute(t, q0, q1, FALSE, argt);
561                                 }else if(m.buttons & 4){
562                                         if(textselect3(t, &q0, &q1))
563                                                 look3(t, q0, q1, FALSE);
564                                 }
565                                 if(w)
566                                         winunlock(w);
567                                 goto Continue;
568                         }
569     Continue:
570                         qunlock(&row);
571                         break;
572                 }
573         }
574 }
575
576 /*
577  * There is a race between process exiting and our finding out it was ever created.
578  * This structure keeps a list of processes that have exited we haven't heard of.
579  */
580 typedef struct Pid Pid;
581 struct Pid
582 {
583         int     pid;
584         char    msg[ERRMAX];
585         Pid     *next;
586 };
587
588 void
589 waitthread(void *)
590 {
591         Waitmsg *w;
592         Command *c, *lc;
593         uint pid;
594         int found, ncmd;
595         Rune *cmd;
596         char *err;
597         Text *t;
598         Pid *pids, *p, *lastp;
599         enum { WErr, WKill, WWait, WCmd, NWALT };
600         Alt alts[NWALT+1];
601
602         threadsetname("waitthread");
603         pids = nil;
604         alts[WErr].c = cerr;
605         alts[WErr].v = &err;
606         alts[WErr].op = CHANRCV;
607         alts[WKill].c = ckill;
608         alts[WKill].v = &cmd;
609         alts[WKill].op = CHANRCV;
610         alts[WWait].c = cwait;
611         alts[WWait].v = &w;
612         alts[WWait].op = CHANRCV;
613         alts[WCmd].c = ccommand;
614         alts[WCmd].v = &c;
615         alts[WCmd].op = CHANRCV;
616         alts[NWALT].op = CHANEND;
617
618         command = nil;
619         for(;;){
620                 switch(alt(alts)){
621                 case WErr:
622                         qlock(&row);
623                         warning(nil, "%s", err);
624                         free(err);
625                         flushimage(display, 1);
626                         qunlock(&row);
627                         break;
628                 case WKill:
629                         found = FALSE;
630                         ncmd = runestrlen(cmd);
631                         for(c=command; c; c=c->next){
632                                 /* -1 for blank */
633                                 if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
634                                         if(postnote(PNGROUP, c->pid, "kill") < 0)
635                                                 warning(nil, "kill %S: %r\n", cmd);
636                                         found = TRUE;
637                                 }
638                         }
639                         if(!found)
640                                 warning(nil, "Kill: no process %S\n", cmd);
641                         free(cmd);
642                         break;
643                 case WWait:
644                         pid = w->pid;
645                         lc = nil;
646                         for(c=command; c; c=c->next){
647                                 if(c->pid == pid){
648                                         if(lc)
649                                                 lc->next = c->next;
650                                         else
651                                                 command = c->next;
652                                         break;
653                                 }
654                                 lc = c;
655                         }
656                         qlock(&row);
657                         t = &row.tag;
658                         textcommit(t, TRUE);
659                         if(c == nil){
660                                 /* helper processes use this exit status */
661                                 if(strncmp(w->msg, "libthread", 9) != 0){
662                                         p = emalloc(sizeof(Pid));
663                                         p->pid = pid;
664                                         strncpy(p->msg, w->msg, sizeof(p->msg));
665                                         p->next = pids;
666                                         pids = p;
667                                 }
668                         }else{
669                                 if(search(t, c->name, c->nname)){
670                                         textdelete(t, t->q0, t->q1, TRUE);
671                                         textsetselect(t, 0, 0);
672                                 }
673                                 if(w->msg[0])
674                                         warning(c->md, "%s\n", w->msg);
675                                 flushimage(display, 1);
676                         }
677                         qunlock(&row);
678                         free(w);
679     Freecmd:
680                         if(c){
681                                 if(c->iseditcmd)
682                                         sendul(cedit, 0);
683                                 free(c->text);
684                                 free(c->name);
685                                 fsysdelid(c->md);
686                                 free(c);
687                         }
688                         break;
689                 case WCmd:
690                         /* has this command already exited? */
691                         lastp = nil;
692                         for(p=pids; p!=nil; p=p->next){
693                                 if(p->pid == c->pid){
694                                         if(p->msg[0])
695                                                 warning(c->md, "%s\n", p->msg);
696                                         if(lastp == nil)
697                                                 pids = p->next;
698                                         else
699                                                 lastp->next = p->next;
700                                         free(p);
701                                         goto Freecmd;
702                                 }
703                                 lastp = p;
704                         }
705                         c->next = command;
706                         command = c;
707                         qlock(&row);
708                         t = &row.tag;
709                         textcommit(t, TRUE);
710                         textinsert(t, 0, c->name, c->nname, TRUE);
711                         textsetselect(t, 0, 0);
712                         flushimage(display, 1);
713                         qunlock(&row);
714                         break;
715                 }
716         }
717 }
718
719 void
720 xfidallocthread(void*)
721 {
722         Xfid *xfree, *x;
723         enum { Alloc, Free, N };
724         static Alt alts[N+1];
725
726         threadsetname("xfidallocthread");
727         alts[Alloc].c = cxfidalloc;
728         alts[Alloc].v = nil;
729         alts[Alloc].op = CHANRCV;
730         alts[Free].c = cxfidfree;
731         alts[Free].v = &x;
732         alts[Free].op = CHANRCV;
733         alts[N].op = CHANEND;
734
735         xfree = nil;
736         for(;;){
737                 switch(alt(alts)){
738                 case Alloc:
739                         x = xfree;
740                         if(x)
741                                 xfree = x->next;
742                         else{
743                                 x = emalloc(sizeof(Xfid));
744                                 x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
745                                 x->arg = x;
746                                 threadcreate(xfidctl, x->arg, STACK);
747                         }
748                         sendp(cxfidalloc, x);
749                         break;
750                 case Free:
751                         x->next = xfree;
752                         xfree = x;
753                         break;
754                 }
755         }
756 }
757
758 /* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
759 void
760 newwindowthread(void*)
761 {
762         Window *w;
763
764         threadsetname("newwindowthread");
765
766         for(;;){
767                 /* only fsysproc is talking to us, so synchronization is trivial */
768                 recvp(cnewwindow);
769                 w = makenewwindow(nil);
770                 winsettag(w);
771                 sendp(cnewwindow, w);
772         }
773 }
774
775 Reffont*
776 rfget(int fix, int save, int setfont, char *name)
777 {
778         Reffont *r;
779         Font *f;
780         int i;
781
782         r = nil;
783         if(name == nil){
784                 name = fontnames[fix];
785                 r = reffonts[fix];
786         }
787         if(r == nil){
788                 for(i=0; i<nfontcache; i++)
789                         if(strcmp(name, fontcache[i]->f->name) == 0){
790                                 r = fontcache[i];
791                                 goto Found;
792                         }
793                 f = openfont(display, name);
794                 if(f == nil){
795                         warning(nil, "can't open font file %s: %r\n", name);
796                         return nil;
797                 }
798                 r = emalloc(sizeof(Reffont));
799                 r->f = f;
800                 fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
801                 fontcache[nfontcache++] = r;
802         }
803     Found:
804         if(save){
805                 incref(r);
806                 if(reffonts[fix])
807                         rfclose(reffonts[fix]);
808                 reffonts[fix] = r;
809                 if(name != fontnames[fix]){
810                         free(fontnames[fix]);
811                         fontnames[fix] = estrdup(name);
812                 }
813         }
814         if(setfont){
815                 reffont.f = r->f;
816                 incref(r);
817                 rfclose(reffonts[0]);
818                 font = r->f;
819                 reffonts[0] = r;
820                 incref(r);
821                 iconinit();
822         }
823         incref(r);
824         return r;
825 }
826
827 void
828 rfclose(Reffont *r)
829 {
830         int i;
831
832         if(decref(r) == 0){
833                 for(i=0; i<nfontcache; i++)
834                         if(r == fontcache[i])
835                                 break;
836                 if(i >= nfontcache)
837                         warning(nil, "internal error: can't find font in cache\n");
838                 else{
839                         nfontcache--;
840                         memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
841                 }
842                 freefont(r->f);
843                 free(r);
844         }
845 }
846
847 Cursor boxcursor = {
848         {-7, -7},
849         {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
850          0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
851          0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
852          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
853         {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
854          0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
855          0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
856          0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
857 };
858
859 void
860 iconinit(void)
861 {
862         Rectangle r;
863         Image *tmp;
864
865         /* Blue */
866         tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
867         tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
868         tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
869         tagcols[TEXT] = display->black;
870         tagcols[HTEXT] = display->black;
871
872         /* Yellow */
873         textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
874         textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
875         textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
876         textcols[TEXT] = display->black;
877         textcols[HTEXT] = display->black;
878
879         if(button){
880                 freeimage(button);
881                 freeimage(modbutton);
882                 freeimage(colbutton);
883         }
884
885         r = Rect(0, 0, Scrollwid+2, font->height+1);
886         button = allocimage(display, r, screen->chan, 0, DNofill);
887         draw(button, r, tagcols[BACK], nil, r.min);
888         r.max.x -= 2;
889         border(button, r, 2, tagcols[BORD], ZP);
890
891         r = button->r;
892         modbutton = allocimage(display, r, screen->chan, 0, DNofill);
893         draw(modbutton, r, tagcols[BACK], nil, r.min);
894         r.max.x -= 2;
895         border(modbutton, r, 2, tagcols[BORD], ZP);
896         r = insetrect(r, 2);
897         tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
898         draw(modbutton, r, tmp, nil, ZP);
899         freeimage(tmp);
900
901         r = button->r;
902         colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
903
904         but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
905         but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
906 }
907
908 /*
909  * /dev/snarf updates when the file is closed, so we must open our own
910  * fd here rather than use snarffd
911  */
912
913 /* rio truncates larges snarf buffers, so this avoids using the
914  * service if the string is huge */
915
916 #define MAXSNARF 100*1024
917
918 void
919 putsnarf(void)
920 {
921         int fd, i, n;
922
923         if(snarffd<0 || snarfbuf.nc==0)
924                 return;
925         if(snarfbuf.nc > MAXSNARF)
926                 return;
927         fd = open("/dev/snarf", OWRITE);
928         if(fd < 0)
929                 return;
930         for(i=0; i<snarfbuf.nc; i+=n){
931                 n = snarfbuf.nc-i;
932                 if(n >= NSnarf)
933                         n = NSnarf;
934                 bufread(&snarfbuf, i, snarfrune, n);
935                 if(fprint(fd, "%.*S", n, snarfrune) < 0)
936                         break;
937         }
938         close(fd);
939 }
940
941 void
942 getsnarf()
943 {
944         int nulls;
945
946         if(snarfbuf.nc > MAXSNARF)
947                 return;
948         if(snarffd < 0)
949                 return;
950         seek(snarffd, 0, 0);
951         bufreset(&snarfbuf);
952         bufload(&snarfbuf, 0, snarffd, &nulls);
953 }