blob: 032f9ded0beccd88fba267a1a7c8be10214ada54 [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9pclient.h>
#include "term.h"
const char *termprog = "win";
#define EVENTSIZE 256
#define STACK 32768
typedef struct Event Event;
typedef struct Q Q;
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
Event blank = {
'M',
'X',
0, 0, 0, 1, 1,
{ ' ', 0 },
{ ' ', 0 }
};
struct Q
{
QLock lk;
int p;
int k;
};
Q q;
CFid *eventfd;
CFid *addrfd;
CFid *datafd;
CFid *ctlfd;
/* int bodyfd; */
char *typing;
int ntypeb;
int ntyper;
int ntypebreak;
int debug;
int rcfd;
int cook = 1;
int password;
int israw(int);
char *name;
char **prog;
Channel *cwait;
int pid = -1;
int label(char*, int);
void error(char*, ...);
void stdinproc(void*);
void stdoutproc(void*);
void type(Event*, int, CFid*, CFid*);
void sende(Event*, int, CFid*, CFid*, CFid*, int);
char *onestring(int, char**);
int delete(Event*);
void deltype(uint, uint);
void sendbs(int, int);
void runproc(void*);
int
fsfidprint(CFid *fid, char *fmt, ...)
{
char buf[256];
va_list arg;
int n;
va_start(arg, fmt);
n = vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
return fswrite(fid, buf, n);
}
void
usage(void)
{
fprint(2, "usage: win cmd args...\n");
threadexitsall("usage");
}
void
waitthread(void *v)
{
recvp(cwait);
threadexitsall(nil);
}
void
hangupnote(void *a, char *msg)
{
if(strcmp(msg, "hangup") == 0 && pid != 0){
postnote(PNGROUP, pid, "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) == pid)
threadexitsall(0);
}
noted(NCONT);
}
noted(NDFLT);
}
void
threadmain(int argc, char **argv)
{
int fd, id;
char buf[256];
char buf1[128];
CFsys *fs;
char *dump;
dump = onestring(argc, argv);
ARGBEGIN{
case 'd':
debug = 1;
break;
case 'n':
name = EARGF(usage());
break;
default:
usage();
}ARGEND
prog = argv;
if(name == nil){
if(argc > 0)
name = argv[0];
else{
name = sysname();
if(name == nil)
name = "gnot";
}
}
/*
* notedisable("sys: write on closed pipe");
* not okay to disable the note, because that
* gets inherited by the subshell, so that something
* as simple as "yes | sed 10q" never exits.
* call notifyoff instead. (is notedisable ever safe?)
*/
notifyoff("sys: write on closed pipe");
noteenable("sys: child");
notify(hangupnote);
if((fs = nsmount("acme", "")) == 0)
sysfatal("nsmount acme: %r");
ctlfd = fsopen(fs, "new/ctl", ORDWR|OCEXEC);
if(ctlfd == 0 || fsread(ctlfd, buf, 12) != 12)
sysfatal("ctl: %r");
id = atoi(buf);
snprint(buf, sizeof buf, "%d", id);
putenv("winid", buf);
sprint(buf, "%d/tag", id);
fd = fsopenfd(fs, buf, OWRITE|OCEXEC);
write(fd, " Send", 1+4);
close(fd);
sprint(buf, "%d/event", id);
eventfd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/addr", id);
addrfd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/data", id);
datafd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/body", id);
/* bodyfd = fsopenfd(fs, buf, ORDWR|OCEXEC); */
if(eventfd==nil || addrfd==nil || datafd==nil)
sysfatal("data files: %r");
/*
if(eventfd<0 || addrfd<0 || datafd<0 || bodyfd<0)
sysfatal("data files: %r");
*/
fsunmount(fs);
cwait = threadwaitchan();
threadcreate(waitthread, nil, STACK);
pid = rcstart(argc, argv, &rcfd, nil);
if(pid == -1)
sysfatal("exec failed");
getwd(buf1, sizeof buf1);
sprint(buf, "name %s/-%s\n0\n", buf1, name);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "dumpdir %s/\n", buf1);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "dump %s\n", dump);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "scroll");
fswrite(ctlfd, buf, strlen(buf));
updatewinsize(25, 80, 0, 0);
proccreate(stdoutproc, nil, STACK);
stdinproc(nil);
}
void
error(char *s, ...)
{
va_list arg;
if(s){
va_start(arg, s);
s = vsmprint(s, arg);
va_end(arg);
fprint(2, "win: %s: %r\n", s);
}
if(pid != -1)
postnote(PNGROUP, pid, "hangup");
threadexitsall(s);
}
char*
onestring(int argc, char **argv)
{
char *p;
int i, n;
static char buf[1024];
if(argc == 0)
return "";
p = buf;
for(i=0; i<argc; i++){
n = strlen(argv[i]);
if(p+n+1 >= buf+sizeof buf)
break;
memmove(p, argv[i], n);
p += n;
*p++ = ' ';
}
p[-1] = 0;
return buf;
}
int
getec(CFid *efd)
{
static char buf[8192];
static char *bufp;
static int nbuf;
if(nbuf == 0){
nbuf = fsread(efd, buf, sizeof buf);
if(nbuf <= 0)
error(nil);
bufp = buf;
}
--nbuf;
return *bufp++;
}
int
geten(CFid *efd)
{
int n, c;
n = 0;
while('0'<=(c=getec(efd)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
geter(CFid *efd, char *buf, int *nb)
{
Rune r;
int n;
r = getec(efd);
buf[0] = r;
n = 1;
if(r < Runeself)
goto Return;
while(!fullrune(buf, n))
buf[n++] = getec(efd);
chartorune(&r, buf);
Return:
*nb = n;
return r;
}
void
gete(CFid *efd, Event *e)
{
int i, nb;
e->c1 = getec(efd);
e->c2 = getec(efd);
e->q0 = geten(efd);
e->q1 = geten(efd);
e->flag = geten(efd);
e->nr = geten(efd);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = geter(efd, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(getec(efd) != '\n')
error("event syntax 2");
}
int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
stdinproc(void *v)
{
CFid *cfd = ctlfd;
CFid *efd = eventfd;
CFid *dfd = datafd;
CFid *afd = addrfd;
int fd0 = rcfd;
Event e, e2, e3, e4;
int n;
USED(v);
for(;;){
if(debug)
fprint(2, "typing[%d,%d)\n", q.p, q.p+ntyper);
gete(efd, &e);
if(debug)
fprint(2, "msg %c%c q[%d,%d)... ", e.c1, e.c2, e.q0, e.q1);
qlock(&q.lk);
switch(e.c1){
default:
Unknown:
print("unknown message %c%c\n", e.c1, e.c2);
break;
case 'E': /* write to body or tag; can't affect us */
switch(e.c2){
case 'I':
case 'D': /* body */
if(debug)
fprint(2, "shift typing %d... ", e.q1-e.q0);
q.p += e.q1-e.q0;
break;
case 'i':
case 'd': /* tag */
break;
default:
goto Unknown;
}
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K':
case 'M':
switch(e.c2){
case 'I':
if(e.nr == 1 && e.r[0] == 0x7F) {
char buf[1];
fsprint(addrfd, "#%ud,#%ud", e.q0, e.q1);
fswrite(datafd, "", 0);
buf[0] = 0x7F;
write(fd0, buf, 1);
break;
}
if(e.q0 < q.p){
if(debug)
fprint(2, "shift typing %d... ", e.q1-e.q0);
q.p += e.q1-e.q0;
}
else if(e.q0 <= q.p+ntyper){
if(debug)
fprint(2, "type... ");
type(&e, fd0, afd, dfd);
}
break;
case 'D':
n = delete(&e);
q.p -= n;
if(israw(fd0) && e.q1 >= q.p+n)
sendbs(fd0, n);
break;
case 'x':
case 'X':
if(e.flag & 2)
gete(efd, &e2);
if(e.flag & 8){
gete(efd, &e3);
gete(efd, &e4);
}
if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){
/* send it straight back */
fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
break;
}
if(e.q0==e.q1 && (e.flag&2)){
e2.flag = e.flag;
e = e2;
}
char buf[100];
snprint(buf, sizeof buf, "%.*S", e.nr, e.r);
if(cistrcmp(buf, "cook") == 0) {
cook = 1;
break;
}
if(cistrcmp(buf, "nocook") == 0) {
cook = 0;
break;
}
if(e.flag & 8){
if(e.q1 != e.q0){
sende(&e, fd0, cfd, afd, dfd, 0);
sende(&blank, fd0, cfd, afd, dfd, 0);
}
sende(&e3, fd0, cfd, afd, dfd, 1);
}else if(e.q1 != e.q0)
sende(&e, fd0, cfd, afd, dfd, 1);
break;
case 'l':
case 'L':
/* just send it back */
if(e.flag & 2)
gete(efd, &e2);
fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
break;
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
qunlock(&q.lk);
}
}
int
dropcr(char *p, int n)
{
int i;
char *w, *r, *q;
r = p;
w = p;
for(i=0; i<n; i++) {
switch(*r) {
case '\b':
if(w > p)
w--;
break;
case '\r':
while(i<n-1 && *(r+1) == '\r') {
r++;
i++;
}
if(i<n && *(r+1) != '\n') {
q = r;
while(q>p && *(q-1) != '\n')
q--;
if(q > p) {
w = q;
break;
}
}
*w++ = '\n';
break;
default:
*w++ = *r;
break;
}
r++;
}
return w-p;
}
void
stdoutproc(void *v)
{
int fd1 = rcfd;
CFid *afd = addrfd;
CFid *dfd = datafd;
int n, m, w, npart;
char *buf, *s, *t;
Rune r;
char x[16], hold[UTFmax];
USED(v);
buf = malloc(8192+UTFmax+1);
npart = 0;
for(;;){
/* Let typing have a go -- maybe there's a rubout waiting. */
yield();
n = read(fd1, buf+npart, 8192);
if(n <= 0)
error(nil);
n = echocancel(buf+npart, n);
if(n == 0)
continue;
n = dropcrnl(buf+npart, n);
if(n == 0)
continue;
n = dropcr(buf+npart, n);
if(n == 0)
continue;
/* squash NULs */
s = memchr(buf+npart, 0, n);
if(s){
for(t=s; s<buf+npart+n; s++)
if(*t = *s) /* assign = */
t++;
n = t-(buf+npart);
}
n += npart;
/* hold on to final partial rune */
npart = 0;
while(n>0 && (buf[n-1]&0xC0)){
--n;
npart++;
if((buf[n]&0xC0)!=0x80){
if(fullrune(buf+n, npart)){
w = chartorune(&r, buf+n);
n += w;
npart -= w;
}
break;
}
}
if(n > 0){
memmove(hold, buf+n, npart);
buf[n] = 0;
n = label(buf, n);
buf[n] = 0;
// clumsy but effective: notice password
// prompts so we can disable echo.
password = 0;
if(cistrstr(buf, "password") || cistrstr(buf, "passphrase")) {
int i;
i = n;
while(i > 0 && buf[i-1] == ' ')
i--;
password = i > 0 && buf[i-1] == ':';
}
qlock(&q.lk);
m = sprint(x, "#%d", q.p);
if(fswrite(afd, x, m) != m){
fprint(2, "stdout writing address %s: %r; resetting\n", x);
if(fswrite(afd, "$", 1) < 0)
fprint(2, "reset: %r\n");
fsseek(afd, 0, 0);
m = fsread(afd, x, sizeof x-1);
if(m >= 0){
x[m] = 0;
q.p = atoi(x);
}
}
if(fswrite(dfd, buf, n) != n)
error("stdout writing body");
/* Make sure acme scrolls to the end of the above write. */
if(fswrite(dfd, nil, 0) != 0)
error("stdout flushing body");
q.p += nrunes(buf, n);
qunlock(&q.lk);
memmove(buf, hold, npart);
}
}
}
char wdir[512];
int
label(char *sr, int n)
{
char *sl, *el, *er, *r, *p;
er = sr+n;
for(r=er-1; r>=sr; r--)
if(*r == '\007')
break;
if(r < sr)
return n;
el = r+1;
if(el-sr > sizeof wdir - strlen(name) - 20)
sr = el - sizeof wdir - strlen(name) - 20;
for(sl=el-3; sl>=sr; sl--)
if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
break;
if(sl < sr)
return n;
*r = 0;
if(strcmp(sl+3, "*9term-hold+") != 0) {
/*
* add /-sysname if not present
*/
snprint(wdir, sizeof wdir, "name %s", sl+3);
p = strrchr(wdir, '/');
if(p==nil || *(p+1) != '-'){
p = wdir+strlen(wdir);
if(*(p-1) != '/')
*p++ = '/';
*p++ = '-';
strcpy(p, name);
}
strcat(wdir, "\n0\n");
fswrite(ctlfd, wdir, strlen(wdir));
}
memmove(sl, el, er-el);
n -= (el-sl);
return n;
}
int
delete(Event *e)
{
uint q0, q1;
int deltap;
q0 = e->q0;
q1 = e->q1;
if(q1 <= q.p)
return e->q1-e->q0;
if(q0 >= q.p+ntyper)
return 0;
deltap = 0;
if(q0 < q.p){
deltap = q.p-q0;
q0 = 0;
}else
q0 -= q.p;
if(q1 > q.p+ntyper)
q1 = ntyper;
else
q1 -= q.p;
deltype(q0, q1);
return deltap;
}
void
addtype(int c, uint p0, char *b, int nb, int nr)
{
int i, w;
Rune r;
uint p;
char *b0;
for(i=0; i<nb; i+=w){
w = chartorune(&r, b+i);
if((r==0x7F||r==3) && c=='K'){
write(rcfd, "\x7F", 1);
/* toss all typing */
q.p += ntyper+nr;
ntypebreak = 0;
ntypeb = 0;
ntyper = 0;
/* buglet: more than one delete ignored */
return;
}
if(r=='\n' || r==0x04)
ntypebreak++;
}
typing = realloc(typing, ntypeb+nb);
if(typing == nil)
error("realloc");
if(p0 == ntyper)
memmove(typing+ntypeb, b, nb);
else{
b0 = typing;
for(p=0; p<p0 && b0<typing+ntypeb; p++){
w = chartorune(&r, b0+i);
b0 += w;
}
if(p != p0)
error("typing: findrune");
memmove(b0+nb, b0, (typing+ntypeb)-b0);
memmove(b0, b, nb);
}
ntypeb += nb;
ntyper += nr;
}
int
israw(int fd0)
{
return (!cook || password) && !isecho(fd0);
}
void
sendtype(int fd0)
{
int i, n, nr, raw;
raw = israw(fd0);
while(ntypebreak || (raw && ntypeb > 0)){
for(i=0; i<ntypeb; i++)
if(typing[i]=='\n' || typing[i]==0x04 || (i==ntypeb-1 && raw)){
if((typing[i] == '\n' || typing[i] == 0x04) && ntypebreak > 0)
ntypebreak--;
n = i+1;
i++;
if(!raw)
echoed(typing, n);
if(write(fd0, typing, n) != n)
error("sending to program");
nr = nrunes(typing, i);
q.p += nr;
ntyper -= nr;
ntypeb -= i;
memmove(typing, typing+i, ntypeb);
goto cont2;
}
print("no breakchar\n");
ntypebreak = 0;
cont2:;
}
}
void
sendbs(int fd0, int n)
{
char buf[128];
int m;
memset(buf, 0x08, sizeof buf);
while(n > 0) {
m = sizeof buf;
if(m > n)
m = n;
n -= m;
write(fd0, buf, m);
}
}
void
deltype(uint p0, uint p1)
{
int w;
uint p, b0, b1;
Rune r;
/* advance to p0 */
b0 = 0;
for(p=0; p<p0 && b0<ntypeb; p++){
w = chartorune(&r, typing+b0);
b0 += w;
}
if(p != p0)
error("deltype 1");
/* advance to p1 */
b1 = b0;
for(; p<p1 && b1<ntypeb; p++){
w = chartorune(&r, typing+b1);
b1 += w;
if(r=='\n' || r==0x04)
ntypebreak--;
}
if(p != p1)
error("deltype 2");
memmove(typing+b0, typing+b1, ntypeb-b1);
ntypeb -= b1-b0;
ntyper -= p1-p0;
}
void
type(Event *e, int fd0, CFid *afd, CFid *dfd)
{
int m, n, nr;
char buf[128];
if(e->nr > 0)
addtype(e->c1, e->q0-q.p, e->b, e->nb, e->nr);
else{
m = e->q0;
while(m < e->q1){
n = sprint(buf, "#%d", m);
fswrite(afd, buf, n);
n = fsread(dfd, buf, sizeof buf);
nr = nrunes(buf, n);
while(m+nr > e->q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
addtype(e->c1, m-q.p, buf, n, nr);
m += nr;
}
}
if(israw(fd0)) {
n = sprint(buf, "#%d,#%d", e->q0, e->q1);
fswrite(afd, buf, n);
fswrite(dfd, "", 0);
q.p -= e->q1 - e->q0;
}
sendtype(fd0);
if(e->nb > 0 && e->b[e->nb-1] == '\n')
cook = 1;
}
void
sende(Event *e, int fd0, CFid *cfd, CFid *afd, CFid *dfd, int donl)
{
int l, m, n, nr, lastc, end;
char abuf[16], buf[128];
end = q.p+ntyper;
l = sprint(abuf, "#%d", end);
fswrite(afd, abuf, l);
if(e->nr > 0){
fswrite(dfd, e->b, e->nb);
addtype(e->c1, ntyper, e->b, e->nb, e->nr);
lastc = e->r[e->nr-1];
}else{
m = e->q0;
lastc = 0;
while(m < e->q1){
n = sprint(buf, "#%d", m);
fswrite(afd, buf, n);
n = fsread(dfd, buf, sizeof buf);
nr = nrunes(buf, n);
while(m+nr > e->q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
l = sprint(abuf, "#%d", end);
fswrite(afd, abuf, l);
fswrite(dfd, buf, n);
addtype(e->c1, ntyper, buf, n, nr);
lastc = buf[n-1];
m += nr;
end += nr;
}
}
if(donl && lastc!='\n'){
fswrite(dfd, "\n", 1);
addtype(e->c1, ntyper, "\n", 1, 1);
}
fswrite(cfd, "dot=addr", 8);
sendtype(fd0);
}