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