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