Blob


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