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