Ignore scroll/noscroll window setting. Instead, scroll when the write begins in or immediately after the displayed window content. In the new scrolling discipline, executing "Noscroll" is replaced by typing Page Up or using the mouse to scroll higher in the buffer, and executing "Scroll" is replaced by typing End or using the mouse to scroll to the bottom of the buffer. R=r, r2 http://codereview.appspot.com/4433060
639 lines
9.9 KiB
C
639 lines
9.9 KiB
C
#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"
|
|
|
|
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':
|
|
/* no-op */
|
|
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, 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 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);
|
|
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,
|
|
Cook
|
|
};
|
|
|
|
char *menu2str[] = {
|
|
"cut",
|
|
"paste",
|
|
"snarf",
|
|
"plumb",
|
|
"send",
|
|
"cook",
|
|
nil
|
|
};
|
|
|
|
|
|
Menu menu2 =
|
|
{
|
|
menu2str
|
|
};
|
|
|
|
Rune newline[] = { '\n' };
|
|
|
|
void
|
|
button2menu(Window *w)
|
|
{
|
|
if(w->deleted)
|
|
return;
|
|
incref(&w->ref);
|
|
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 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){
|
|
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);
|
|
}
|
|
|