| #include <u.h> |
| #include <signal.h> |
| #include <libc.h> |
| #include <ctype.h> |
| #include <draw.h> |
| #include <thread.h> |
| #include <mouse.h> |
| #include <cursor.h> |
| #include <keyboard.h> |
| #include <frame.h> |
| #include <plumb.h> |
| #include <complete.h> |
| #define Extern |
| #include "dat.h" |
| #include "fns.h" |
| #include "term.h" |
| |
| const char *termprog = "9term"; |
| int use9wm; |
| int mainpid; |
| int mousepid; |
| int plumbfd; |
| int rcpid; |
| int rcfd; |
| int sfd; |
| 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 errorshouldabort = 0; |
| int cooked; |
| |
| 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 'c': |
| cooked = TRUE; |
| break; |
| case 'w': /* started from rio or 9wm */ |
| use9wm = TRUE; |
| 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 = "."; |
| |
| if(initdraw(derror, fontname, "9term") < 0) |
| sysfatal("initdraw: %r"); |
| |
| 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){ |
| postnote(PNPROC, rcpid, "hangup"); |
| noted(NDFLT); |
| } |
| 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; i<nelem(buf[0])-1; i++) |
| if(nbrecv(keyboardctl->c, 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|8|16)) || 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 |
| */ |
| |
| enum |
| { |
| Cut, |
| Paste, |
| Snarf, |
| Plumb, |
| Send, |
| Scroll, |
| Cook |
| }; |
| |
| char *menu2str[] = { |
| "cut", |
| "paste", |
| "snarf", |
| "plumb", |
| "send", |
| "cook", |
| "scroll", |
| nil |
| }; |
| |
| |
| Menu menu2 = |
| { |
| menu2str |
| }; |
| |
| Rune newline[] = { '\n' }; |
| |
| 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"; |
| |
| 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; |
| } |
| wclose(w); |
| wsendctlmesg(w, Wakeup, ZR, nil); |
| flushimage(display, 1); |
| } |
| |
| int |
| rawon(void) |
| { |
| return !cooked && !isecho(sfd); |
| } |
| |
| /* |
| * I/O with child rc. |
| */ |
| |
| int label(Rune*, int); |
| |
| 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"); |
| } |
| n = echocancel(data+cnt, n); |
| if(n == 0) |
| continue; |
| 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 |
| label(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){ |
| if(strcmp(dir, "*9term-hold+") == 0) { |
| w->holding = 1; |
| wrepaint(w); |
| flushimage(display, 1); |
| } else { |
| 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]; |
| 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); |
| |
| if(isecho(sfd)) |
| echoed(pair.s, pair.ns); |
| if(write(rcfd, pair.s, pair.ns) < 0) |
| threadexitsall(nil); |
| } |
| } |
| |
| /* |
| * 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<p-buf; x+=n) |
| if((n = write(fd, buf+x, (p-x)-buf)) <= 0) |
| goto break2; |
| |
| if(i >= 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); |
| } |
| |