#include #include #include #include #include #include #include #include #include #include #include #include #define Extern #include "dat.h" #include "fns.h" #include "term.h" #include "label.h" int use9wm; int mainpid; int plumbfd; int rcpid; int rcfd; int sfd; int noecho; Window *w; char *fontname; void derror(Display*, char*); void mousethread(void*); void keyboardthread(void*); void winclosethread(void*); void deletethread(void*); void rcoutputproc(void*); void rcinputproc(void*); void hangupnote(void*, char*); void resizethread(void*); void servedevtext(void); int (*label)(Rune*, int) = labelcooked; int errorshouldabort = 0; void usage(void) { fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n"); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { char *p; rfork(RFNOTEG); font = nil; _wantfocuschanges = 1; mainpid = getpid(); messagesize = 8192; ARGBEGIN{ default: usage(); case 'l': loginshell = TRUE; break; case 'f': fontname = EARGF(usage()); break; case 's': scrolling = TRUE; break; case 'w': /* started from rio or 9wm */ use9wm = TRUE; break; case 'L': p=ARGF(); if (strcmp(p, "none") == 0) label = labelnone; else if (strcmp(p, "basic") == 0) label = labelbasic; else label = labelcooked; break; case 'W': winsize = EARGF(usage()); break; }ARGEND if(fontname) putenv("font", fontname); p = getenv("tabstop"); if(p == 0) p = getenv("TABSTOP"); if(p && maxtab <= 0) maxtab = strtoul(p, 0, 0); if(maxtab <= 0) maxtab = 4; free(p); startdir = "."; initdraw(derror, fontname, "9term"); notify(hangupnote); noteenable("sys: child"); mousectl = initmouse(nil, screen); if(mousectl == nil) error("cannot find mouse"); keyboardctl = initkeyboard(nil); if(keyboardctl == nil) error("cannot find keyboard"); mouse = &mousectl->m; winclosechan = chancreate(sizeof(Window*), 0); deletechan = chancreate(sizeof(char*), 0); timerinit(); servedevtext(); rcpid = rcstart(argc, argv, &rcfd, &sfd); w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil); threadcreate(keyboardthread, nil, STACK); threadcreate(mousethread, nil, STACK); threadcreate(resizethread, nil, STACK); proccreate(rcoutputproc, nil, STACK); proccreate(rcinputproc, nil, STACK); } void derror(Display *d, char *errorstr) { USED(d); error(errorstr); } void hangupnote(void *a, char *msg) { if(getpid() != mainpid) noted(NDFLT); if(strcmp(msg, "hangup") == 0) noted(NCONT); if(strstr(msg, "child")){ char buf[128]; int n; n = awaitnohang(buf, sizeof buf-1); if(n > 0){ buf[n] = 0; if(atoi(buf) == rcpid) threadexitsall(0); } noted(NCONT); } noted(NDFLT); } void keyboardthread(void *v) { Rune buf[2][20], *rp; int i, n; USED(v); threadsetname("keyboardthread"); n = 0; for(;;){ rp = buf[n]; n = 1-n; recv(keyboardctl->c, rp); for(i=1; ic, rp+i) <= 0) break; rp[i] = L'\0'; sendp(w->ck, rp); } } void resizethread(void *v) { Point p; USED(v); for(;;){ p = stringsize(display->defaultfont, "0"); if(p.x && p.y) updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x, Dx(screen->r), Dy(screen->r)); wresize(w, screen, 0); flushimage(display, 1); if(recv(mousectl->resizec, nil) != 1) break; if(getwindow(display, Refnone) < 0) sysfatal("can't reattach to window"); } } void mousethread(void *v) { int sending; Mouse tmp; USED(v); sending = FALSE; threadsetname("mousethread"); while(readmouse(mousectl) >= 0){ if(sending){ Send: /* send to window */ if(mouse->buttons == 0) sending = FALSE; else wsetcursor(w, 0); tmp = mousectl->m; send(w->mc.c, &tmp); continue; } if((mouse->buttons&1) || ptinrect(mouse->xy, w->scrollr)){ sending = TRUE; goto Send; }else if(mouse->buttons&2) button2menu(w); else bouncemouse(mouse); } } void wborder(Window *w, int type) { } Window* wpointto(Point pt) { return w; } Window* new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv) { Window *w; Mousectl *mc; Channel *cm, *ck, *cctl; if(i == nil) return nil; cm = chancreate(sizeof(Mouse), 0); ck = chancreate(sizeof(Rune*), 0); cctl = chancreate(sizeof(Wctlmesg), 4); if(cm==nil || ck==nil || cctl==nil) error("new: channel alloc failed"); mc = emalloc(sizeof(Mousectl)); *mc = *mousectl; // mc->image = i; mc->c = cm; w = wmk(i, mc, ck, cctl, scrollit); free(mc); /* wmk copies *mc */ window = erealloc(window, ++nwindow*sizeof(Window*)); window[nwindow-1] = w; if(hideit){ hidden[nhidden++] = w; w->screenr = ZR; } threadcreate(winctl, w, STACK); if(!hideit) wcurrent(w); flushimage(display, 1); wsetpid(w, pid, 1); wsetname(w); if(dir) w->dir = estrdup(dir); return w; } /* * Button 2 menu. Extra entry for always cook */ int cooked; enum { Cut, Paste, Snarf, Plumb, Send, Scroll, Cook, Look, RLook, }; char *menu2str[] = { "cut", "paste", "snarf", "plumb", "send", "scroll", "cook", "look", "rlook", nil }; Menu menu2 = { menu2str }; Rune newline[] = { '\n' }; char lookmenu[UTFmax*15]; char rlookmenu[UTFmax*15]; char* lookstr(char* M, const char* L) { if (w->q0 != w->q1){ snprint(M, UTFmax*15, "%s %.10*S", L, min(10,w->q1-w->q0), w->r+w->q0); } else if (nlook){ snprint(M, UTFmax*15, "%s %.10S", L, look); } else { snprint(M, 15, "Look %s", L); } return M; } void button2menu(Window *w) { if(w->deleted) return; incref(&w->ref); if(w->scrolling) menu2str[Scroll] = "noscroll"; else menu2str[Scroll] = "scroll"; if(cooked) menu2str[Cook] = "nocook"; else menu2str[Cook] = "cook"; menu2str[Look] = lookstr(lookmenu, "→"); menu2str[RLook] = lookstr(rlookmenu, "←"); switch(menuhit(2, mousectl, &menu2, wscreen)){ case Cut: wsnarf(w); wcut(w); wscrdraw(w); break; case Snarf: wsnarf(w); break; case Paste: riogetsnarf(); wpaste(w); wscrdraw(w); break; case Plumb: wplumb(w); break; case Send: riogetsnarf(); wsnarf(w); if(nsnarf == 0) break; if(w->rawing){ waddraw(w, snarf, nsnarf); if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') waddraw(w, newline, 1); }else{ winsert(w, snarf, nsnarf, w->nr); if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') winsert(w, newline, 1, w->nr); } wsetselect(w, w->nr, w->nr); wshow(w, w->nr); break; case Scroll: if(w->scrolling ^= 1) wshow(w, w->nr); break; case Cook: cooked ^= 1; break; case Look: wlook(w, 0); wsetselect(w, w->q0, w->q1); wshow(w, w->q0); break; case RLook: wlook(w, 1); wsetselect(w, w->q0, w->q1); wshow(w, w->q0); break; } wclose(w); wsendctlmesg(w, Wakeup, ZR, nil); flushimage(display, 1); } int rawon(void) { return !cooked && !isecho(sfd); } /* * I/O with child rc. */ void rcoutputproc(void *arg) { int i, cnt, n, nb, nr; static char data[9000]; Conswritemesg cwm; Rune *r; Stringpair pair; i = 0; cnt = 0; for(;;){ /* XXX Let typing have a go -- maybe there's a rubout waiting. */ i = 1-i; n = read(rcfd, data+cnt, sizeof data-cnt); if(n <= 0){ if(n < 0) fprint(2, "9term: rc read error: %r\n"); threadexitsall("eof on rc output"); } cnt += n; r = runemalloc(cnt); cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil); /* approach end of buffer */ while(fullrune(data+nb, cnt-nb)){ nb += chartorune(&r[nr], data+nb); if(r[nr]) nr++; } if(nb < cnt) memmove(data, data+nb, cnt-nb); cnt -= nb; nr = label(r, nr); if(nr == 0) continue; recv(w->conswrite, &cwm); pair.s = r; pair.ns = nr; send(cwm.cw, &pair); } } void winterrupt(Window *w) { char rubout[1]; USED(w); rubout[0] = getintr(sfd); write(rcfd, rubout, 1); } /* * Process in-band messages about window title changes. * The messages are of the form: * * \033];xxx\007 * * where xxx is the new directory. This format was chosen * because it changes the label on xterm windows. */ int labelbasic(Rune *sr, int n) { Rune *sl, *el, *er, *r; char *p, *dir; er = sr+n; for(r=er-1; r>=sr; r--) if(*r == '\007') break; if(r < sr) return n; el = r+1; for(sl=el-3; sl>=sr; sl--) if(sl[0]=='\033' && sl[1]==']' && sl[2]==';') break; if(sl < sr) return n; dir = smprint("%.*S", (el-1)-(sl+3), sl+3); if(dir){ drawsetlabel(dir); free(w->dir); w->dir = dir; } /* remove trailing /-sysname if present */ p = strrchr(dir, '/'); if(p && *(p+1) == '-'){ if(p == dir) p++; *p = 0; } runemove(sl, el, er-el); n -= (el-sl); return n; } void rcinputproc(void *arg) { static char data[9000]; int s; Consreadmesg crm; Channel *c1, *c2; Stringpair pair; for(;;){ recv(w->consread, &crm); c1 = crm.c1; c2 = crm.c2; pair.s = data; pair.ns = sizeof data; send(c1, &pair); recv(c2, &pair); s = setecho(sfd, 0); if(write(rcfd, pair.s, pair.ns) < 0) threadexitsall(nil); if(s) setecho(sfd, s); } } /* * Snarf buffer - rio uses runes internally */ void rioputsnarf(void) { char *s; s = smprint("%.*S", nsnarf, snarf); if(s){ putsnarf(s); free(s); } } void riogetsnarf(void) { char *s; int n, nb, nulls; s = getsnarf(); if(s == nil) return; n = strlen(s)+1; free(snarf); snarf = runemalloc(n); cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls); free(s); } /* * Clumsy hack to make " and "" work. * Then again, what's not a clumsy hack here in Unix land? */ char adir[100]; char thesocket[100]; int afd; void listenproc(void*); void textproc(void*); void removethesocket(void) { if(thesocket[0]) if(remove(thesocket) < 0) fprint(2, "remove %s: %r\n", thesocket); } void servedevtext(void) { char buf[100]; snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid()); if((afd = announce(buf, adir)) < 0){ putenv("text9term", ""); return; } putenv("text9term", buf); proccreate(listenproc, nil, STACK); strcpy(thesocket, buf+5); atexit(removethesocket); } void listenproc(void *arg) { int fd; char dir[100]; threadsetname("listen %s", thesocket); USED(arg); for(;;){ fd = listen(adir, dir); if(fd < 0){ close(afd); return; } proccreate(textproc, (void*)(uintptr)fd, STACK); } } void textproc(void *arg) { int fd, i, x, n, end; Rune r; char buf[4096], *p, *ep; threadsetname("textproc"); fd = (uintptr)arg; p = buf; ep = buf+sizeof buf; if(w == nil){ close(fd); return; } end = w->org+w->nr; /* avoid possible output loop */ for(i=w->org;; i++){ if(i >= end || ep-p < UTFmax){ for(x=0; x= end) break; p = buf; } if(i < w->org) i = w->org; r = w->r[i-w->org]; if(r < Runeself) *p++ = r; else p += runetochar(p, &r); } break2: close(fd); }