plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

9term.c (11203B)


      1 #include <u.h>
      2 #include <pwd.h>
      3 #include <signal.h>
      4 #include <libc.h>
      5 #include <ctype.h>
      6 #include <draw.h>
      7 #include <thread.h>
      8 #include <mouse.h>
      9 #include <cursor.h>
     10 #include <keyboard.h>
     11 #include <frame.h>
     12 #include <plumb.h>
     13 #include <complete.h>
     14 #define Extern
     15 #include "dat.h"
     16 #include "fns.h"
     17 #include "term.h"
     18 
     19 const char *termprog = "9term";
     20 int use9wm;
     21 int mainpid;
     22 int mousepid;
     23 int plumbfd;
     24 int rcpid;
     25 int rcfd;
     26 int sfd;
     27 Window *w;
     28 char *fontname;
     29 
     30 void derror(Display*, char*);
     31 void	mousethread(void*);
     32 void	keyboardthread(void*);
     33 void winclosethread(void*);
     34 void deletethread(void*);
     35 void rcoutputproc(void*);
     36 void	rcinputproc(void*);
     37 void hangupnote(void*, char*);
     38 void resizethread(void*);
     39 void	servedevtext(void);
     40 
     41 int errorshouldabort = 0;
     42 int cooked;
     43 
     44 void
     45 usage(void)
     46 {
     47 	fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n");
     48 	threadexitsall("usage");
     49 }
     50 
     51 int
     52 threadmaybackground(void)
     53 {
     54 	return 1;
     55 }
     56 
     57 void
     58 threadmain(int argc, char *argv[])
     59 {
     60 	char *p, *env;
     61 
     62 	rfork(RFNOTEG);
     63 	font = nil;
     64 	_wantfocuschanges = 1;
     65 	mainpid = getpid();
     66 	messagesize = 8192;
     67 
     68 	threadmaybackground();
     69 
     70 	env = getenv("__CFBundleIdentifier");
     71 	if(env != nil && strcmp(env, "com.swtch.9term") == 0) {
     72 		// Being invoked as $PLAN9/mac/9term.app.
     73 		// Set $SHELL and daemonize to let parent exit.
     74 		// This makes sure that each click on 9term
     75 		// brings up a new window.
     76 		extern void _threaddaemonize(void);
     77 		struct passwd *pw;
     78 
     79 		unsetenv("__CFBundleIdentifier");
     80 		pw = getpwuid(getuid());
     81 		if(pw != nil && pw->pw_shell != nil)
     82 			setenv("SHELL", pw->pw_shell, 1);
     83 		loginshell = TRUE;
     84 		//_threaddaemonize();
     85 	}
     86 
     87 	ARGBEGIN{
     88 	default:
     89 		usage();
     90 	case 'l':
     91 		loginshell = TRUE;
     92 		break;
     93 	case 'f':
     94 		fontname = EARGF(usage());
     95 		break;
     96 	case 's':
     97 		scrolling = TRUE;
     98 		break;
     99 	case 'c':
    100 		cooked = TRUE;
    101 		break;
    102 	case 'w':	/* started from rio or 9wm */
    103 		use9wm = TRUE;
    104 		break;
    105 	case 'W':
    106 		winsize = EARGF(usage());
    107 		break;
    108 	}ARGEND
    109 
    110 	if(fontname)
    111 		putenv("font", fontname);
    112 
    113 	p = getenv("tabstop");
    114 	if(p == 0)
    115 		p = getenv("TABSTOP");
    116 	if(p && maxtab <= 0)
    117 		maxtab = strtoul(p, 0, 0);
    118 	if(maxtab <= 0)
    119 		maxtab = 4;
    120 	free(p);
    121 
    122 	startdir = ".";
    123 
    124 	if(initdraw(derror, fontname, "9term") < 0)
    125 		sysfatal("initdraw: %r");
    126 
    127 	notify(hangupnote);
    128 	noteenable("sys: child");
    129 
    130 	mousectl = initmouse(nil, screen);
    131 	if(mousectl == nil)
    132 		error("cannot find mouse");
    133 	keyboardctl = initkeyboard(nil);
    134 	if(keyboardctl == nil)
    135 		error("cannot find keyboard");
    136 	mouse = &mousectl->m;
    137 
    138 	winclosechan = chancreate(sizeof(Window*), 0);
    139 	deletechan = chancreate(sizeof(char*), 0);
    140 
    141 	timerinit();
    142 	servedevtext();
    143 	rcpid = rcstart(argc, argv, &rcfd, &sfd);
    144 	w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil);
    145 
    146 	threadcreate(keyboardthread, nil, STACK);
    147 	threadcreate(mousethread, nil, STACK);
    148 	threadcreate(resizethread, nil, STACK);
    149 
    150 	proccreate(rcoutputproc, nil, STACK);
    151 	proccreate(rcinputproc, nil, STACK);
    152 }
    153 
    154 void
    155 derror(Display *d, char *errorstr)
    156 {
    157 	USED(d);
    158 	error(errorstr);
    159 }
    160 
    161 void
    162 hangupnote(void *a, char *msg)
    163 {
    164 	if(getpid() != mainpid)
    165 		noted(NDFLT);
    166 	if(strcmp(msg, "hangup") == 0){
    167 		postnote(PNPROC, rcpid, "hangup");
    168 		noted(NDFLT);
    169 	}
    170 	if(strstr(msg, "child")){
    171 		char buf[128];
    172 		int n;
    173 
    174 		n = awaitnohang(buf, sizeof buf-1);
    175 		if(n > 0){
    176 			buf[n] = 0;
    177 			if(atoi(buf) == rcpid)
    178 				threadexitsall(0);
    179 		}
    180 		noted(NCONT);
    181 	}
    182 	noted(NDFLT);
    183 }
    184 
    185 void
    186 keyboardthread(void *v)
    187 {
    188 	Rune buf[2][20], *rp;
    189 	int i, n;
    190 
    191 	USED(v);
    192 	threadsetname("keyboardthread");
    193 	n = 0;
    194 	for(;;){
    195 		rp = buf[n];
    196 		n = 1-n;
    197 		recv(keyboardctl->c, rp);
    198 		for(i=1; i<nelem(buf[0])-1; i++)
    199 			if(nbrecv(keyboardctl->c, rp+i) <= 0)
    200 				break;
    201 		rp[i] = L'\0';
    202 		sendp(w->ck, rp);
    203 	}
    204 }
    205 
    206 void
    207 resizethread(void *v)
    208 {
    209 	Point p;
    210 
    211 	USED(v);
    212 
    213 	for(;;){
    214 		p = stringsize(display->defaultfont, "0");
    215 		if(p.x && p.y)
    216 			updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x,
    217 				Dx(screen->r), Dy(screen->r));
    218 		wresize(w, screen, 0);
    219 		flushimage(display, 1);
    220 		if(recv(mousectl->resizec, nil) != 1)
    221 			break;
    222 		if(getwindow(display, Refnone) < 0)
    223 			sysfatal("can't reattach to window");
    224 	}
    225 }
    226 
    227 void
    228 mousethread(void *v)
    229 {
    230 	int sending;
    231 	Mouse tmp;
    232 
    233 	USED(v);
    234 
    235 	sending = FALSE;
    236 	threadsetname("mousethread");
    237 	while(readmouse(mousectl) >= 0){
    238 		if(sending){
    239 		Send:
    240 			/* send to window */
    241 			if(mouse->buttons == 0)
    242 				sending = FALSE;
    243 			else
    244 				wsetcursor(w, 0);
    245 			tmp = mousectl->m;
    246 			send(w->mc.c, &tmp);
    247 			continue;
    248 		}
    249 		if((mouse->buttons&(1|8|16)) || ptinrect(mouse->xy, w->scrollr)){
    250 			sending = TRUE;
    251 			goto Send;
    252 		}else if(mouse->buttons&2)
    253 			button2menu(w);
    254 		else
    255 			bouncemouse(mouse);
    256 	}
    257 }
    258 
    259 void
    260 wborder(Window *w, int type)
    261 {
    262 }
    263 
    264 Window*
    265 wpointto(Point pt)
    266 {
    267 	return w;
    268 }
    269 
    270 Window*
    271 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
    272 {
    273 	Window *w;
    274 	Mousectl *mc;
    275 	Channel *cm, *ck, *cctl;
    276 
    277 	if(i == nil)
    278 		return nil;
    279 	cm = chancreate(sizeof(Mouse), 0);
    280 	ck = chancreate(sizeof(Rune*), 0);
    281 	cctl = chancreate(sizeof(Wctlmesg), 4);
    282 	if(cm==nil || ck==nil || cctl==nil)
    283 		error("new: channel alloc failed");
    284 	mc = emalloc(sizeof(Mousectl));
    285 	*mc = *mousectl;
    286 /*	mc->image = i; */
    287 	mc->c = cm;
    288 	w = wmk(i, mc, ck, cctl, scrollit);
    289 	free(mc);	/* wmk copies *mc */
    290 	window = erealloc(window, ++nwindow*sizeof(Window*));
    291 	window[nwindow-1] = w;
    292 	if(hideit){
    293 		hidden[nhidden++] = w;
    294 		w->screenr = ZR;
    295 	}
    296 	threadcreate(winctl, w, STACK);
    297 	if(!hideit)
    298 		wcurrent(w);
    299 	flushimage(display, 1);
    300 	wsetpid(w, pid, 1);
    301 	wsetname(w);
    302 	if(dir)
    303 		w->dir = estrdup(dir);
    304 	return w;
    305 }
    306 
    307 /*
    308  * Button 2 menu.  Extra entry for always cook
    309  */
    310 
    311 enum
    312 {
    313 	Cut,
    314 	Paste,
    315 	Snarf,
    316 	Plumb,
    317 	Look,
    318 	Send,
    319 	Scroll,
    320 	Cook
    321 };
    322 
    323 char		*menu2str[] = {
    324 	"cut",
    325 	"paste",
    326 	"snarf",
    327 	"plumb",
    328 	"look",
    329 	"send",
    330 	"cook",
    331 	"scroll",
    332 	nil
    333 };
    334 
    335 
    336 Menu menu2 =
    337 {
    338 	menu2str
    339 };
    340 
    341 Rune newline[] = { '\n' };
    342 
    343 void
    344 button2menu(Window *w)
    345 {
    346 	if(w->deleted)
    347 		return;
    348 	incref(&w->ref);
    349 	if(w->scrolling)
    350 		menu2str[Scroll] = "noscroll";
    351 	else
    352 		menu2str[Scroll] = "scroll";
    353 	if(cooked)
    354 		menu2str[Cook] = "nocook";
    355 	else
    356 		menu2str[Cook] = "cook";
    357 
    358 	switch(menuhit(2, mousectl, &menu2, wscreen)){
    359 	case Cut:
    360 		wsnarf(w);
    361 		wcut(w);
    362 		wscrdraw(w);
    363 		break;
    364 
    365 	case Snarf:
    366 		wsnarf(w);
    367 		break;
    368 
    369 	case Paste:
    370 		riogetsnarf();
    371 		wpaste(w);
    372 		wscrdraw(w);
    373 		break;
    374 
    375 	case Plumb:
    376 		wplumb(w);
    377 		break;
    378 
    379 	case Look:
    380 		wlook(w);
    381 		break;
    382 
    383 	case Send:
    384 		riogetsnarf();
    385 		wsnarf(w);
    386 		if(nsnarf == 0)
    387 			break;
    388 		if(w->rawing){
    389 			waddraw(w, snarf, nsnarf);
    390 			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
    391 				waddraw(w, newline, 1);
    392 		}else{
    393 			winsert(w, snarf, nsnarf, w->nr);
    394 			if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
    395 				winsert(w, newline, 1, w->nr);
    396 		}
    397 		wsetselect(w, w->nr, w->nr);
    398 		wshow(w, w->nr);
    399 		break;
    400 
    401 	case Scroll:
    402 		if(w->scrolling ^= 1)
    403 			wshow(w, w->nr);
    404 		break;
    405 	case Cook:
    406 		cooked ^= 1;
    407 		break;
    408 	}
    409 	wclose(w);
    410 	wsendctlmesg(w, Wakeup, ZR, nil);
    411 	flushimage(display, 1);
    412 }
    413 
    414 int
    415 rawon(void)
    416 {
    417 	return !cooked && !isecho(sfd);
    418 }
    419 
    420 /*
    421  * I/O with child rc.
    422  */
    423 
    424 int label(Rune*, int);
    425 
    426 void
    427 rcoutputproc(void *arg)
    428 {
    429 	int i, cnt, n, nb, nr;
    430 	static char data[9000];
    431 	Conswritemesg cwm;
    432 	Rune *r;
    433 	Stringpair pair;
    434 
    435 	i = 0;
    436 	cnt = 0;
    437 	for(;;){
    438 		/* XXX Let typing have a go -- maybe there's a rubout waiting. */
    439 		i = 1-i;
    440 		n = read(rcfd, data+cnt, sizeof data-cnt);
    441 		if(n <= 0){
    442 			if(n < 0)
    443 				fprint(2, "9term: rc read error: %r\n");
    444 			threadexitsall("eof on rc output");
    445 		}
    446 		n = echocancel(data+cnt, n);
    447 		if(n == 0)
    448 			continue;
    449 		cnt += n;
    450 		r = runemalloc(cnt);
    451 		cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
    452 		/* approach end of buffer */
    453 		while(fullrune(data+nb, cnt-nb)){
    454 			nb += chartorune(&r[nr], data+nb);
    455 			if(r[nr])
    456 				nr++;
    457 		}
    458 		if(nb < cnt)
    459 			memmove(data, data+nb, cnt-nb);
    460 		cnt -= nb;
    461 
    462 		nr = label(r, nr);
    463 		if(nr == 0)
    464 			continue;
    465 
    466 		recv(w->conswrite, &cwm);
    467 		pair.s = r;
    468 		pair.ns = nr;
    469 		send(cwm.cw, &pair);
    470 	}
    471 }
    472 
    473 void
    474 winterrupt(Window *w)
    475 {
    476 	char rubout[1];
    477 
    478 	USED(w);
    479 	rubout[0] = getintr(sfd);
    480 	write(rcfd, rubout, 1);
    481 }
    482 
    483 int
    484 intrc(void)
    485 {
    486 	return getintr(sfd);
    487 }
    488 
    489 /*
    490  * Process in-band messages about window title changes.
    491  * The messages are of the form:
    492  *
    493  *	\033];xxx\007
    494  *
    495  * where xxx is the new directory.  This format was chosen
    496  * because it changes the label on xterm windows.
    497  */
    498 int
    499 label(Rune *sr, int n)
    500 {
    501 	Rune *sl, *el, *er, *r;
    502 	char *p, *dir;
    503 
    504 	er = sr+n;
    505 	for(r=er-1; r>=sr; r--)
    506 		if(*r == '\007')
    507 			break;
    508 	if(r < sr)
    509 		return n;
    510 
    511 	el = r+1;
    512 	for(sl=el-3; sl>=sr; sl--)
    513 		if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
    514 			break;
    515 	if(sl < sr)
    516 		return n;
    517 
    518 	dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
    519 	if(dir){
    520 		if(strcmp(dir, "*9term-hold+") == 0) {
    521 			w->holding = 1;
    522 			wrepaint(w);
    523 			flushimage(display, 1);
    524 		} else {
    525 			drawsetlabel(dir);
    526 			free(w->dir);
    527 			w->dir = dir;
    528 		}
    529 	}
    530 
    531 	/* remove trailing /-sysname if present */
    532 	p = strrchr(dir, '/');
    533 	if(p && *(p+1) == '-'){
    534 		if(p == dir)
    535 			p++;
    536 		*p = 0;
    537 	}
    538 
    539 	runemove(sl, el, er-el);
    540 	n -= (el-sl);
    541 	return n;
    542 }
    543 
    544 void
    545 rcinputproc(void *arg)
    546 {
    547 	static char data[9000];
    548 	Consreadmesg crm;
    549 	Channel *c1, *c2;
    550 	Stringpair pair;
    551 
    552 	for(;;){
    553 		recv(w->consread, &crm);
    554 		c1 = crm.c1;
    555 		c2 = crm.c2;
    556 
    557 		pair.s = data;
    558 		pair.ns = sizeof data;
    559 		send(c1, &pair);
    560 		recv(c2, &pair);
    561 
    562 		if(isecho(sfd))
    563 			echoed(pair.s, pair.ns);
    564 		if(write(rcfd, pair.s, pair.ns) < 0)
    565 			threadexitsall(nil);
    566 	}
    567 }
    568 
    569 /*
    570  * Snarf buffer - rio uses runes internally
    571  */
    572 void
    573 rioputsnarf(void)
    574 {
    575 	char *s;
    576 
    577 	s = smprint("%.*S", nsnarf, snarf);
    578 	if(s){
    579 		putsnarf(s);
    580 		free(s);
    581 	}
    582 }
    583 
    584 void
    585 riogetsnarf(void)
    586 {
    587 	char *s;
    588 	int n, nb, nulls;
    589 
    590 	s = getsnarf();
    591 	if(s == nil)
    592 		return;
    593 	n = strlen(s)+1;
    594 	free(snarf);
    595 	snarf = runemalloc(n);
    596 	cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls);
    597 	free(s);
    598 }
    599 
    600 /*
    601  * Clumsy hack to make " and "" work.
    602  * Then again, what's not a clumsy hack here in Unix land?
    603  */
    604 
    605 char adir[100];
    606 char thesocket[100];
    607 int afd;
    608 
    609 void listenproc(void*);
    610 void textproc(void*);
    611 
    612 void
    613 removethesocket(void)
    614 {
    615 	if(thesocket[0])
    616 		if(remove(thesocket) < 0)
    617 			fprint(2, "remove %s: %r\n", thesocket);
    618 }
    619 
    620 void
    621 servedevtext(void)
    622 {
    623 	char buf[100];
    624 
    625 	snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());
    626 
    627 	if((afd = announce(buf, adir)) < 0){
    628 		putenv("text9term", "");
    629 		return;
    630 	}
    631 
    632 	putenv("text9term", buf);
    633 	proccreate(listenproc, nil, STACK);
    634 	strcpy(thesocket, buf+5);
    635 	atexit(removethesocket);
    636 }
    637 
    638 void
    639 listenproc(void *arg)
    640 {
    641 	int fd;
    642 	char dir[100];
    643 
    644 	threadsetname("listen %s", thesocket);
    645 	USED(arg);
    646 	for(;;){
    647 		fd = listen(adir, dir);
    648 		if(fd < 0){
    649 			close(afd);
    650 			return;
    651 		}
    652 		proccreate(textproc, (void*)(uintptr)fd, STACK);
    653 	}
    654 }
    655 
    656 void
    657 textproc(void *arg)
    658 {
    659 	int fd, i, x, n, end;
    660 	Rune r;
    661 	char buf[4096], *p, *ep;
    662 
    663 	threadsetname("textproc");
    664 	fd = (uintptr)arg;
    665 	p = buf;
    666 	ep = buf+sizeof buf;
    667 	if(w == nil){
    668 		close(fd);
    669 		return;
    670 	}
    671 	end = w->org+w->nr;	/* avoid possible output loop */
    672 	for(i=w->org;; i++){
    673 		if(i >= end || ep-p < UTFmax){
    674 			for(x=0; x<p-buf; x+=n)
    675 				if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
    676 					goto break2;
    677 
    678 			if(i >= end)
    679 				break;
    680 			p = buf;
    681 		}
    682 		if(i < w->org)
    683 			i = w->org;
    684 		r = w->r[i-w->org];
    685 		if(r < Runeself)
    686 			*p++ = r;
    687 		else
    688 			p += runetochar(p, &r);
    689 	}
    690 break2:
    691 	close(fd);
    692 }