14 /* for generating syms in mkfile only: */
18 void mousethread(void*);
19 void keyboardthread(void*);
20 void waitthread(void*);
21 void xfidallocthread(void*);
22 void newwindowthread(void*);
23 void plumbproc(void*);
32 int swapscrollbuttons = FALSE;
36 NSnarf = 1000 /* less than 1024, I/O buffer size */
38 Rune snarfrune[NSnarf+1];
42 "/lib/font/bit/lucsans/euro.8.font",
43 "/lib/font/bit/lucm/unicode.9.font"
48 void shutdownthread(void*);
49 void acmeerrorinit(void);
50 void readfile(Column*, char*);
51 static int shutdown(void*, char*);
54 derror(Display *d, char *errorstr)
61 threadmain(int argc, char *argv[])
69 rfork(RFENVG|RFNAMEG);
76 {extern int _threaddebuglevel;
77 _threaddebuglevel = ~0;
81 globalautoindent = TRUE;
95 fontnames[0] = ARGF();
96 if(fontnames[0] == nil)
100 fontnames[1] = ARGF();
101 if(fontnames[1] == nil)
115 swapscrollbuttons = TRUE;
124 fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwidthfontname -l loadfile -W winsize\n");
125 threadexitsall("usage");
128 fontnames[0] = estrdup(fontnames[0]);
129 fontnames[1] = estrdup(fontnames[1]);
132 fmtinstall('t', timefmt);
134 cputype = getenv("cputype");
135 objtype = getenv("objtype");
136 home = getenv("HOME");
137 acmeshell = getenv("acmeshell");
138 if(acmeshell && *acmeshell == '\0')
140 p = getenv("tabstop");
142 maxtab = strtoul(p, nil, 0);
148 rowloadfonts(loadfile);
149 putenv("font", fontnames[0]);
150 snarffd = open("/dev/snarf", OREAD|OCEXEC);
153 sprint(buf, "/acme/bin/%s", cputype);
154 bind(buf, "/bin", MBEFORE);
156 bind("/acme/bin", "/bin", MBEFORE);
158 getwd(wdir, sizeof wdir);
161 if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
162 fprint(2, "acme: can't open display: %r\n");
163 threadexitsall("geninitdraw");
166 if(initdraw(derror, fontnames[0], "acme") < 0){
167 fprint(2, "acme: can't open display: %r\n");
168 threadexitsall("initdraw");
172 font = d->defaultfont;
176 reffonts[0] = &reffont;
177 incref(&reffont.ref); /* one to hold up 'font' variable */
178 incref(&reffont.ref); /* one to hold up reffonts[0] */
179 fontcache = emalloc(sizeof(Reffont*));
181 fontcache[0] = &reffont;
187 cwait = threadwaitchan();
188 ccommand = chancreate(sizeof(Command**), 0);
189 ckill = chancreate(sizeof(Rune*), 0);
190 cxfidalloc = chancreate(sizeof(Xfid*), 0);
191 cxfidfree = chancreate(sizeof(Xfid*), 0);
192 cnewwindow = chancreate(sizeof(Channel*), 0);
193 cerr = chancreate(sizeof(char*), 0);
194 cedit = chancreate(sizeof(int), 0);
195 cexit = chancreate(sizeof(int), 0);
196 cwarn = chancreate(sizeof(void*), 1);
197 if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
198 fprint(2, "acme: can't create initial channels: %r\n");
199 threadexitsall("channels");
201 chansetname(ccommand, "ccommand");
202 chansetname(ckill, "ckill");
203 chansetname(cxfidalloc, "cxfidalloc");
204 chansetname(cxfidfree, "cxfidfree");
205 chansetname(cnewwindow, "cnewwindow");
206 chansetname(cerr, "cerr");
207 chansetname(cedit, "cedit");
208 chansetname(cexit, "cexit");
209 chansetname(cwarn, "cwarn");
211 mousectl = initmouse(nil, screen);
213 fprint(2, "acme: can't initialize mouse: %r\n");
214 threadexitsall("mouse");
216 mouse = &mousectl->m;
217 keyboardctl = initkeyboard(nil);
218 if(keyboardctl == nil){
219 fprint(2, "acme: can't initialize keyboard: %r\n");
220 threadexitsall("keyboard");
225 plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
227 fprint(2, "acme: can't initialize plumber: %r\n");
229 cplumb = chancreate(sizeof(Plumbmsg*), 0);
230 threadcreate(plumbproc, nil, STACK);
232 plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
239 if(!loadfile || !rowload(&row, loadfile, TRUE)){
240 rowinit(&row, screen->clipr);
245 ncol = (argc+(WPERCOL-1))/WPERCOL;
252 for(i=0; i<ncol; i++){
253 c = rowadd(&row, nil, -1);
255 error("initializing columns");
257 c = row.col[row.ncol-1];
261 for(i=0; i<argc; i++){
262 p = utfrrune(argv[i], '/');
263 if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
264 readfile(c, argv[i]);
266 readfile(row.col[i/WPERCOL], argv[i]);
269 flushimage(display, 1);
272 threadcreate(keyboardthread, nil, STACK);
273 threadcreate(mousethread, nil, STACK);
274 threadcreate(waitthread, nil, STACK);
275 threadcreate(xfidallocthread, nil, STACK);
276 threadcreate(newwindowthread, nil, STACK);
277 /* threadcreate(shutdownthread, nil, STACK); */
278 threadnotify(shutdown, 1);
285 readfile(Column *c, char *s)
292 w = coladd(c, nil, nil, -1);
294 runesnprint(rb, sizeof rb, "%s/%s", wdir, s);
296 runesnprint(rb, sizeof rb, "%s", s);
298 rs = cleanrname(runestr(rb, nr));
299 winsetname(w, rs.r, rs.nr);
300 textload(&w->body, 0, s, 1);
301 w->body.file->mod = FALSE;
304 winresize(w, w->r, FALSE, TRUE);
305 textscrdraw(&w->body);
306 textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
311 "sys: write on closed pipe",
329 shutdown(void *v, char *msg)
335 for(i=0; ignotes[i]; i++)
336 if(strncmp(ignotes[i], msg, strlen(ignotes[i])) == 0)
340 if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
344 for(i=0; oknotes[i]; i++)
345 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
347 print("acme: %s\n", msg);
353 shutdownthread(void *v)
360 threadsetname("shutdown");
361 c = threadnotechan();
362 while((msg = recvp(c)) != nil)
374 /* flushimage(display, 1); */
376 for(c=command; c; c=c->next)
377 postnote(PNGROUP, c->pid, "hangup");
384 acmeerrorproc(void *v)
390 threadsetname("acmeerrorproc");
391 buf = emalloc(8192+1);
392 while((n=read(errorfd, buf, 8192)) >= 0){
394 sendp(cerr, estrdup(buf));
404 error("can't create pipe");
406 sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
407 fd = create(acmeerrorfile, OWRITE, 0666);
409 remove(acmeerrorfile);
410 fd = create(acmeerrorfile, OWRITE, 0666);
412 error("can't create acmeerror file");
414 sprint(buf, "%d", pfd[0]);
415 write(fd, buf, strlen(buf));
417 /* reopen pfd[1] close on exec */
418 sprint(buf, "/fd/%d", pfd[1]);
419 errorfd = open(buf, OREAD|OCEXEC);
421 fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
422 fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
426 error("can't re-open acmeerror file");
427 proccreate(acmeerrorproc, nil, STACK);
437 threadsetname("plumbproc");
439 m = threadplumbrecv(plumbeditfd);
448 keyboardthread(void *v)
453 enum { KTimer, KKey, NKALT };
454 static Alt alts[NKALT+1];
457 alts[KTimer].c = nil;
458 alts[KTimer].v = nil;
459 alts[KTimer].op = CHANNOP;
460 alts[KKey].c = keyboardctl->c;
462 alts[KKey].op = CHANRCV;
463 alts[NKALT].op = CHANEND;
467 threadsetname("keyboardthread");
473 if(t!=nil && t->what==Tag){
477 flushimage(display, 1);
479 alts[KTimer].c = nil;
480 alts[KTimer].op = CHANNOP;
484 typetext = rowtype(&row, r, mouse->xy);
486 if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
488 if(t!=nil && t->w!=nil)
489 t->w->body.file->curtext = &t->w->body;
492 if(t!=nil && t->what==Tag) {
493 timer = timerstart(500);
494 alts[KTimer].c = timer->c;
495 alts[KTimer].op = CHANRCV;
498 alts[KTimer].c = nil;
499 alts[KTimer].op = CHANNOP;
501 if(nbrecv(keyboardctl->c, &r) > 0)
503 flushimage(display, 1);
519 enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
520 static Alt alts[NMALT+1];
523 threadsetname("mousethread");
524 alts[MResize].c = mousectl->resizec;
525 alts[MResize].v = nil;
526 alts[MResize].op = CHANRCV;
527 alts[MMouse].c = mousectl->c;
528 alts[MMouse].v = &mousectl->m;
529 alts[MMouse].op = CHANRCV;
530 alts[MPlumb].c = cplumb;
531 alts[MPlumb].v = ±
532 alts[MPlumb].op = CHANRCV;
533 alts[MWarnings].c = cwarn;
534 alts[MWarnings].v = nil;
535 alts[MWarnings].op = CHANRCV;
537 alts[MPlumb].op = CHANNOP;
538 alts[NMALT].op = CHANEND;
544 flushimage(display, 1);
547 if(getwindow(display, Refnone) < 0)
548 error("attach to window");
549 draw(screen, screen->r, display->white, nil, ZP);
552 rowresize(&row, screen->clipr);
555 if(strcmp(pm->type, "text") == 0){
556 act = plumblookup(pm->attr, "action");
557 if(act==nil || strcmp(act, "showfile")==0)
559 else if(strcmp(act, "showdata")==0)
568 * Make a copy so decisions are consistent; mousectl changes
569 * underfoot. Can't just receive into m because this introduces
570 * another race; see /sys/src/libdraw/mouse.c.
574 t = rowwhich(&row, m.xy);
576 if((t!=mousetext && t!=nil && t->w!=nil) &&
577 (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
578 xfidlog(t->w, "focus");
581 if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
582 winlock(mousetext->w, 'M');
584 wincommit(mousetext->w, mousetext);
585 winunlock(mousetext->w);
591 if(t==nil || m.buttons==0)
596 else if(m.buttons == 2)
598 else if(m.buttons == 4)
601 if(t->what==Body && ptinrect(m.xy, t->scrollr)){
603 if(swapscrollbuttons){
616 /* scroll buttons, wheels, etc. */
617 if(w != nil && (m.buttons & (8|16))){
621 but = Kscrollonedown;
628 if(ptinrect(m.xy, t->scrollr)){
630 if(t->what == Columntag)
631 rowdragcol(&row, t->col, but);
632 else if(t->what == Tag){
633 coldragwin(t->col, t->w, but);
635 barttext = &t->w->body;
657 activecol = t->col; /* button 1 only */
658 if(t->w!=nil && t==&t->w->body)
660 }else if(m.buttons & 2){
661 if(textselect2(t, &q0, &q1, &argt))
662 execute(t, q0, q1, FALSE, argt);
663 }else if(m.buttons & 4){
664 if(textselect3(t, &q0, &q1))
665 look3(t, q0, q1, FALSE);
679 * There is a race between process exiting and our finding out it was ever created.
680 * This structure keeps a list of processes that have exited we haven't heard of.
682 typedef struct Pid Pid;
700 Pid *pids, *p, *lastp;
701 enum { WErr, WKill, WWait, WCmd, NWALT };
705 threadsetname("waitthread");
709 alts[WErr].op = CHANRCV;
710 alts[WKill].c = ckill;
711 alts[WKill].v = &cmd;
712 alts[WKill].op = CHANRCV;
713 alts[WWait].c = cwait;
715 alts[WWait].op = CHANRCV;
716 alts[WCmd].c = ccommand;
718 alts[WCmd].op = CHANRCV;
719 alts[NWALT].op = CHANEND;
726 warning(nil, "%s", err);
728 flushimage(display, 1);
733 ncmd = runestrlen(cmd);
734 for(c=command; c; c=c->next){
736 if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
737 if(postnote(PNGROUP, c->pid, "kill") < 0)
738 warning(nil, "kill %S: %r\n", cmd);
743 warning(nil, "Kill: no process %S\n", cmd);
749 for(c=command; c; c=c->next){
763 /* helper processes use this exit status */
764 if(strncmp(w->msg, "libthread", 9) != 0){
765 p = emalloc(sizeof(Pid));
767 strncpy(p->msg, w->msg, sizeof(p->msg));
772 if(search(t, c->name, c->nname)){
773 textdelete(t, t->q0, t->q1, TRUE);
774 textsetselect(t, 0, 0);
777 warning(c->md, "%.*S: exit %s\n", c->nname-1, c->name, w->msg);
778 flushimage(display, 1);
793 /* has this command already exited? */
795 for(p=pids; p!=nil; p=p->next){
796 if(p->pid == c->pid){
798 warning(c->md, "%s\n", p->msg);
802 lastp->next = p->next;
813 textinsert(t, 0, c->name, c->nname, TRUE);
814 textsetselect(t, 0, 0);
815 flushimage(display, 1);
823 xfidallocthread(void *v)
826 enum { Alloc, Free, N };
827 static Alt alts[N+1];
830 threadsetname("xfidallocthread");
831 alts[Alloc].c = cxfidalloc;
833 alts[Alloc].op = CHANRCV;
834 alts[Free].c = cxfidfree;
836 alts[Free].op = CHANRCV;
837 alts[N].op = CHANEND;
847 x = emalloc(sizeof(Xfid));
848 x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
849 chansetname(x->c, "xc%p", x->c);
851 threadcreate(xfidctl, x->arg, STACK);
853 sendp(cxfidalloc, x);
863 /* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
865 newwindowthread(void *v)
870 threadsetname("newwindowthread");
873 /* only fsysproc is talking to us, so synchronization is trivial */
875 w = makenewwindow(nil);
878 sendp(cnewwindow, w);
883 rfget(int fix, int save, int setfont, char *name)
891 name = fontnames[fix];
895 for(i=0; i<nfontcache; i++)
896 if(strcmp(name, fontcache[i]->f->name) == 0){
900 f = openfont(display, name);
902 warning(nil, "can't open font file %s: %r\n", name);
905 r = emalloc(sizeof(Reffont));
907 fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
908 fontcache[nfontcache++] = r;
914 rfclose(reffonts[fix]);
916 if(name != fontnames[fix]){
917 free(fontnames[fix]);
918 fontnames[fix] = estrdup(name);
924 rfclose(reffonts[0]);
939 if(decref(&r->ref) == 0){
940 for(i=0; i<nfontcache; i++)
941 if(r == fontcache[i])
944 warning(nil, "internal error: can't find font in cache\n");
947 memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
956 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
957 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
958 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
959 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
960 {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
961 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
962 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
963 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
972 if(tagcols[BACK] == nil) {
974 tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
975 tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
976 tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
977 tagcols[TEXT] = display->black;
978 tagcols[HTEXT] = display->black;
981 textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
982 textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
983 textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
984 textcols[TEXT] = display->black;
985 textcols[HTEXT] = display->black;
988 r = Rect(0, 0, Scrollwid+ButtonBorder, font->height+1);
989 if(button && eqrect(r, button->r))
994 freeimage(modbutton);
995 freeimage(colbutton);
998 button = allocimage(display, r, screen->chan, 0, DNofill);
999 draw(button, r, tagcols[BACK], nil, r.min);
1000 r.max.x -= ButtonBorder;
1001 border(button, r, ButtonBorder, tagcols[BORD], ZP);
1004 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1005 draw(modbutton, r, tagcols[BACK], nil, r.min);
1006 r.max.x -= ButtonBorder;
1007 border(modbutton, r, ButtonBorder, tagcols[BORD], ZP);
1008 r = insetrect(r, ButtonBorder);
1009 tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
1010 draw(modbutton, r, tmp, nil, ZP);
1014 colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
1016 but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
1017 but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
1021 * /dev/snarf updates when the file is closed, so we must open our own
1022 * fd here rather than use snarffd
1025 /* rio truncates larges snarf buffers, so this avoids using the
1026 * service if the string is huge */
1028 #define MAXSNARF 100*1024
1039 if(snarfbuf.nc > MAXSNARF)
1043 for(i=0; i<snarfbuf.nc; i+=n){
1047 bufread(&snarfbuf, i, snarfrune, n);
1048 if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
1051 s = fmtstrflush(&f);
1061 int nb, nr, nulls, len;
1065 if(s == nil || s[0]==0){
1071 r = runemalloc(len+1);
1072 cvttorunes(s, len, r, &nb, &nr, &nulls);
1073 bufreset(&snarfbuf);
1074 bufinsert(&snarfbuf, 0, r, nr);
1087 /* This is not foolproof, but it will stop a lot of them. */
1089 return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0);
1097 tm = localtime(va_arg(f->args, ulong));
1098 return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d",
1099 tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);