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 mousepid;
21 int plumbfd;
22 int rcpid;
23 int rcfd;
24 int sfd;
25 int noecho;
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;
42 void
43 usage(void)
44 {
45 fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n");
46 threadexitsall("usage");
47 }
49 void
50 threadmain(int argc, char *argv[])
51 {
52 char *p;
54 rfork(RFNOTEG);
55 font = nil;
56 _wantfocuschanges = 1;
57 mainpid = getpid();
58 messagesize = 8192;
60 ARGBEGIN{
61 default:
62 usage();
63 case 'l':
64 loginshell = TRUE;
65 break;
66 case 'f':
67 fontname = EARGF(usage());
68 break;
69 case 's':
70 scrolling = TRUE;
71 break;
72 case 'w': /* started from rio or 9wm */
73 use9wm = TRUE;
74 break;
75 case 'W':
76 winsize = EARGF(usage());
77 break;
78 }ARGEND
80 if(fontname)
81 putenv("font", fontname);
83 p = getenv("tabstop");
84 if(p == 0)
85 p = getenv("TABSTOP");
86 if(p && maxtab <= 0)
87 maxtab = strtoul(p, 0, 0);
88 if(maxtab <= 0)
89 maxtab = 4;
90 free(p);
92 startdir = ".";
94 if(initdraw(derror, fontname, "9term") < 0)
95 sysfatal("initdraw: %r");
97 notify(hangupnote);
98 noteenable("sys: child");
100 mousectl = initmouse(nil, screen);
101 if(mousectl == nil)
102 error("cannot find mouse");
103 keyboardctl = initkeyboard(nil);
104 if(keyboardctl == nil)
105 error("cannot find keyboard");
106 mouse = &mousectl->m;
108 winclosechan = chancreate(sizeof(Window*), 0);
109 deletechan = chancreate(sizeof(char*), 0);
111 timerinit();
112 servedevtext();
113 rcpid = rcstart(argc, argv, &rcfd, &sfd);
114 w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil);
116 threadcreate(keyboardthread, nil, STACK);
117 threadcreate(mousethread, nil, STACK);
118 threadcreate(resizethread, nil, STACK);
120 proccreate(rcoutputproc, nil, STACK);
121 proccreate(rcinputproc, nil, STACK);
124 void
125 derror(Display *d, char *errorstr)
127 USED(d);
128 error(errorstr);
131 void
132 hangupnote(void *a, char *msg)
134 if(getpid() != mainpid)
135 noted(NDFLT);
136 if(strcmp(msg, "hangup") == 0){
137 postnote(PNPROC, rcpid, "hangup");
138 noted(NDFLT);
140 if(strstr(msg, "child")){
141 char buf[128];
142 int n;
144 n = awaitnohang(buf, sizeof buf-1);
145 if(n > 0){
146 buf[n] = 0;
147 if(atoi(buf) == rcpid)
148 threadexitsall(0);
150 noted(NCONT);
152 noted(NDFLT);
155 void
156 keyboardthread(void *v)
158 Rune buf[2][20], *rp;
159 int i, n;
161 USED(v);
162 threadsetname("keyboardthread");
163 n = 0;
164 for(;;){
165 rp = buf[n];
166 n = 1-n;
167 recv(keyboardctl->c, rp);
168 for(i=1; i<nelem(buf[0])-1; i++)
169 if(nbrecv(keyboardctl->c, rp+i) <= 0)
170 break;
171 rp[i] = L'\0';
172 sendp(w->ck, rp);
176 void
177 resizethread(void *v)
179 Point p;
181 USED(v);
183 for(;;){
184 p = stringsize(display->defaultfont, "0");
185 if(p.x && p.y)
186 updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x,
187 Dx(screen->r), Dy(screen->r));
188 wresize(w, screen, 0);
189 flushimage(display, 1);
190 if(recv(mousectl->resizec, nil) != 1)
191 break;
192 if(getwindow(display, Refnone) < 0)
193 sysfatal("can't reattach to window");
197 void
198 mousethread(void *v)
200 int sending;
201 Mouse tmp;
203 USED(v);
205 sending = FALSE;
206 threadsetname("mousethread");
207 while(readmouse(mousectl) >= 0){
208 if(sending){
209 Send:
210 /* send to window */
211 if(mouse->buttons == 0)
212 sending = FALSE;
213 else
214 wsetcursor(w, 0);
215 tmp = mousectl->m;
216 send(w->mc.c, &tmp);
217 continue;
219 if((mouse->buttons&1) || ptinrect(mouse->xy, w->scrollr)){
220 sending = TRUE;
221 goto Send;
222 }else if(mouse->buttons&2)
223 button2menu(w);
224 else
225 bouncemouse(mouse);
229 void
230 wborder(Window *w, int type)
234 Window*
235 wpointto(Point pt)
237 return w;
240 Window*
241 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
243 Window *w;
244 Mousectl *mc;
245 Channel *cm, *ck, *cctl;
247 if(i == nil)
248 return nil;
249 cm = chancreate(sizeof(Mouse), 0);
250 ck = chancreate(sizeof(Rune*), 0);
251 cctl = chancreate(sizeof(Wctlmesg), 4);
252 if(cm==nil || ck==nil || cctl==nil)
253 error("new: channel alloc failed");
254 mc = emalloc(sizeof(Mousectl));
255 *mc = *mousectl;
256 /* mc->image = i; */
257 mc->c = cm;
258 w = wmk(i, mc, ck, cctl, scrollit);
259 free(mc); /* wmk copies *mc */
260 window = erealloc(window, ++nwindow*sizeof(Window*));
261 window[nwindow-1] = w;
262 if(hideit){
263 hidden[nhidden++] = w;
264 w->screenr = ZR;
266 threadcreate(winctl, w, STACK);
267 if(!hideit)
268 wcurrent(w);
269 flushimage(display, 1);
270 wsetpid(w, pid, 1);
271 wsetname(w);
272 if(dir)
273 w->dir = estrdup(dir);
274 return w;
277 /*
278 * Button 2 menu. Extra entry for always cook
279 */
280 int cooked;
282 enum
284 Cut,
285 Paste,
286 Snarf,
287 Plumb,
288 Send,
289 Scroll,
290 Cook
291 };
293 char *menu2str[] = {
294 "cut",
295 "paste",
296 "snarf",
297 "plumb",
298 "send",
299 "scroll",
300 "cook",
301 nil
302 };
305 Menu menu2 =
307 menu2str
308 };
310 Rune newline[] = { '\n' };
312 void
313 button2menu(Window *w)
315 if(w->deleted)
316 return;
317 incref(&w->ref);
318 if(w->scrolling)
319 menu2str[Scroll] = "noscroll";
320 else
321 menu2str[Scroll] = "scroll";
322 if(cooked)
323 menu2str[Cook] = "nocook";
324 else
325 menu2str[Cook] = "cook";
327 switch(menuhit(2, mousectl, &menu2, wscreen)){
328 case Cut:
329 wsnarf(w);
330 wcut(w);
331 wscrdraw(w);
332 break;
334 case Snarf:
335 wsnarf(w);
336 break;
338 case Paste:
339 riogetsnarf();
340 wpaste(w);
341 wscrdraw(w);
342 break;
344 case Plumb:
345 wplumb(w);
346 break;
348 case Send:
349 riogetsnarf();
350 wsnarf(w);
351 if(nsnarf == 0)
352 break;
353 if(w->rawing){
354 waddraw(w, snarf, nsnarf);
355 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
356 waddraw(w, newline, 1);
357 }else{
358 winsert(w, snarf, nsnarf, w->nr);
359 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
360 winsert(w, newline, 1, w->nr);
362 wsetselect(w, w->nr, w->nr);
363 wshow(w, w->nr);
364 break;
366 case Scroll:
367 if(w->scrolling ^= 1)
368 wshow(w, w->nr);
369 break;
371 case Cook:
372 cooked ^= 1;
373 break;
375 wclose(w);
376 wsendctlmesg(w, Wakeup, ZR, nil);
377 flushimage(display, 1);
380 int
381 rawon(void)
383 return !cooked && !isecho(sfd);
386 /*
387 * I/O with child rc.
388 */
390 int label(Rune*, int);
392 void
393 rcoutputproc(void *arg)
395 int i, cnt, n, nb, nr;
396 static char data[9000];
397 Conswritemesg cwm;
398 Rune *r;
399 Stringpair pair;
401 i = 0;
402 cnt = 0;
403 for(;;){
404 /* XXX Let typing have a go -- maybe there's a rubout waiting. */
405 i = 1-i;
406 n = read(rcfd, data+cnt, sizeof data-cnt);
407 if(n <= 0){
408 if(n < 0)
409 fprint(2, "9term: rc read error: %r\n");
410 threadexitsall("eof on rc output");
412 cnt += n;
413 r = runemalloc(cnt);
414 cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
415 /* approach end of buffer */
416 while(fullrune(data+nb, cnt-nb)){
417 nb += chartorune(&r[nr], data+nb);
418 if(r[nr])
419 nr++;
421 if(nb < cnt)
422 memmove(data, data+nb, cnt-nb);
423 cnt -= nb;
425 nr = label(r, nr);
426 if(nr == 0)
427 continue;
429 recv(w->conswrite, &cwm);
430 pair.s = r;
431 pair.ns = nr;
432 send(cwm.cw, &pair);
436 void
437 winterrupt(Window *w)
439 char rubout[1];
441 USED(w);
442 rubout[0] = getintr(sfd);
443 write(rcfd, rubout, 1);
446 /*
447 * Process in-band messages about window title changes.
448 * The messages are of the form:
450 * \033];xxx\007
452 * where xxx is the new directory. This format was chosen
453 * because it changes the label on xterm windows.
454 */
455 int
456 label(Rune *sr, int n)
458 Rune *sl, *el, *er, *r;
459 char *p, *dir;
461 er = sr+n;
462 for(r=er-1; r>=sr; r--)
463 if(*r == '\007')
464 break;
465 if(r < sr)
466 return n;
468 el = r+1;
469 for(sl=el-3; sl>=sr; sl--)
470 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
471 break;
472 if(sl < sr)
473 return n;
475 dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
476 if(dir){
477 drawsetlabel(dir);
478 free(w->dir);
479 w->dir = dir;
482 /* remove trailing /-sysname if present */
483 p = strrchr(dir, '/');
484 if(p && *(p+1) == '-'){
485 if(p == dir)
486 p++;
487 *p = 0;
490 runemove(sl, el, er-el);
491 n -= (el-sl);
492 return n;
495 void
496 rcinputproc(void *arg)
498 static char data[9000];
499 int s;
500 Consreadmesg crm;
501 Channel *c1, *c2;
502 Stringpair pair;
504 for(;;){
505 recv(w->consread, &crm);
506 c1 = crm.c1;
507 c2 = crm.c2;
509 pair.s = data;
510 pair.ns = sizeof data;
511 send(c1, &pair);
512 recv(c2, &pair);
514 s = setecho(sfd, 0);
515 if(write(rcfd, pair.s, pair.ns) < 0)
516 threadexitsall(nil);
517 if(s)
518 setecho(sfd, s);
522 /*
523 * Snarf buffer - rio uses runes internally
524 */
525 void
526 rioputsnarf(void)
528 char *s;
530 s = smprint("%.*S", nsnarf, snarf);
531 if(s){
532 putsnarf(s);
533 free(s);
537 void
538 riogetsnarf(void)
540 char *s;
541 int n, nb, nulls;
543 s = getsnarf();
544 if(s == nil)
545 return;
546 n = strlen(s)+1;
547 free(snarf);
548 snarf = runemalloc(n);
549 cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls);
550 free(s);
553 /*
554 * Clumsy hack to make " and "" work.
555 * Then again, what's not a clumsy hack here in Unix land?
556 */
558 char adir[100];
559 char thesocket[100];
560 int afd;
562 void listenproc(void*);
563 void textproc(void*);
565 void
566 removethesocket(void)
568 if(thesocket[0])
569 if(remove(thesocket) < 0)
570 fprint(2, "remove %s: %r\n", thesocket);
573 void
574 servedevtext(void)
576 char buf[100];
578 snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());
580 if((afd = announce(buf, adir)) < 0){
581 putenv("text9term", "");
582 return;
585 putenv("text9term", buf);
586 proccreate(listenproc, nil, STACK);
587 strcpy(thesocket, buf+5);
588 atexit(removethesocket);
591 void
592 listenproc(void *arg)
594 int fd;
595 char dir[100];
597 threadsetname("listen %s", thesocket);
598 USED(arg);
599 for(;;){
600 fd = listen(adir, dir);
601 if(fd < 0){
602 close(afd);
603 return;
605 proccreate(textproc, (void*)(uintptr)fd, STACK);
609 void
610 textproc(void *arg)
612 int fd, i, x, n, end;
613 Rune r;
614 char buf[4096], *p, *ep;
616 threadsetname("textproc");
617 fd = (uintptr)arg;
618 p = buf;
619 ep = buf+sizeof buf;
620 if(w == nil){
621 close(fd);
622 return;
624 end = w->org+w->nr; /* avoid possible output loop */
625 for(i=w->org;; i++){
626 if(i >= end || ep-p < UTFmax){
627 for(x=0; x<p-buf; x+=n)
628 if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
629 goto break2;
631 if(i >= end)
632 break;
633 p = buf;
635 if(i < w->org)
636 i = w->org;
637 r = w->r[i-w->org];
638 if(r < Runeself)
639 *p++ = r;
640 else
641 p += runetochar(p, &r);
643 break2:
644 close(fd);