master
acme.c
ohmu
·
2026-09-23
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 <libsec.h>
12#include <unistd.h>
13#include <sys/stat.h>
14
15#include "dat.h"
16#include "fns.h"
17#include "parser.h"
18
19/* for generating syms in mkfile only: */
20#include <bio.h>
21#include "edit.h"
22
23Display *xdisplay;
24Window xwindow;
25Cursor xcursor;
26
27void mousethread(void*);
28void keyboardthread(void*);
29void waitthread(void*);
30void xfidallocthread(void*);
31void newwindowthread(void*);
32void plumbproc(void*);
33int timefmt(Fmt*);
34
35Reffont **fontcache;
36int nfontcache;
37char wdir[512] = ".";
38char *fontnames[2];
39Reffont *reffonts[2];
40int snarffd = -1;
41char *mtpt;
42
43enum{
44 NSnarf = 1000 /* less than 1024, I/O buffer size */
45};
46Rune snarfrune[NSnarf+1];
47
48Command *command;
49
50void shutdownthread(void*);
51void acmeerrorinit(void);
52void readfile(Column*, char*);
53static int shutdown(void*, char*);
54
55void
56derror(Display *d, char *errorstr)
57{
58 USED(d);
59 error(errorstr);
60}
61
62void
63threadmain(int argc, char *argv[])
64{
65 int i;
66 char *p, *loadfile;
67 Column *c;
68 Display *d;
69 char mtptbuf[256];
70 rfork(RFENVG|RFNAMEG);
71
72 loadfile = nil;
73 PID = getpid();
74
75 ARGBEGIN{
76 case 'D':
77 {extern int _threaddebuglevel;
78 _threaddebuglevel = ~0;
79 }
80 break;
81
82 case 'l':
83 loadfile = ARGF();
84 if(loadfile == nil)
85 goto Usage;
86 break;
87 case 'm':
88 mtpt = ARGF();
89 if(mtpt == nil)
90 goto Usage;
91 break;
92 case 'w':
93 winsize = ARGF();
94 if(winsize == nil)
95 goto Usage;
96 break;
97 case 'c':
98 customconfigpath = ARGF();
99 if (customconfigpath == nil)
100 goto Usage;
101 break;
102 default:
103 Usage:
104 fprint(2, "usage: acme -m mountpoint -l dumpfile -w winsize -c custom_config WxH\n");
105 threadexitsall("usage");
106 }ARGEND
107
108 if(mtpt == nil){
109 snprintf(mtptbuf, sizeof(mtptbuf), "/tmp/acme.%d", PID);
110 mtpt = mtptbuf;
111 }
112 mkdir(mtpt, 0700);
113
114 initconfig(customconfigpath);
115
116 if(acmeshell)
117 putenv("SHELL", acmeshell);
118
119 quotefmtinstall();
120 fmtinstall('t', timefmt);
121
122 cputype = getenv("cputype");
123 objtype = getenv("objtype");
124 home = getenv("HOME");
125 p = getenv("tabstop");
126 if(p != nil){
127 maxtab = strtoul(p, nil, 0);
128 free(p);
129 }
130 if(maxtab == 0)
131 maxtab = 4;
132 if(loadfile)
133 rowloadfonts(loadfile);
134 putenv("font", fontnames[0]);
135 snarffd = open("/dev/snarf", OREAD|OCEXEC);
136/*
137 if(cputype){
138 sprint(buf, "/acme/bin/%s", cputype);
139 bind(buf, "/bin", MBEFORE);
140 }
141 bind("/acme/bin", "/bin", MBEFORE);
142*/
143 getwd(wdir, sizeof wdir);
144
145/*
146 if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
147 fprint(2, "acme: can't open display: %r\n");
148 threadexitsall("geninitdraw");
149 }
150*/
151 if(initdraw(derror, fontnames[0], "acme") < 0){
152 fprint(2, "acme: can't open display: %r\n");
153 threadexitsall("initdraw");
154 }
155
156 d = display;
157 font = d->defaultfont;
158/*assert(font); */
159
160 reffont.f = font;
161 reffonts[0] = &reffont;
162 incref(&reffont.ref); /* one to hold up 'font' variable */
163 incref(&reffont.ref); /* one to hold up reffonts[0] */
164 fontcache = emalloc(sizeof(Reffont*));
165 nfontcache = 1;
166 fontcache[0] = &reffont;
167
168 iconinit();
169 timerinit();
170 rxinit();
171
172 cwait = threadwaitchan();
173 ccommand = chancreate(sizeof(Command**), 0);
174 ckill = chancreate(sizeof(Rune*), 0);
175 cxfidalloc = chancreate(sizeof(Xfid*), 0);
176 cxfidfree = chancreate(sizeof(Xfid*), 0);
177 cnewwindow = chancreate(sizeof(Channel*), 0);
178 cerr = chancreate(sizeof(char*), 0);
179 cedit = chancreate(sizeof(int), 0);
180 cexit = chancreate(sizeof(int), 0);
181 cwarn = chancreate(sizeof(void*), 1);
182 if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
183 fprint(2, "acme: can't create initial channels: %r\n");
184 threadexitsall("channels");
185 }
186 chansetname(ccommand, "ccommand");
187 chansetname(ckill, "ckill");
188 chansetname(cxfidalloc, "cxfidalloc");
189 chansetname(cxfidfree, "cxfidfree");
190 chansetname(cnewwindow, "cnewwindow");
191 chansetname(cerr, "cerr");
192 chansetname(cedit, "cedit");
193 chansetname(cexit, "cexit");
194 chansetname(cwarn, "cwarn");
195 mousectl = initmouse(nil, screen);
196 if(mousectl == nil){
197 fprint(2, "acme: can't initialize mouse: %r\n");
198 threadexitsall("mouse");
199 }
200 mouse = &mousectl->m;
201 keyboardctl = initkeyboard(nil);
202 if(keyboardctl == nil){
203 fprint(2, "acme: can't initialize keyboard: %r\n");
204 threadexitsall("keyboard");
205 }
206 startplumbing();
207/*
208 plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
209 if(plumbeditfd < 0)
210 fprint(2, "acme: can't initialize plumber: %r\n");
211 else{
212 cplumb = chancreate(sizeof(Plumbmsg*), 0);
213 threadcreate(plumbproc, nil, STACK);
214 }
215 plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
216*/
217
218 fsysinit();
219
220 #define WPERCOL 8
221 disk = diskinit();
222 if(!loadfile || !rowload(&row, loadfile, TRUE)){
223 rowinit(&row, screen->clipr);
224 if(ncol < 0){
225 if(argc == 0)
226 ncol = 2;
227 else{
228 ncol = (argc+(WPERCOL-1))/WPERCOL;
229 if(ncol < 2)
230 ncol = 2;
231 }
232 }
233 if(ncol == 0)
234 ncol = 2;
235 for(i=0; i<ncol; i++){
236 c = rowadd(&row, nil, -1);
237 if(c==nil && i==0)
238 error("initializing columns");
239 }
240 c = row.col[row.ncol-1];
241 if (openarg) {
242 if (argc == 0)
243 readfile(c, wdir);
244 else {
245 for(i=0; i<argc; i++){
246 p = utfrrune(argv[i], '/');
247 if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
248 readfile(c, argv[i]);
249 else
250 readfile(row.col[i/WPERCOL], argv[i]);
251 }
252 }
253 }
254 }
255 flushimage(display, 1);
256
257 acmeerrorinit();
258 threadcreate(keyboardthread, nil, STACK);
259 threadcreate(mousethread, nil, STACK);
260 threadcreate(waitthread, nil, STACK);
261 threadcreate(xfidallocthread, nil, STACK);
262 threadcreate(newwindowthread, nil, STACK);
263/* threadcreate(shutdownthread, nil, STACK); */
264 threadnotify(shutdown, 1);
265 recvul(cexit);
266 killprocs();
267 threadexitsall(nil);
268}
269
270void
271readfile(Column *c, char *s)
272{
273 Window *w;
274 Rune rb[256];
275 int nr;
276 Runestr rs;
277
278 w = coladd(c, nil, nil, -1);
279 if(s[0] != '/')
280 runesnprint(rb, sizeof rb, "%s/%s", wdir, s);
281 else
282 runesnprint(rb, sizeof rb, "%s", s);
283 nr = runestrlen(rb);
284 rs = cleanrname(runestr(rb, nr));
285 winsetname(w, rs.r, rs.nr);
286 textload(&w->body, 0, s, 1);
287 w->body.file->mod = FALSE;
288 w->dirty = FALSE;
289 winsettag(w);
290 winresize(w, w->r, FALSE, TRUE);
291 textscrdraw(&w->body);
292 textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
293 xfidlog(w, "new");
294}
295
296char *ignotes[] = {
297 "sys: write on closed pipe",
298 "sys: ttin",
299 "sys: ttou",
300 "sys: tstp",
301 nil
302};
303
304char *oknotes[] ={
305 "delete",
306 "hangup",
307 "kill",
308 "exit",
309 nil
310};
311
312int dumping;
313
314static int
315shutdown(void *v, char *msg)
316{
317 int i;
318
319 USED(v);
320
321 for(i=0; ignotes[i]; i++)
322 if(strncmp(ignotes[i], msg, strlen(ignotes[i])) == 0)
323 return 1;
324
325 killprocs();
326 if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==PID){
327 dumping = TRUE;
328 rowdump(&row, nil);
329 }
330 for(i=0; oknotes[i]; i++)
331 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
332 threadexitsall(msg);
333 print("acme: %s\n", msg);
334 return 0;
335}
336
337/*
338void
339shutdownthread(void *v)
340{
341 char *msg;
342 Channel *c;
343
344 USED(v);
345
346 threadsetname("shutdown");
347 c = threadnotechan();
348 while((msg = recvp(c)) != nil)
349 shutdown(nil, msg);
350}
351*/
352
353void
354killprocs(void)
355{
356 Command *c;
357
358 fsysclose();
359/* if(display) */
360/* flushimage(display, 1); */
361
362 for(c=command; c; c=c->next)
363 postnote(PNGROUP, c->pid, "hangup");
364}
365
366static int errorfd;
367int erroutfd;
368
369void
370acmeerrorproc(void *v)
371{
372 char *buf;
373 int n;
374
375 USED(v);
376 threadsetname("acmeerrorproc");
377 buf = emalloc(8192+1);
378 while((n=read(errorfd, buf, 8192)) >= 0){
379 buf[n] = '\0';
380 sendp(cerr, estrdup(buf));
381 }
382}
383
384void
385acmeerrorinit(void)
386{
387 int pfd[2];
388
389 if(pipe(pfd) < 0)
390 error("can't create pipe");
391#if 0
392 sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), PID);
393 fd = create(acmeerrorfile, OWRITE, 0666);
394 if(fd < 0){
395 remove(acmeerrorfile);
396 fd = create(acmeerrorfile, OWRITE, 0666);
397 if(fd < 0)
398 error("can't create acmeerror file");
399 }
400 sprint(buf, "%d", pfd[0]);
401 write(fd, buf, strlen(buf));
402 close(fd);
403 /* reopen pfd[1] close on exec */
404 sprint(buf, "/fd/%d", pfd[1]);
405 errorfd = open(buf, OREAD|OCEXEC);
406#endif
407 fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
408 fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
409 erroutfd = pfd[0];
410 errorfd = pfd[1];
411 if(errorfd < 0)
412 error("can't re-open acmeerror file");
413 proccreate(acmeerrorproc, nil, STACK);
414}
415
416/*
417void
418plumbproc(void *v)
419{
420 Plumbmsg *m;
421
422 USED(v);
423 threadsetname("plumbproc");
424 for(;;){
425 m = threadplumbrecv(plumbeditfd);
426 if(m == nil)
427 threadexits(nil);
428 sendp(cplumb, m);
429 }
430}
431*/
432
433void
434keyboardthread(void *v)
435{
436 Rune r;
437 Timer *timer;
438 Text *t;
439 enum { KTimer, KKey, NKALT };
440 static Alt alts[NKALT+1];
441
442 USED(v);
443 alts[KTimer].c = nil;
444 alts[KTimer].v = nil;
445 alts[KTimer].op = CHANNOP;
446 alts[KKey].c = keyboardctl->c;
447 alts[KKey].v = &r;
448 alts[KKey].op = CHANRCV;
449 alts[NKALT].op = CHANEND;
450
451 timer = nil;
452 typetext = nil;
453 threadsetname("keyboardthread");
454 for(;;){
455 switch(alt(alts)){
456 case KTimer:
457 timerstop(timer);
458 t = typetext;
459 if(t!=nil && t->what==Tag){
460 winlock(t->w, 'K');
461 wincommit(t->w, t);
462 winunlock(t->w);
463 flushimage(display, 1);
464 }
465 alts[KTimer].c = nil;
466 alts[KTimer].op = CHANNOP;
467 break;
468 case KKey:
469 casekeyboard:
470 typetext = rowtype(&row, r, mouse->xy);
471 t = typetext;
472 if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
473 activecol = t->col;
474 if(t!=nil && t->w!=nil)
475 t->w->body.file->curtext = &t->w->body;
476 if(timer != nil)
477 timercancel(timer);
478 if(t!=nil && t->what==Tag) {
479 timer = timerstart(500);
480 alts[KTimer].c = timer->c;
481 alts[KTimer].op = CHANRCV;
482 }else{
483 timer = nil;
484 alts[KTimer].c = nil;
485 alts[KTimer].op = CHANNOP;
486 }
487 if(nbrecv(keyboardctl->c, &r) > 0)
488 goto casekeyboard;
489 flushimage(display, 1);
490 break;
491 }
492 }
493}
494
495void
496mousethread(void *v)
497{
498 Text *t, *argt;
499 int but;
500 uint q0, q1;
501 Window *w;
502 Plumbmsg *pm;
503 Mouse m;
504 char *act;
505 enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
506 static Alt alts[NMALT+1];
507 Image *mainbg;
508
509 USED(v);
510 threadsetname("mousethread");
511 alts[MResize].c = mousectl->resizec;
512 alts[MResize].v = nil;
513 alts[MResize].op = CHANRCV;
514 alts[MMouse].c = mousectl->c;
515 alts[MMouse].v = &mousectl->m;
516 alts[MMouse].op = CHANRCV;
517 alts[MPlumb].c = cplumb;
518 alts[MPlumb].v = ±
519 alts[MPlumb].op = CHANRCV;
520 alts[MWarnings].c = cwarn;
521 alts[MWarnings].v = nil;
522 alts[MWarnings].op = CHANRCV;
523 if(cplumb == nil)
524 alts[MPlumb].op = CHANNOP;
525 alts[NMALT].op = CHANEND;
526
527 for(;;){
528 qlock(&row.lk);
529 flushwarnings();
530 qunlock(&row.lk);
531 flushimage(display, 1);
532 switch(alt(alts)){
533 case MResize:
534 if(getwindow(display, Refnone) < 0)
535 error("attach to window");
536
537 if(ispastel("root"))
538 mainbg = allocimagemix(display, C_ROOTBG, C_MIX);
539 else
540 mainbg = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_ROOTBG);
541 draw(screen, screen->r, mainbg, nil, ZP);
542 iconinit();
543 scrlresize();
544 rowresize(&row, screen->clipr);
545 break;
546 case MPlumb:
547 if(strcmp(pm->type, "text") == 0){
548 act = plumblookup(pm->attr, "action");
549 if(act==nil || strcmp(act, "showfile")==0)
550 plumblook(pm);
551 else if(strcmp(act, "showdata")==0)
552 plumbshow(pm);
553 }
554 plumbfree(pm);
555 break;
556 case MWarnings:
557 break;
558 case MMouse:
559 /*
560 * Make a copy so decisions are consistent; mousectl changes
561 * underfoot. Can't just receive into m because this introduces
562 * another race; see /sys/src/libdraw/mouse.c.
563 */
564 m = mousectl->m;
565 qlock(&row.lk);
566 t = rowwhich(&row, m.xy);
567
568 if((t!=mousetext && t!=nil && t->w!=nil) &&
569 (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
570 xfidlog(t->w, "focus");
571 }
572
573 if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
574 winlock(mousetext->w, 'M');
575 mousetext->eq0 = ~0;
576 wincommit(mousetext->w, mousetext);
577 winunlock(mousetext->w);
578 }
579 mousetext = t;
580 if(t == nil)
581 goto Continue;
582 w = t->w;
583 if(t==nil || m.buttons==0)
584 goto Continue;
585 but = 0;
586 if(m.buttons == 1)
587 but = 1;
588 else if(m.buttons == 2)
589 but = 2;
590 else if(m.buttons == 4)
591 but = 3;
592 barttext = t;
593 if(t->what==Body && ptinrect(m.xy, t->scrollr)){
594 if(but){
595 if(swapscrollbuttons){
596 if(but == 1)
597 but = 3;
598 else if(but == 3)
599 but = 1;
600 }
601 winlock(w, 'M');
602 t->eq0 = ~0;
603 textscroll(t, but);
604 winunlock(w);
605 }
606 goto Continue;
607 }
608 /* scroll buttons, wheels, etc. */
609 if(w != nil && (m.buttons & (8|16))){
610 if(m.buttons & 8)
611 but = Kscrolloneup;
612 else
613 but = Kscrollonedown;
614 winlock(w, 'M');
615 t->eq0 = ~0;
616 texttype(t, but);
617 winunlock(w);
618 goto Continue;
619 }
620 if(ptinrect(m.xy, t->scrollr)){
621 if(but){
622 if(t->what == Columntag)
623 rowdragcol(&row, t->col, but);
624 else if(t->what == Tag){
625 coldragwin(t->col, t->w, but);
626 if(t->w)
627 barttext = &t->w->body;
628 }
629 if(t->col)
630 activecol = t->col;
631 }
632 goto Continue;
633 }
634 if(m.buttons){
635 if(w)
636 winlock(w, 'M');
637 t->eq0 = ~0;
638 if(w)
639 wincommit(w, t);
640 else
641 textcommit(t, TRUE);
642 if(m.buttons & 1){
643 textselect(t);
644 if(w)
645 winsettag(w);
646 argtext = t;
647 seltext = t;
648 if(t->col)
649 activecol = t->col; /* button 1 only */
650 if(t->w!=nil && t==&t->w->body)
651 activewin = t->w;
652 }else if(m.buttons & 2){
653 if(textselect2(t, &q0, &q1, &argt))
654 execute(t, q0, q1, FALSE, argt);
655 }else if(m.buttons & 4){
656 if(textselect3(t, &q0, &q1))
657 look3(t, q0, q1, FALSE);
658 }
659 if(w)
660 winunlock(w);
661 goto Continue;
662 }
663 Continue:
664 qunlock(&row.lk);
665 break;
666 }
667 }
668}
669
670/*
671 * There is a race between process exiting and our finding out it was ever created.
672 * This structure keeps a list of processes that have exited we haven't heard of.
673 */
674typedef struct Pid Pid;
675struct Pid
676{
677 int pid;
678 char msg[ERRMAX];
679 Pid *next;
680};
681
682void
683waitthread(void *v)
684{
685 Waitmsg *w;
686 Command *c, *lc;
687 uint pid;
688 int found, ncmd;
689 Rune *cmd;
690 char *err;
691 Text *t;
692 Pid *pids, *p, *lastp;
693 enum { WErr, WKill, WWait, WCmd, NWALT };
694 Alt alts[NWALT+1];
695
696 USED(v);
697 threadsetname("waitthread");
698 pids = nil;
699 alts[WErr].c = cerr;
700 alts[WErr].v = &err;
701 alts[WErr].op = CHANRCV;
702 alts[WKill].c = ckill;
703 alts[WKill].v = &cmd;
704 alts[WKill].op = CHANRCV;
705 alts[WWait].c = cwait;
706 alts[WWait].v = &w;
707 alts[WWait].op = CHANRCV;
708 alts[WCmd].c = ccommand;
709 alts[WCmd].v = &c;
710 alts[WCmd].op = CHANRCV;
711 alts[NWALT].op = CHANEND;
712
713 command = nil;
714 for(;;){
715 switch(alt(alts)){
716 case WErr:
717 qlock(&row.lk);
718 warning(nil, "%s", err);
719 free(err);
720 flushimage(display, 1);
721 qunlock(&row.lk);
722 break;
723 case WKill:
724 found = FALSE;
725 ncmd = runestrlen(cmd);
726 for(c=command; c; c=c->next){
727 /* -1 for blank */
728 if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
729 if(postnote(PNGROUP, c->pid, "kill") < 0)
730 warning(nil, "kill %S: %r\n", cmd);
731 found = TRUE;
732 }
733 }
734 if(!found)
735 warning(nil, "Kill: no process %S\n", cmd);
736 free(cmd);
737 break;
738 case WWait:
739 pid = w->pid;
740 lc = nil;
741 for(c=command; c; c=c->next){
742 if(c->pid == pid){
743 if(lc)
744 lc->next = c->next;
745 else
746 command = c->next;
747 break;
748 }
749 lc = c;
750 }
751 qlock(&row.lk);
752 t = &row.tag;
753 textcommit(t, TRUE);
754 if(c == nil){
755 /* helper processes use this exit status */
756 if(strncmp(w->msg, "libthread", 9) != 0){
757 p = emalloc(sizeof(Pid));
758 p->pid = pid;
759 strncpy(p->msg, w->msg, sizeof(p->msg));
760 p->next = pids;
761 pids = p;
762 }
763 }else{
764 if(search(t, c->name, c->nname)){
765 textdelete(t, t->q0, t->q1, TRUE);
766 textsetselect(t, 0, 0);
767 }
768 if(w->msg[0])
769 warning(c->md, "%.*S: exit %s\n", c->nname-1, c->name, w->msg);
770 flushimage(display, 1);
771 }
772 qunlock(&row.lk);
773 free(w);
774 Freecmd:
775 if(c){
776 if(c->iseditcmd)
777 sendul(cedit, 0);
778 free(c->text);
779 free(c->name);
780 fsysdelid(c->md);
781 free(c);
782 }
783 break;
784 case WCmd:
785 /* has this command already exited? */
786 lastp = nil;
787 for(p=pids; p!=nil; p=p->next){
788 if(p->pid == c->pid){
789 if(p->msg[0])
790 warning(c->md, "%s\n", p->msg);
791 if(lastp == nil)
792 pids = p->next;
793 else
794 lastp->next = p->next;
795 free(p);
796 goto Freecmd;
797 }
798 lastp = p;
799 }
800 c->next = command;
801 command = c;
802 qlock(&row.lk);
803 t = &row.tag;
804 textcommit(t, TRUE);
805 textinsert(t, 0, c->name, c->nname, TRUE);
806 textsetselect(t, 0, 0);
807 flushimage(display, 1);
808 qunlock(&row.lk);
809 break;
810 }
811 }
812}
813
814void
815xfidallocthread(void *v)
816{
817 Xfid *xfree, *x;
818 enum { Alloc, Free, N };
819 static Alt alts[N+1];
820
821 USED(v);
822 threadsetname("xfidallocthread");
823 alts[Alloc].c = cxfidalloc;
824 alts[Alloc].v = nil;
825 alts[Alloc].op = CHANRCV;
826 alts[Free].c = cxfidfree;
827 alts[Free].v = &x;
828 alts[Free].op = CHANRCV;
829 alts[N].op = CHANEND;
830
831 xfree = nil;
832 for(;;){
833 switch(alt(alts)){
834 case Alloc:
835 x = xfree;
836 if(x)
837 xfree = x->next;
838 else{
839 x = emalloc(sizeof(Xfid));
840 x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
841 chansetname(x->c, "xc%p", x->c);
842 x->arg = x;
843 threadcreate(xfidctl, x->arg, STACK);
844 }
845 sendp(cxfidalloc, x);
846 break;
847 case Free:
848 x->next = xfree;
849 xfree = x;
850 break;
851 }
852 }
853}
854
855/* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
856void
857newwindowthread(void *v)
858{
859 Window *w;
860
861 USED(v);
862 threadsetname("newwindowthread");
863
864 for(;;){
865 /* only fsysproc is talking to us, so synchronization is trivial */
866 recvp(cnewwindow);
867 w = makenewwindow(nil);
868 winsettag(w);
869 xfidlog(w, "new");
870 sendp(cnewwindow, w);
871 }
872}
873
874Reffont*
875rfget(int fix, int save, int setfont, char *name)
876{
877 Reffont *r;
878 Font *f;
879 int i;
880
881 r = nil;
882 if(name == nil){
883 name = fontnames[fix];
884 r = reffonts[fix];
885 }
886 if(r == nil){
887 for(i=0; i<nfontcache; i++)
888 if(strcmp(name, fontcache[i]->f->name) == 0){
889 r = fontcache[i];
890 goto Found;
891 }
892 f = openfont(display, name);
893 if(f == nil){
894 warning(nil, "can't open font file %s: %r\n", name);
895 return nil;
896 }
897 r = emalloc(sizeof(Reffont));
898 r->f = f;
899 fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
900 fontcache[nfontcache++] = r;
901 }
902 Found:
903 if(save){
904 incref(&r->ref);
905 if(reffonts[fix])
906 rfclose(reffonts[fix]);
907 reffonts[fix] = r;
908 if(name != fontnames[fix]){
909 free(fontnames[fix]);
910 fontnames[fix] = estrdup(name);
911 }
912 }
913 if(setfont){
914 reffont.f = r->f;
915 incref(&r->ref);
916 rfclose(reffonts[0]);
917 font = r->f;
918 reffonts[0] = r;
919 incref(&r->ref);
920 iconinit();
921 }
922 incref(&r->ref);
923 return r;
924}
925
926void
927rfclose(Reffont *r)
928{
929 int i;
930
931 if(decref(&r->ref) == 0){
932 for(i=0; i<nfontcache; i++)
933 if(r == fontcache[i])
934 break;
935 if(i >= nfontcache)
936 warning(nil, "internal error: can't find font in cache\n");
937 else{
938 nfontcache--;
939 memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
940 }
941 freefont(r->f);
942 free(r);
943 }
944}
945
946Cursor boxcursor = {
947 {-7, -7},
948 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
949 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
950 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
951 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
952 {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
953 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
954 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
955 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
956};
957
958int
959ispastel(char *color)
960{
961 int i;
962 for(i = 0; i < npastels; i++)
963 if(strcmp(pastels[i], color) == 0)
964 return 1;
965 return 0;
966}
967
968Image*
969tilebit(uint32_t tilebg, char *bgkey, uint32_t tilefg, char *fgkey)
970{
971 Image *t, *fg;
972
973 if(ispastel(fgkey))
974 fg = allocimagemix(display, tilefg, C_MIX);
975 else
976 fg = allocimage(display, Rect(0,0,1,1), RGBA32, 1, tilefg);
977
978 if(ispastel(bgkey))
979 t = allocimagemix(display, tilebg, C_MIX);
980 else
981 t = allocimage(display, Rect(0,0,4,2), screen->chan, 1, tilebg);
982 if(t == nil){
983 freeimage(fg);
984 return nil;
985 }
986
987 draw(t, Rect(0,0,1,1), fg, nil, ZP);
988 draw(t, Rect(2,1,3,2), fg, nil, ZP);
989 freeimage(fg);
990 return t;
991}
992
993void
994iconinit(void)
995{
996 Rectangle r, rr;
997 Image *tmp;
998
999 if(tagcols[BACK] == nil) {
1000 if(ispastel("tagbg"))
1001 tagcols[BACK] = allocimagemix(display, C_TAGBG, C_MIX);
1002 else
1003 tagcols[BACK] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TAGBG);
1004
1005 if(ispastel("htagbg"))
1006 tagcols[HIGH] = allocimagemix(display, C_TAGHLBG, C_MIX);
1007 else
1008 tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TAGHLBG);
1009
1010 if(ispastel("colbtn"))
1011 tagcols[BORD] = allocimagemix(display, C_COLBUTTON, C_MIX);
1012 else
1013 tagcols[BORD] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_COLBUTTON);
1014
1015 if(ispastel("tagfg"))
1016 tagcols[TEXT] = allocimagemix(display, C_TAGFG, C_MIX);
1017 else
1018 tagcols[TEXT] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TAGFG);
1019
1020 if(ispastel("htagfg"))
1021 tagcols[HTEXT] = allocimagemix(display, C_TAGHLFG, C_MIX);
1022 else
1023 tagcols[HTEXT] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TAGHLFG);
1024
1025 if(ispastel("txtbg"))
1026 textcols[BACK] = allocimagemix(display, C_TXTBG, C_MIX);
1027 else
1028 textcols[BACK] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TXTBG);
1029
1030 if(ispastel("htxtbg"))
1031 textcols[HIGH] = allocimagemix(display, C_TXTHLBG, C_MIX);
1032 else
1033 textcols[HIGH] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TXTHLBG);
1034
1035 if(strcmp(scrollstyle, "xxiivv") == 0){
1036 if(ispastel("scrolltilefg"))
1037 tmp = allocimagemix(display, C_SCROLLTILEFG, C_MIX);
1038 else
1039 tmp = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_SCROLLTILEFG);
1040
1041 if(ispastel("scrolltilebg"))
1042 textcols[BORD] = allocimagemix(display, C_SCROLLTILEBG, C_MIX);
1043 else
1044 textcols[BORD] = allocimage(display, Rect(0,0,2,2), RGBA32, 1, C_SCROLLTILEBG);
1045
1046 draw(textcols[BORD], Rect(1,1,2,2), tmp, nil, ZP);
1047 draw(textcols[BORD], Rect(0,0,1,1), tmp, nil, ZP);
1048 freeimage(tmp);
1049 }else if(strcmp(scrollstyle, "def") == 0){
1050 if(ispastel("scrlbg"))
1051 textcols[BORD] = allocimagemix(display, C_SCROLLBG, C_MIX);
1052 else
1053 textcols[BORD] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_SCROLLBG);
1054 }
1055
1056 if(ispastel("txtfg"))
1057 textcols[TEXT] = allocimagemix(display, C_TXTFG, C_MIX);
1058 else
1059 textcols[TEXT] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TXTFG);
1060
1061 if(ispastel("htxtfg"))
1062 textcols[HTEXT] = allocimagemix(display, C_TXTHLFG, C_MIX);
1063 else
1064 textcols[HTEXT] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TXTHLFG);
1065 }
1066
1067 r = Rect(0, 0, Scrollwid+ButtonBorder, font->height+1);
1068 if(button && eqrect(r, button->r))
1069 return;
1070
1071 if(button){
1072 freeimage(button);
1073 freeimage(modbutton);
1074 freeimage(colbutton);
1075 }
1076
1077
1078 if(strcmp(winbtnstyle, "square") == 0){
1079 r = Rect(0, 0, Scrollwid, font->height+1);
1080 button = allocimage(display, r, screen->chan, 0, DNofill);
1081 draw(button, r, tagcols[BORD], nil, r.min);
1082 r.max.x -= 4;
1083 draw(button, Rect(r.min.x + 2, r.min.y + 4, r.min.x + 8, r.min.y + 10), tagcols[BACK], nil, ZP);
1084 r = button->r;
1085 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1086 draw(modbutton, r, tagcols[BORD], nil, r.min);
1087 r = insetrect(r, 2);
1088 if(ispastel("tmpbtn"))
1089 tmp = allocimagemix(display, C_TMPBUTTON, C_MIX);
1090 else
1091 tmp = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TMPBUTTON);
1092 draw(modbutton, Rect(r.min.x, r.min.y + 2, r.min.x + 6, r.min.y + 8), tmp, nil, ZP);
1093 }else if(strcmp(winbtnstyle, "ellipse") == 0){
1094 r = Rect(0, 0, Scrollwid, font->height+1);
1095 button = allocimage(display, r, screen->chan, 0, DNofill);
1096 draw(button, r, tagcols[BORD], nil, r.min);
1097 r.max.x -= 4;
1098 fillellipse(button, (Point){r.min.x + 5, r.min.y + 7}, 3, 3, tagcols[BACK], ZP);
1099 r = button->r;
1100 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1101 draw(modbutton, r, tagcols[BORD], nil, r.min);
1102 r = insetrect(r, 2);
1103 if(ispastel("tmpbtn"))
1104 tmp = allocimagemix(display, C_TMPBUTTON, C_MIX);
1105 else
1106 tmp = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TMPBUTTON);
1107 fillellipse(modbutton, (Point){r.min.x + 3, r.min.y + 5}, 3, 3, tmp, ZP);
1108 }else if(strcmp(winbtnstyle, "p9-2e") == 0){
1109 rr = r;
1110 rr.max.y--;
1111 rr.max.x--;
1112
1113 button = allocimage(display, r, screen->chan, 0, DNofill);
1114 draw(button, r, tagcols[BACK], nil, ZP);
1115 draw(button, insetrect(rr, 1), tagcols[BORD], nil, ZP);
1116 draw(button, insetrect(rr, 3), tagcols[BACK], nil, ZP);
1117
1118 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1119 draw(modbutton, r, tagcols[BACK], nil, ZP);
1120 draw(modbutton, insetrect(rr, 1), tagcols[BORD], nil, ZP);
1121 draw(modbutton, insetrect(rr, 3), tagcols[BACK], nil, ZP);
1122 if(ispastel("tmpbtn"))
1123 tmp = allocimagemix(display, C_TMPBUTTON, C_MIX);
1124 else
1125 tmp = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TMPBUTTON);
1126 draw(modbutton, insetrect(rr, 4), tmp, nil, ZP);
1127 freeimage(tmp);
1128 }else{
1129 button = allocimage(display, r, screen->chan, 0, DNofill);
1130 draw(button, r, tagcols[BACK], nil, r.min);
1131 r.max.x -= ButtonBorder;
1132 border(button, r, ButtonBorder, tagcols[BORD], ZP);
1133
1134 r = button->r;
1135 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1136 draw(modbutton, r, tagcols[BACK], nil, r.min);
1137 r.max.x -= ButtonBorder;
1138 border(modbutton, r, ButtonBorder, tagcols[BORD], ZP);
1139 r = insetrect(r, ButtonBorder);
1140
1141 if(ispastel("tmpbtn"))
1142 tmp = allocimagemix(display, C_TMPBUTTON, C_MIX);
1143 else
1144 tmp = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_TMPBUTTON);
1145
1146 draw(modbutton, r, tmp, nil, ZP);
1147 freeimage(tmp);
1148 }
1149
1150 r = button->r;
1151
1152 if(strcmp(colbtnstyle, "p9-2e") == 0){
1153 r = Rect(0, 0, Scrollwid, button->r.max.y);
1154 colbutton = allocimage(display, r, screen->chan, 0, DNofill);
1155 draw(colbutton, r, tagcols[BACK], nil, ZP);
1156 rr = r;
1157 rr.max.y--;
1158 rr.max.x++;
1159 draw(colbutton, insetrect(rr, 1), tagcols[BORD], nil, ZP);
1160 tmp = tilebit(C_COLBTNTILEBG, "colbtntilebg", C_COLBTNTILEFG, "colbtntilefg");
1161 draw(colbutton, insetrect(rr, 2), tmp, nil, ZP);
1162 freeimage(tmp);
1163 }else if(ispastel("winbtn"))
1164 colbutton = allocimagemix(display, C_WINBUTTON, C_MIX);
1165 else
1166 colbutton = allocimage(display, r, RGBA32, 1, C_WINBUTTON);
1167
1168 if(ispastel("btn2hl"))
1169 but2col = allocimagemix(display, C_BUTTON2HL, C_MIX);
1170 else
1171 but2col = allocimage(display, r, screen->chan, 1, C_BUTTON2HL);
1172
1173 if(ispastel("btn3hl"))
1174 but3col = allocimagemix(display, C_BUTTON3HL, C_MIX);
1175 else
1176 but3col = allocimage(display, r, screen->chan, 1, C_BUTTON3HL);
1177}
1178/*
1179 * /dev/snarf updates when the file is closed, so we must open our own
1180 * fd here rather than use snarffd
1181 */
1182
1183/* rio truncates larges snarf buffers, so this avoids using the
1184 * service if the string is huge */
1185
1186#define MAXSNARF 100*1024
1187
1188void
1189acmeputsnarf(void)
1190{
1191 int i, n;
1192 Fmt f;
1193 char *s;
1194
1195 if(snarfbuf.nc==0)
1196 return;
1197 if(snarfbuf.nc > MAXSNARF)
1198 return;
1199
1200 fmtstrinit(&f);
1201 for(i=0; i<snarfbuf.nc; i+=n){
1202 n = snarfbuf.nc-i;
1203 if(n >= NSnarf)
1204 n = NSnarf;
1205 bufread(&snarfbuf, i, snarfrune, n);
1206 if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
1207 break;
1208 }
1209 s = fmtstrflush(&f);
1210 if(s && s[0])
1211 putsnarf(s);
1212 free(s);
1213}
1214
1215void
1216acmegetsnarf(void)
1217{
1218 char *s;
1219 int nb, nr, nulls, len;
1220 Rune *r;
1221
1222 s = getsnarf();
1223 if(s == nil || s[0]==0){
1224 free(s);
1225 return;
1226 }
1227
1228 len = strlen(s);
1229 r = runemalloc(len+1);
1230 cvttorunes(s, len, r, &nb, &nr, &nulls);
1231 bufreset(&snarfbuf);
1232 bufinsert(&snarfbuf, 0, r, nr);
1233 free(r);
1234 free(s);
1235}
1236
1237int
1238ismtpt(char *file)
1239{
1240 int n;
1241
1242 if(mtpt == nil)
1243 return 0;
1244
1245 /* This is not foolproof, but it will stop a lot of them. */
1246 n = strlen(mtpt);
1247 return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0);
1248}
1249
1250int
1251timefmt(Fmt *f)
1252{
1253 Tm *tm;
1254
1255 tm = localtime(va_arg(f->args, ulong));
1256 return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d",
1257 tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);
1258}