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 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;
40 int cooked;
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 /* no-op */
71 break;
72 case 'c':
73 cooked = TRUE;
74 break;
75 case 'w': /* started from rio or 9wm */
76 use9wm = TRUE;
77 break;
78 case 'W':
79 winsize = EARGF(usage());
80 break;
81 }ARGEND
83 if(fontname)
84 putenv("font", fontname);
86 p = getenv("tabstop");
87 if(p == 0)
88 p = getenv("TABSTOP");
89 if(p && maxtab <= 0)
90 maxtab = strtoul(p, 0, 0);
91 if(maxtab <= 0)
92 maxtab = 4;
93 free(p);
95 startdir = ".";
97 if(initdraw(derror, fontname, "9term") < 0)
98 sysfatal("initdraw: %r");
100 notify(hangupnote);
101 noteenable("sys: child");
103 mousectl = initmouse(nil, screen);
104 if(mousectl == nil)
105 error("cannot find mouse");
106 keyboardctl = initkeyboard(nil);
107 if(keyboardctl == nil)
108 error("cannot find keyboard");
109 mouse = &mousectl->m;
111 winclosechan = chancreate(sizeof(Window*), 0);
112 deletechan = chancreate(sizeof(char*), 0);
114 timerinit();
115 servedevtext();
116 rcpid = rcstart(argc, argv, &rcfd, &sfd);
117 w = new(screen, FALSE, rcpid, ".", nil, nil);
119 threadcreate(keyboardthread, nil, STACK);
120 threadcreate(mousethread, nil, STACK);
121 threadcreate(resizethread, nil, STACK);
123 proccreate(rcoutputproc, nil, STACK);
124 proccreate(rcinputproc, nil, STACK);
127 void
128 derror(Display *d, char *errorstr)
130 USED(d);
131 error(errorstr);
134 void
135 hangupnote(void *a, char *msg)
137 if(getpid() != mainpid)
138 noted(NDFLT);
139 if(strcmp(msg, "hangup") == 0){
140 postnote(PNPROC, rcpid, "hangup");
141 noted(NDFLT);
143 if(strstr(msg, "child")){
144 char buf[128];
145 int n;
147 n = awaitnohang(buf, sizeof buf-1);
148 if(n > 0){
149 buf[n] = 0;
150 if(atoi(buf) == rcpid)
151 threadexitsall(0);
153 noted(NCONT);
155 noted(NDFLT);
158 void
159 keyboardthread(void *v)
161 Rune buf[2][20], *rp;
162 int i, n;
164 USED(v);
165 threadsetname("keyboardthread");
166 n = 0;
167 for(;;){
168 rp = buf[n];
169 n = 1-n;
170 recv(keyboardctl->c, rp);
171 for(i=1; i<nelem(buf[0])-1; i++)
172 if(nbrecv(keyboardctl->c, rp+i) <= 0)
173 break;
174 rp[i] = L'\0';
175 sendp(w->ck, rp);
179 void
180 resizethread(void *v)
182 Point p;
184 USED(v);
186 for(;;){
187 p = stringsize(display->defaultfont, "0");
188 if(p.x && p.y)
189 updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x,
190 Dx(screen->r), Dy(screen->r));
191 wresize(w, screen, 0);
192 flushimage(display, 1);
193 if(recv(mousectl->resizec, nil) != 1)
194 break;
195 if(getwindow(display, Refnone) < 0)
196 sysfatal("can't reattach to window");
200 void
201 mousethread(void *v)
203 int sending;
204 Mouse tmp;
206 USED(v);
208 sending = FALSE;
209 threadsetname("mousethread");
210 while(readmouse(mousectl) >= 0){
211 if(sending){
212 Send:
213 /* send to window */
214 if(mouse->buttons == 0)
215 sending = FALSE;
216 else
217 wsetcursor(w, 0);
218 tmp = mousectl->m;
219 send(w->mc.c, &tmp);
220 continue;
222 if((mouse->buttons&(1|8|16)) || ptinrect(mouse->xy, w->scrollr)){
223 sending = TRUE;
224 goto Send;
225 }else if(mouse->buttons&2)
226 button2menu(w);
227 else
228 bouncemouse(mouse);
232 void
233 wborder(Window *w, int type)
237 Window*
238 wpointto(Point pt)
240 return w;
243 Window*
244 new(Image *i, int hideit, int pid, char *dir, char *cmd, char **argv)
246 Window *w;
247 Mousectl *mc;
248 Channel *cm, *ck, *cctl;
250 if(i == nil)
251 return nil;
252 cm = chancreate(sizeof(Mouse), 0);
253 ck = chancreate(sizeof(Rune*), 0);
254 cctl = chancreate(sizeof(Wctlmesg), 4);
255 if(cm==nil || ck==nil || cctl==nil)
256 error("new: channel alloc failed");
257 mc = emalloc(sizeof(Mousectl));
258 *mc = *mousectl;
259 /* mc->image = i; */
260 mc->c = cm;
261 w = wmk(i, mc, ck, cctl);
262 free(mc); /* wmk copies *mc */
263 window = erealloc(window, ++nwindow*sizeof(Window*));
264 window[nwindow-1] = w;
265 if(hideit){
266 hidden[nhidden++] = w;
267 w->screenr = ZR;
269 threadcreate(winctl, w, STACK);
270 if(!hideit)
271 wcurrent(w);
272 flushimage(display, 1);
273 wsetpid(w, pid, 1);
274 wsetname(w);
275 if(dir)
276 w->dir = estrdup(dir);
277 return w;
280 /*
281 * Button 2 menu. Extra entry for always cook
282 */
284 enum
286 Cut,
287 Paste,
288 Snarf,
289 Plumb,
290 Send,
291 Cook
292 };
294 char *menu2str[] = {
295 "cut",
296 "paste",
297 "snarf",
298 "plumb",
299 "send",
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(cooked)
319 menu2str[Cook] = "nocook";
320 else
321 menu2str[Cook] = "cook";
323 switch(menuhit(2, mousectl, &menu2, wscreen)){
324 case Cut:
325 wsnarf(w);
326 wcut(w);
327 wscrdraw(w);
328 break;
330 case Snarf:
331 wsnarf(w);
332 break;
334 case Paste:
335 riogetsnarf();
336 wpaste(w);
337 wscrdraw(w);
338 break;
340 case Plumb:
341 wplumb(w);
342 break;
344 case Send:
345 riogetsnarf();
346 wsnarf(w);
347 if(nsnarf == 0)
348 break;
349 if(w->rawing){
350 waddraw(w, snarf, nsnarf);
351 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
352 waddraw(w, newline, 1);
353 }else{
354 winsert(w, snarf, nsnarf, w->nr);
355 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
356 winsert(w, newline, 1, w->nr);
358 wsetselect(w, w->nr, w->nr);
359 wshow(w, w->nr);
360 break;
362 case Cook:
363 cooked ^= 1;
364 break;
366 wclose(w);
367 wsendctlmesg(w, Wakeup, ZR, nil);
368 flushimage(display, 1);
371 int
372 rawon(void)
374 return !cooked && !isecho(sfd);
377 /*
378 * I/O with child rc.
379 */
381 int label(Rune*, int);
383 void
384 rcoutputproc(void *arg)
386 int i, cnt, n, nb, nr;
387 static char data[9000];
388 Conswritemesg cwm;
389 Rune *r;
390 Stringpair pair;
392 i = 0;
393 cnt = 0;
394 for(;;){
395 /* XXX Let typing have a go -- maybe there's a rubout waiting. */
396 i = 1-i;
397 n = read(rcfd, data+cnt, sizeof data-cnt);
398 if(n <= 0){
399 if(n < 0)
400 fprint(2, "9term: rc read error: %r\n");
401 threadexitsall("eof on rc output");
403 n = echocancel(data+cnt, n);
404 if(n == 0)
405 continue;
406 cnt += n;
407 r = runemalloc(cnt);
408 cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
409 /* approach end of buffer */
410 while(fullrune(data+nb, cnt-nb)){
411 nb += chartorune(&r[nr], data+nb);
412 if(r[nr])
413 nr++;
415 if(nb < cnt)
416 memmove(data, data+nb, cnt-nb);
417 cnt -= nb;
419 nr = label(r, nr);
420 if(nr == 0)
421 continue;
423 recv(w->conswrite, &cwm);
424 pair.s = r;
425 pair.ns = nr;
426 send(cwm.cw, &pair);
430 void
431 winterrupt(Window *w)
433 char rubout[1];
435 USED(w);
436 rubout[0] = getintr(sfd);
437 write(rcfd, rubout, 1);
440 /*
441 * Process in-band messages about window title changes.
442 * The messages are of the form:
444 * \033];xxx\007
446 * where xxx is the new directory. This format was chosen
447 * because it changes the label on xterm windows.
448 */
449 int
450 label(Rune *sr, int n)
452 Rune *sl, *el, *er, *r;
453 char *p, *dir;
455 er = sr+n;
456 for(r=er-1; r>=sr; r--)
457 if(*r == '\007')
458 break;
459 if(r < sr)
460 return n;
462 el = r+1;
463 for(sl=el-3; sl>=sr; sl--)
464 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
465 break;
466 if(sl < sr)
467 return n;
469 dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
470 if(dir){
471 drawsetlabel(dir);
472 free(w->dir);
473 w->dir = dir;
476 /* remove trailing /-sysname if present */
477 p = strrchr(dir, '/');
478 if(p && *(p+1) == '-'){
479 if(p == dir)
480 p++;
481 *p = 0;
484 runemove(sl, el, er-el);
485 n -= (el-sl);
486 return n;
489 void
490 rcinputproc(void *arg)
492 static char data[9000];
493 Consreadmesg crm;
494 Channel *c1, *c2;
495 Stringpair pair;
497 for(;;){
498 recv(w->consread, &crm);
499 c1 = crm.c1;
500 c2 = crm.c2;
502 pair.s = data;
503 pair.ns = sizeof data;
504 send(c1, &pair);
505 recv(c2, &pair);
507 if(isecho(sfd))
508 echoed(pair.s, pair.ns);
509 if(write(rcfd, pair.s, pair.ns) < 0)
510 threadexitsall(nil);
514 /*
515 * Snarf buffer - rio uses runes internally
516 */
517 void
518 rioputsnarf(void)
520 char *s;
522 s = smprint("%.*S", nsnarf, snarf);
523 if(s){
524 putsnarf(s);
525 free(s);
529 void
530 riogetsnarf(void)
532 char *s;
533 int n, nb, nulls;
535 s = getsnarf();
536 if(s == nil)
537 return;
538 n = strlen(s)+1;
539 free(snarf);
540 snarf = runemalloc(n);
541 cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls);
542 free(s);
545 /*
546 * Clumsy hack to make " and "" work.
547 * Then again, what's not a clumsy hack here in Unix land?
548 */
550 char adir[100];
551 char thesocket[100];
552 int afd;
554 void listenproc(void*);
555 void textproc(void*);
557 void
558 removethesocket(void)
560 if(thesocket[0])
561 if(remove(thesocket) < 0)
562 fprint(2, "remove %s: %r\n", thesocket);
565 void
566 servedevtext(void)
568 char buf[100];
570 snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());
572 if((afd = announce(buf, adir)) < 0){
573 putenv("text9term", "");
574 return;
577 putenv("text9term", buf);
578 proccreate(listenproc, nil, STACK);
579 strcpy(thesocket, buf+5);
580 atexit(removethesocket);
583 void
584 listenproc(void *arg)
586 int fd;
587 char dir[100];
589 threadsetname("listen %s", thesocket);
590 USED(arg);
591 for(;;){
592 fd = listen(adir, dir);
593 if(fd < 0){
594 close(afd);
595 return;
597 proccreate(textproc, (void*)(uintptr)fd, STACK);
601 void
602 textproc(void *arg)
604 int fd, i, x, n, end;
605 Rune r;
606 char buf[4096], *p, *ep;
608 threadsetname("textproc");
609 fd = (uintptr)arg;
610 p = buf;
611 ep = buf+sizeof buf;
612 if(w == nil){
613 close(fd);
614 return;
616 end = w->org+w->nr; /* avoid possible output loop */
617 for(i=w->org;; i++){
618 if(i >= end || ep-p < UTFmax){
619 for(x=0; x<p-buf; x+=n)
620 if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
621 goto break2;
623 if(i >= end)
624 break;
625 p = buf;
627 if(i < w->org)
628 i = w->org;
629 r = w->r[i-w->org];
630 if(r < Runeself)
631 *p++ = r;
632 else
633 p += runetochar(p, &r);
635 break2:
636 close(fd);