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 'f':
63 fontname = EARGF(usage());
64 break;
65 case 's':
66 scrolling = TRUE;
67 break;
68 case 'w': /* started from rio or 9wm */
69 use9wm = TRUE;
70 break;
71 case 'W':
72 winsize = EARGF(usage());
73 break;
74 }ARGEND
76 if(fontname)
77 putenv("font", fontname);
79 p = getenv("tabstop");
80 if(p == 0)
81 p = getenv("TABSTOP");
82 if(p && maxtab <= 0)
83 maxtab = strtoul(p, 0, 0);
84 if(maxtab <= 0)
85 maxtab = 4;
86 free(p);
88 startdir = ".";
90 initdraw(derror, fontname, "9term");
91 notify(hangupnote);
92 noteenable("sys: child");
94 mousectl = initmouse(nil, screen);
95 if(mousectl == nil)
96 error("cannot find mouse");
97 keyboardctl = initkeyboard(nil);
98 if(keyboardctl == nil)
99 error("cannot find keyboard");
100 mouse = &mousectl->m;
102 winclosechan = chancreate(sizeof(Window*), 0);
103 deletechan = chancreate(sizeof(char*), 0);
105 timerinit();
106 servedevtext();
107 rcpid = rcstart(argc, argv, &rcfd, &sfd);
108 w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil);
110 threadcreate(keyboardthread, nil, STACK);
111 threadcreate(mousethread, nil, STACK);
112 threadcreate(resizethread, nil, STACK);
114 proccreate(rcoutputproc, nil, STACK);
115 proccreate(rcinputproc, nil, STACK);
118 void
119 derror(Display *d, char *errorstr)
121 USED(d);
122 error(errorstr);
125 void
126 hangupnote(void *a, char *msg)
128 if(getpid() != mainpid)
129 noted(NDFLT);
130 if(strcmp(msg, "hangup") == 0 && rcpid != 0){
131 postnote(PNGROUP, rcpid, "hangup");
132 noted(NDFLT);
134 if(strstr(msg, "child")){
135 char buf[128];
136 int n;
138 n = awaitnohang(buf, sizeof buf-1);
139 if(n > 0){
140 buf[n] = 0;
141 if(atoi(buf) == rcpid)
142 threadexitsall(0);
144 noted(NCONT);
146 noted(NDFLT);
149 void
150 keyboardthread(void *v)
152 Rune buf[2][20], *rp;
153 int i, n;
155 USED(v);
156 threadsetname("keyboardthread");
157 n = 0;
158 for(;;){
159 rp = buf[n];
160 n = 1-n;
161 recv(keyboardctl->c, rp);
162 for(i=1; i<nelem(buf[0])-1; i++)
163 if(nbrecv(keyboardctl->c, rp+i) <= 0)
164 break;
165 rp[i] = L'\0';
166 sendp(w->ck, rp);
170 void
171 resizethread(void *v)
173 Point p;
175 USED(v);
177 while(recv(mousectl->resizec, nil) == 1){
178 if(getwindow(display, Refnone) < 0)
179 sysfatal("can't reattach to window");
180 p = stringsize(display->defaultfont, "0");
181 if(p.x && p.y)
182 updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x,
183 Dx(screen->r), Dy(screen->r));
184 wresize(w, screen, 0);
185 flushimage(display, 1);
189 void
190 mousethread(void *v)
192 int sending;
193 Mouse tmp;
195 USED(v);
197 sending = FALSE;
198 threadsetname("mousethread");
199 while(readmouse(mousectl) >= 0){
200 if(sending){
201 Send:
202 /* send to window */
203 if(mouse->buttons == 0)
204 sending = FALSE;
205 else
206 wsetcursor(w, 0);
207 tmp = mousectl->m;
208 send(w->mc.c, &tmp);
209 continue;
211 if((mouse->buttons&1) || ptinrect(mouse->xy, w->scrollr)){
212 sending = TRUE;
213 goto Send;
214 }else if(mouse->buttons&2)
215 button2menu(w);
216 else
217 bouncemouse(mouse);
221 void
222 wborder(Window *w, int type)
226 Window*
227 wpointto(Point pt)
229 return w;
232 Window*
233 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
235 Window *w;
236 Mousectl *mc;
237 Channel *cm, *ck, *cctl;
239 if(i == nil)
240 return nil;
241 cm = chancreate(sizeof(Mouse), 0);
242 ck = chancreate(sizeof(Rune*), 0);
243 cctl = chancreate(sizeof(Wctlmesg), 4);
244 if(cm==nil || ck==nil || cctl==nil)
245 error("new: channel alloc failed");
246 mc = emalloc(sizeof(Mousectl));
247 *mc = *mousectl;
248 // mc->image = i;
249 mc->c = cm;
250 w = wmk(i, mc, ck, cctl, scrollit);
251 free(mc); /* wmk copies *mc */
252 window = erealloc(window, ++nwindow*sizeof(Window*));
253 window[nwindow-1] = w;
254 if(hideit){
255 hidden[nhidden++] = w;
256 w->screenr = ZR;
258 threadcreate(winctl, w, 8192);
259 if(!hideit)
260 wcurrent(w);
261 flushimage(display, 1);
262 wsetpid(w, pid, 1);
263 wsetname(w);
264 if(dir)
265 w->dir = estrdup(dir);
266 return w;
269 /*
270 * Button 2 menu. Extra entry for always cook
271 */
272 int cooked;
274 enum
276 Cut,
277 Paste,
278 Snarf,
279 Plumb,
280 Send,
281 Scroll,
282 Cook,
283 };
285 char *menu2str[] = {
286 "cut",
287 "paste",
288 "snarf",
289 "plumb",
290 "send",
291 "scroll",
292 "cook",
293 nil
294 };
297 Menu menu2 =
299 menu2str
300 };
302 Rune newline[] = { '\n' };
304 void
305 button2menu(Window *w)
307 if(w->deleted)
308 return;
309 incref(&w->ref);
310 if(w->scrolling)
311 menu2str[Scroll] = "noscroll";
312 else
313 menu2str[Scroll] = "scroll";
314 if(cooked)
315 menu2str[Cook] = "nocook";
316 else
317 menu2str[Cook] = "cook";
319 switch(menuhit(2, mousectl, &menu2, wscreen)){
320 case Cut:
321 wsnarf(w);
322 wcut(w);
323 wscrdraw(w);
324 break;
326 case Snarf:
327 wsnarf(w);
328 break;
330 case Paste:
331 riogetsnarf();
332 wpaste(w);
333 wscrdraw(w);
334 break;
336 case Plumb:
337 wplumb(w);
338 break;
340 case Send:
341 riogetsnarf();
342 wsnarf(w);
343 if(nsnarf == 0)
344 break;
345 if(w->rawing){
346 waddraw(w, snarf, nsnarf);
347 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
348 waddraw(w, newline, 1);
349 }else{
350 winsert(w, snarf, nsnarf, w->nr);
351 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
352 winsert(w, newline, 1, w->nr);
354 wsetselect(w, w->nr, w->nr);
355 wshow(w, w->nr);
356 break;
358 case Scroll:
359 if(w->scrolling ^= 1)
360 wshow(w, w->nr);
361 break;
363 case Cook:
364 cooked ^= 1;
365 break;
367 wclose(w);
368 wsendctlmesg(w, Wakeup, ZR, nil);
369 flushimage(display, 1);
372 int
373 rawon(void)
375 return !cooked && !isecho(sfd);
378 /*
379 * I/O with child rc.
380 */
382 int label(Rune*, int);
384 void
385 rcoutputproc(void *arg)
387 int i, cnt, n, nb, nr;
388 static char data[9000];
389 Conswritemesg cwm;
390 Rune *r;
391 Stringpair pair;
393 i = 0;
394 cnt = 0;
395 for(;;){
396 /* XXX Let typing have a go -- maybe there's a rubout waiting. */
397 i = 1-i;
398 n = read(rcfd, data+cnt, sizeof data-cnt);
399 if(n <= 0){
400 if(n < 0)
401 fprint(2, "9term: rc read error: %r\n");
402 threadexitsall("eof on rc output");
404 cnt += n;
405 r = runemalloc(cnt);
406 cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
407 /* approach end of buffer */
408 while(fullrune(data+nb, cnt-nb)){
409 nb += chartorune(&r[nr], data+nb);
410 if(r[nr])
411 nr++;
413 if(nb < cnt)
414 memmove(data, data+nb, cnt-nb);
415 cnt -= nb;
417 nr = label(r, nr);
418 if(nr == 0)
419 continue;
421 recv(w->conswrite, &cwm);
422 pair.s = r;
423 pair.ns = nr;
424 send(cwm.cw, &pair);
428 void
429 winterrupt(Window *w)
431 char rubout[1];
433 USED(w);
434 rubout[0] = getintr(sfd);
435 write(rcfd, rubout, 1);
438 /*
439 * Process in-band messages about window title changes.
440 * The messages are of the form:
442 * \033];xxx\007
444 * where xxx is the new directory. This format was chosen
445 * because it changes the label on xterm windows.
446 */
447 int
448 label(Rune *sr, int n)
450 Rune *sl, *el, *er, *r;
451 char *p, *dir;
453 er = sr+n;
454 for(r=er-1; r>=sr; r--)
455 if(*r == '\007')
456 break;
457 if(r < sr)
458 return n;
460 el = r+1;
461 for(sl=el-3; sl>=sr; sl--)
462 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
463 break;
464 if(sl < sr)
465 return n;
467 dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
468 if(dir){
469 drawsetlabel(dir);
470 free(w->dir);
471 w->dir = dir;
474 /* remove trailing /-sysname if present */
475 p = strrchr(dir, '/');
476 if(p && *(p+1) == '-'){
477 if(p == dir)
478 p++;
479 *p = 0;
482 runemove(sl, el, er-el);
483 n -= (el-sl);
484 return n;
487 void
488 rcinputproc(void *arg)
490 static char data[9000];
491 int s;
492 Consreadmesg crm;
493 Channel *c1, *c2;
494 Stringpair pair;
496 for(;;){
497 recv(w->consread, &crm);
498 c1 = crm.c1;
499 c2 = crm.c2;
501 pair.s = data;
502 pair.ns = sizeof data;
503 send(c1, &pair);
504 recv(c2, &pair);
506 s = setecho(sfd, 0);
507 if(write(rcfd, pair.s, pair.ns) < 0)
508 threadexitsall(nil);
509 if(s)
510 setecho(sfd, s);
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*)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 = (int)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);