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){
407 error("can't create pipe");
409 sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
410 fd = create(acmeerrorfile, OWRITE, 0666);
412 remove(acmeerrorfile);
413 fd = create(acmeerrorfile, OWRITE, 0666);
415 error("can't create acmeerror file");
417 sprint(buf, "%d", pfd[0]);
418 write(fd, buf, strlen(buf));
420 /* reopen pfd[1] close on exec */
421 sprint(buf, "/fd/%d", pfd[1]);
422 errorfd = open(buf, OREAD|OCEXEC);
424 fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
425 fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
429 error("can't re-open acmeerror file");
430 proccreate(acmeerrorproc, nil, STACK);
440 threadsetname("plumbproc");
442 m = threadplumbrecv(plumbeditfd);
451 keyboardthread(void *v)
456 enum { KTimer, KKey, NKALT };
457 static Alt alts[NKALT+1];
460 alts[KTimer].c = nil;
461 alts[KTimer].v = nil;
462 alts[KTimer].op = CHANNOP;
463 alts[KKey].c = keyboardctl->c;
465 alts[KKey].op = CHANRCV;
466 alts[NKALT].op = CHANEND;
470 threadsetname("keyboardthread");
476 if(t!=nil && t->what==Tag){
480 flushimage(display, 1);
482 alts[KTimer].c = nil;
483 alts[KTimer].op = CHANNOP;
487 typetext = rowtype(&row, r, mouse->xy);
489 if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright)) /* scrolling doesn't change activecol */
491 if(t!=nil && t->w!=nil)
492 t->w->body.file->curtext = &t->w->body;
495 if(t!=nil && t->what==Tag) {
496 timer = timerstart(500);
497 alts[KTimer].c = timer->c;
498 alts[KTimer].op = CHANRCV;
501 alts[KTimer].c = nil;
502 alts[KTimer].op = CHANNOP;
504 if(nbrecv(keyboardctl->c, &r) > 0)
506 flushimage(display, 1);
522 enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
523 static Alt alts[NMALT+1];
526 threadsetname("mousethread");
527 alts[MResize].c = mousectl->resizec;
528 alts[MResize].v = nil;
529 alts[MResize].op = CHANRCV;
530 alts[MMouse].c = mousectl->c;
531 alts[MMouse].v = &mousectl->m;
532 alts[MMouse].op = CHANRCV;
533 alts[MPlumb].c = cplumb;
534 alts[MPlumb].v = ±
535 alts[MPlumb].op = CHANRCV;
536 alts[MWarnings].c = cwarn;
537 alts[MWarnings].v = nil;
538 alts[MWarnings].op = CHANRCV;
540 alts[MPlumb].op = CHANNOP;
541 alts[NMALT].op = CHANEND;
547 flushimage(display, 1);
550 if(getwindow(display, Refnone) < 0)
551 error("attach to window");
552 draw(screen, screen->r, display->white, nil, ZP);
555 rowresize(&row, screen->clipr);
558 if(strcmp(pm->type, "text") == 0){
559 act = plumblookup(pm->attr, "action");
560 if(act==nil || strcmp(act, "showfile")==0)
562 else if(strcmp(act, "showdata")==0)
571 * Make a copy so decisions are consistent; mousectl changes
572 * underfoot. Can't just receive into m because this introduces
573 * another race; see /sys/src/libdraw/mouse.c.
577 t = rowwhich(&row, m.xy);
579 if((t!=mousetext && t!=nil && t->w!=nil) &&
580 (mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
581 xfidlog(t->w, "focus");
584 if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
585 winlock(mousetext->w, 'M');
587 wincommit(mousetext->w, mousetext);
588 winunlock(mousetext->w);
594 if(t==nil || m.buttons==0)
599 else if(m.buttons == 2)
601 else if(m.buttons == 4)
604 if(t->what==Body && ptinrect(m.xy, t->scrollr)){
606 if(swapscrollbuttons){
619 /* scroll buttons, wheels, etc. */
620 if(w != nil && (m.buttons & (8|16))){
624 but = Kscrollonedown;
631 if(ptinrect(m.xy, t->scrollr)){
633 if(t->what == Columntag)
634 rowdragcol(&row, t->col, but);
635 else if(t->what == Tag){
636 coldragwin(t->col, t->w, but);
638 barttext = &t->w->body;
660 activecol = t->col; /* button 1 only */
661 if(t->w!=nil && t==&t->w->body)
663 }else if(m.buttons & 2){
664 if(textselect2(t, &q0, &q1, &argt))
665 execute(t, q0, q1, FALSE, argt);
666 }else if(m.buttons & 4){
667 if(textselect3(t, &q0, &q1))
668 look3(t, q0, q1, FALSE);
682 * There is a race between process exiting and our finding out it was ever created.
683 * This structure keeps a list of processes that have exited we haven't heard of.
685 typedef struct Pid Pid;
703 Pid *pids, *p, *lastp;
704 enum { WErr, WKill, WWait, WCmd, NWALT };
708 threadsetname("waitthread");
712 alts[WErr].op = CHANRCV;
713 alts[WKill].c = ckill;
714 alts[WKill].v = &cmd;
715 alts[WKill].op = CHANRCV;
716 alts[WWait].c = cwait;
718 alts[WWait].op = CHANRCV;
719 alts[WCmd].c = ccommand;
721 alts[WCmd].op = CHANRCV;
722 alts[NWALT].op = CHANEND;
729 warning(nil, "%s", err);
731 flushimage(display, 1);
736 ncmd = runestrlen(cmd);
737 for(c=command; c; c=c->next){
739 if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
740 if(postnote(PNGROUP, c->pid, "kill") < 0)
741 warning(nil, "kill %S: %r\n", cmd);
746 warning(nil, "Kill: no process %S\n", cmd);
752 for(c=command; c; c=c->next){
766 /* helper processes use this exit status */
767 if(strncmp(w->msg, "libthread", 9) != 0){
768 p = emalloc(sizeof(Pid));
770 strncpy(p->msg, w->msg, sizeof(p->msg));
775 if(search(t, c->name, c->nname)){
776 textdelete(t, t->q0, t->q1, TRUE);
777 textsetselect(t, 0, 0);
780 warning(c->md, "%.*S: exit %s\n", c->nname-1, c->name, w->msg);
781 flushimage(display, 1);
796 /* has this command already exited? */
798 for(p=pids; p!=nil; p=p->next){
799 if(p->pid == c->pid){
801 warning(c->md, "%s\n", p->msg);
805 lastp->next = p->next;
816 textinsert(t, 0, c->name, c->nname, TRUE);
817 textsetselect(t, 0, 0);
818 flushimage(display, 1);
826 xfidallocthread(void *v)
829 enum { Alloc, Free, N };
830 static Alt alts[N+1];
833 threadsetname("xfidallocthread");
834 alts[Alloc].c = cxfidalloc;
836 alts[Alloc].op = CHANRCV;
837 alts[Free].c = cxfidfree;
839 alts[Free].op = CHANRCV;
840 alts[N].op = CHANEND;
850 x = emalloc(sizeof(Xfid));
851 x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
852 chansetname(x->c, "xc%p", x->c);
854 threadcreate(xfidctl, x->arg, STACK);
856 sendp(cxfidalloc, x);
866 /* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
868 newwindowthread(void *v)
873 threadsetname("newwindowthread");
876 /* only fsysproc is talking to us, so synchronization is trivial */
878 w = makenewwindow(nil);
881 sendp(cnewwindow, w);
886 rfget(int fix, int save, int setfont, char *name)
894 name = fontnames[fix];
898 for(i=0; i<nfontcache; i++)
899 if(strcmp(name, fontcache[i]->f->name) == 0){
903 f = openfont(display, name);
905 warning(nil, "can't open font file %s: %r\n", name);
908 r = emalloc(sizeof(Reffont));
910 fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
911 fontcache[nfontcache++] = r;
917 rfclose(reffonts[fix]);
919 if(name != fontnames[fix]){
920 free(fontnames[fix]);
921 fontnames[fix] = estrdup(name);
927 rfclose(reffonts[0]);
942 if(decref(&r->ref) == 0){
943 for(i=0; i<nfontcache; i++)
944 if(r == fontcache[i])
947 warning(nil, "internal error: can't find font in cache\n");
950 memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
959 {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
960 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
961 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
962 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
963 {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
964 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
965 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
966 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
969 Cursor2 boxcursor2 = {
971 {0xFF, 0xFF, 0xFF, 0xFF,
972 0xFF, 0xFF, 0xFF, 0xFF,
973 0xFF, 0xFF, 0xFF, 0xFF,
974 0xFF, 0xFF, 0xFF, 0xFF,
975 0xFF, 0xFF, 0xFF, 0xFF,
976 0xFF, 0xFF, 0xFF, 0xFF,
977 0xFF, 0xFF, 0xFF, 0xFF,
978 0xFF, 0xFF, 0xFF, 0xFF,
979 0xFF, 0xFF, 0xFF, 0xFF,
980 0xFF, 0xFF, 0xFF, 0xFF,
981 0xFF, 0xC0, 0x03, 0xFF,
982 0xFF, 0xC0, 0x03, 0xFF,
983 0xFF, 0xC0, 0x03, 0xFF,
984 0xFF, 0xC0, 0x03, 0xFF,
985 0xFF, 0xC0, 0x03, 0xFF,
986 0xFF, 0xC0, 0x03, 0xFF,
987 0xFF, 0xC0, 0x03, 0xFF,
988 0xFF, 0xC0, 0x03, 0xFF,
989 0xFF, 0xC0, 0x03, 0xFF,
990 0xFF, 0xC0, 0x03, 0xFF,
991 0xFF, 0xC0, 0x03, 0xFF,
992 0xFF, 0xC0, 0x03, 0xFF,
993 0xFF, 0xFF, 0xFF, 0xFF,
994 0xFF, 0xFF, 0xFF, 0xFF,
995 0xFF, 0xFF, 0xFF, 0xFF,
996 0xFF, 0xFF, 0xFF, 0xFF,
997 0xFF, 0xFF, 0xFF, 0xFF,
998 0xFF, 0xFF, 0xFF, 0xFF,
999 0xFF, 0xFF, 0xFF, 0xFF,
1000 0xFF, 0xFF, 0xFF, 0xFF,
1001 0xFF, 0xFF, 0xFF, 0xFF,
1002 0xFF, 0xFF, 0xFF, 0xFF},
1003 {0x00, 0x00, 0x00, 0x00,
1004 0x00, 0x00, 0x00, 0x00,
1005 0x3F, 0xFF, 0xFF, 0xFC,
1006 0x3F, 0xFF, 0xFF, 0xFC,
1007 0x3F, 0xFF, 0xFF, 0xFC,
1008 0x3F, 0xFF, 0xFF, 0xFC,
1009 0x3F, 0xFF, 0xFF, 0xFC,
1010 0x3F, 0xFF, 0xFF, 0xFC,
1011 0x3F, 0x00, 0x00, 0xFC,
1012 0x3F, 0x00, 0x00, 0xFC,
1013 0x3F, 0x00, 0x00, 0xFC,
1014 0x3F, 0x00, 0x00, 0xFC,
1015 0x3F, 0x00, 0x00, 0xFC,
1016 0x3F, 0x00, 0x00, 0xFC,
1017 0x3F, 0x00, 0x00, 0xFC,
1018 0x3F, 0x00, 0x00, 0xFC,
1019 0x3F, 0x00, 0x00, 0xFC,
1020 0x3F, 0x00, 0x00, 0xFC,
1021 0x3F, 0x00, 0x00, 0xFC,
1022 0x3F, 0x00, 0x00, 0xFC,
1023 0x3F, 0x00, 0x00, 0xFC,
1024 0x3F, 0x00, 0x00, 0xFC,
1025 0x3F, 0x00, 0x00, 0xFC,
1026 0x3F, 0x00, 0x00, 0xFC,
1027 0x3F, 0xFF, 0xFF, 0xFC,
1028 0x3F, 0xFF, 0xFF, 0xFC,
1029 0x3F, 0xFF, 0xFF, 0xFC,
1030 0x3F, 0xFF, 0xFF, 0xFC,
1031 0x3F, 0xFF, 0xFF, 0xFC,
1032 0x3F, 0xFF, 0xFF, 0xFC,
1033 0x00, 0x00, 0x00, 0x00,
1034 0x00, 0x00, 0x00, 0x00}
1043 if(tagcols[BACK] == nil) {
1045 tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
1046 tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
1047 tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
1048 tagcols[TEXT] = display->black;
1049 tagcols[HTEXT] = display->black;
1052 textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
1053 textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
1054 textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
1055 textcols[TEXT] = display->black;
1056 textcols[HTEXT] = display->black;
1059 r = Rect(0, 0, Scrollwid+ButtonBorder, font->height+1);
1060 if(button && eqrect(r, button->r))
1065 freeimage(modbutton);
1066 freeimage(colbutton);
1069 button = allocimage(display, r, screen->chan, 0, DNofill);
1070 draw(button, r, tagcols[BACK], nil, r.min);
1071 r.max.x -= ButtonBorder;
1072 border(button, r, ButtonBorder, tagcols[BORD], ZP);
1075 modbutton = allocimage(display, r, screen->chan, 0, DNofill);
1076 draw(modbutton, r, tagcols[BACK], nil, r.min);
1077 r.max.x -= ButtonBorder;
1078 border(modbutton, r, ButtonBorder, tagcols[BORD], ZP);
1079 r = insetrect(r, ButtonBorder);
1080 tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
1081 draw(modbutton, r, tmp, nil, ZP);
1085 colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
1087 but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
1088 but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
1092 * /dev/snarf updates when the file is closed, so we must open our own
1093 * fd here rather than use snarffd
1096 /* rio truncates larges snarf buffers, so this avoids using the
1097 * service if the string is huge */
1099 #define MAXSNARF 100*1024
1110 if(snarfbuf.nc > MAXSNARF)
1114 for(i=0; i<snarfbuf.nc; i+=n){
1118 bufread(&snarfbuf, i, snarfrune, n);
1119 if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
1122 s = fmtstrflush(&f);
1132 int nb, nr, nulls, len;
1136 if(s == nil || s[0]==0){
1142 r = runemalloc(len+1);
1143 cvttorunes(s, len, r, &nb, &nr, &nulls);
1144 bufreset(&snarfbuf);
1145 bufinsert(&snarfbuf, 0, r, nr);
1158 /* This is not foolproof, but it will stop a lot of them. */
1160 return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0);
1168 tm = localtime(va_arg(f->args, ulong));
1169 return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d",
1170 tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);