t9term.c - plan9port - [fork] Plan 9 from user space | |
git clone git://src.adamsgaard.dk/plan9port | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
t9term.c (10642B) | |
--- | |
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" | |
17 | |
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; | |
28 | |
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); | |
39 | |
40 int errorshouldabort = 0; | |
41 int cooked; | |
42 | |
43 void | |
44 usage(void) | |
45 { | |
46 fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n"… | |
47 threadexitsall("usage"); | |
48 } | |
49 | |
50 int | |
51 threadmaybackground(void) | |
52 { | |
53 return 1; | |
54 } | |
55 | |
56 void | |
57 threadmain(int argc, char *argv[]) | |
58 { | |
59 char *p; | |
60 | |
61 rfork(RFNOTEG); | |
62 font = nil; | |
63 _wantfocuschanges = 1; | |
64 mainpid = getpid(); | |
65 messagesize = 8192; | |
66 | |
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 | |
89 | |
90 if(fontname) | |
91 putenv("font", fontname); | |
92 | |
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); | |
101 | |
102 startdir = "."; | |
103 | |
104 if(initdraw(derror, fontname, "9term") < 0) | |
105 sysfatal("initdraw: %r"); | |
106 | |
107 notify(hangupnote); | |
108 noteenable("sys: child"); | |
109 | |
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; | |
117 | |
118 winclosechan = chancreate(sizeof(Window*), 0); | |
119 deletechan = chancreate(sizeof(char*), 0); | |
120 | |
121 timerinit(); | |
122 servedevtext(); | |
123 rcpid = rcstart(argc, argv, &rcfd, &sfd); | |
124 w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil); | |
125 | |
126 threadcreate(keyboardthread, nil, STACK); | |
127 threadcreate(mousethread, nil, STACK); | |
128 threadcreate(resizethread, nil, STACK); | |
129 | |
130 proccreate(rcoutputproc, nil, STACK); | |
131 proccreate(rcinputproc, nil, STACK); | |
132 } | |
133 | |
134 void | |
135 derror(Display *d, char *errorstr) | |
136 { | |
137 USED(d); | |
138 error(errorstr); | |
139 } | |
140 | |
141 void | |
142 hangupnote(void *a, char *msg) | |
143 { | |
144 if(getpid() != mainpid) | |
145 noted(NDFLT); | |
146 if(strcmp(msg, "hangup") == 0){ | |
147 postnote(PNPROC, rcpid, "hangup"); | |
148 noted(NDFLT); | |
149 } | |
150 if(strstr(msg, "child")){ | |
151 char buf[128]; | |
152 int n; | |
153 | |
154 n = awaitnohang(buf, sizeof buf-1); | |
155 if(n > 0){ | |
156 buf[n] = 0; | |
157 if(atoi(buf) == rcpid) | |
158 threadexitsall(0); | |
159 } | |
160 noted(NCONT); | |
161 } | |
162 noted(NDFLT); | |
163 } | |
164 | |
165 void | |
166 keyboardthread(void *v) | |
167 { | |
168 Rune buf[2][20], *rp; | |
169 int i, n; | |
170 | |
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); | |
183 } | |
184 } | |
185 | |
186 void | |
187 resizethread(void *v) | |
188 { | |
189 Point p; | |
190 | |
191 USED(v); | |
192 | |
193 for(;;){ | |
194 p = stringsize(display->defaultfont, "0"); | |
195 if(p.x && p.y) | |
196 updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-… | |
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"); | |
204 } | |
205 } | |
206 | |
207 void | |
208 mousethread(void *v) | |
209 { | |
210 int sending; | |
211 Mouse tmp; | |
212 | |
213 USED(v); | |
214 | |
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; | |
228 } | |
229 if((mouse->buttons&(1|8|16)) || ptinrect(mouse->xy, w->s… | |
230 sending = TRUE; | |
231 goto Send; | |
232 }else if(mouse->buttons&2) | |
233 button2menu(w); | |
234 else | |
235 bouncemouse(mouse); | |
236 } | |
237 } | |
238 | |
239 void | |
240 wborder(Window *w, int type) | |
241 { | |
242 } | |
243 | |
244 Window* | |
245 wpointto(Point pt) | |
246 { | |
247 return w; | |
248 } | |
249 | |
250 Window* | |
251 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, c… | |
252 { | |
253 Window *w; | |
254 Mousectl *mc; | |
255 Channel *cm, *ck, *cctl; | |
256 | |
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; | |
275 } | |
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; | |
285 } | |
286 | |
287 /* | |
288 * Button 2 menu. Extra entry for always cook | |
289 */ | |
290 | |
291 enum | |
292 { | |
293 Cut, | |
294 Paste, | |
295 Snarf, | |
296 Plumb, | |
297 Look, | |
298 Send, | |
299 Scroll, | |
300 Cook | |
301 }; | |
302 | |
303 char *menu2str[] = { | |
304 "cut", | |
305 "paste", | |
306 "snarf", | |
307 "plumb", | |
308 "look", | |
309 "send", | |
310 "cook", | |
311 "scroll", | |
312 nil | |
313 }; | |
314 | |
315 | |
316 Menu menu2 = | |
317 { | |
318 menu2str | |
319 }; | |
320 | |
321 Rune newline[] = { '\n' }; | |
322 | |
323 void | |
324 button2menu(Window *w) | |
325 { | |
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"; | |
337 | |
338 switch(menuhit(2, mousectl, &menu2, wscreen)){ | |
339 case Cut: | |
340 wsnarf(w); | |
341 wcut(w); | |
342 wscrdraw(w); | |
343 break; | |
344 | |
345 case Snarf: | |
346 wsnarf(w); | |
347 break; | |
348 | |
349 case Paste: | |
350 riogetsnarf(); | |
351 wpaste(w); | |
352 wscrdraw(w); | |
353 break; | |
354 | |
355 case Plumb: | |
356 wplumb(w); | |
357 break; | |
358 | |
359 case Look: | |
360 wlook(w); | |
361 break; | |
362 | |
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]!='\0… | |
371 waddraw(w, newline, 1); | |
372 }else{ | |
373 winsert(w, snarf, nsnarf, w->nr); | |
374 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\0… | |
375 winsert(w, newline, 1, w->nr); | |
376 } | |
377 wsetselect(w, w->nr, w->nr); | |
378 wshow(w, w->nr); | |
379 break; | |
380 | |
381 case Scroll: | |
382 if(w->scrolling ^= 1) | |
383 wshow(w, w->nr); | |
384 break; | |
385 case Cook: | |
386 cooked ^= 1; | |
387 break; | |
388 } | |
389 wclose(w); | |
390 wsendctlmesg(w, Wakeup, ZR, nil); | |
391 flushimage(display, 1); | |
392 } | |
393 | |
394 int | |
395 rawon(void) | |
396 { | |
397 return !cooked && !isecho(sfd); | |
398 } | |
399 | |
400 /* | |
401 * I/O with child rc. | |
402 */ | |
403 | |
404 int label(Rune*, int); | |
405 | |
406 void | |
407 rcoutputproc(void *arg) | |
408 { | |
409 int i, cnt, n, nb, nr; | |
410 static char data[9000]; | |
411 Conswritemesg cwm; | |
412 Rune *r; | |
413 Stringpair pair; | |
414 | |
415 i = 0; | |
416 cnt = 0; | |
417 for(;;){ | |
418 /* XXX Let typing have a go -- maybe there's a rubout wa… | |
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"); | |
425 } | |
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++; | |
437 } | |
438 if(nb < cnt) | |
439 memmove(data, data+nb, cnt-nb); | |
440 cnt -= nb; | |
441 | |
442 nr = label(r, nr); | |
443 if(nr == 0) | |
444 continue; | |
445 | |
446 recv(w->conswrite, &cwm); | |
447 pair.s = r; | |
448 pair.ns = nr; | |
449 send(cwm.cw, &pair); | |
450 } | |
451 } | |
452 | |
453 void | |
454 winterrupt(Window *w) | |
455 { | |
456 char rubout[1]; | |
457 | |
458 USED(w); | |
459 rubout[0] = getintr(sfd); | |
460 write(rcfd, rubout, 1); | |
461 } | |
462 | |
463 int | |
464 intrc(void) | |
465 { | |
466 return getintr(sfd); | |
467 } | |
468 | |
469 /* | |
470 * Process in-band messages about window title changes. | |
471 * The messages are of the form: | |
472 * | |
473 * \033];xxx\007 | |
474 * | |
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) | |
480 { | |
481 Rune *sl, *el, *er, *r; | |
482 char *p, *dir; | |
483 | |
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; | |
490 | |
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; | |
497 | |
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; | |
508 } | |
509 } | |
510 | |
511 /* remove trailing /-sysname if present */ | |
512 p = strrchr(dir, '/'); | |
513 if(p && *(p+1) == '-'){ | |
514 if(p == dir) | |
515 p++; | |
516 *p = 0; | |
517 } | |
518 | |
519 runemove(sl, el, er-el); | |
520 n -= (el-sl); | |
521 return n; | |
522 } | |
523 | |
524 void | |
525 rcinputproc(void *arg) | |
526 { | |
527 static char data[9000]; | |
528 Consreadmesg crm; | |
529 Channel *c1, *c2; | |
530 Stringpair pair; | |
531 | |
532 for(;;){ | |
533 recv(w->consread, &crm); | |
534 c1 = crm.c1; | |
535 c2 = crm.c2; | |
536 | |
537 pair.s = data; | |
538 pair.ns = sizeof data; | |
539 send(c1, &pair); | |
540 recv(c2, &pair); | |
541 | |
542 if(isecho(sfd)) | |
543 echoed(pair.s, pair.ns); | |
544 if(write(rcfd, pair.s, pair.ns) < 0) | |
545 threadexitsall(nil); | |
546 } | |
547 } | |
548 | |
549 /* | |
550 * Snarf buffer - rio uses runes internally | |
551 */ | |
552 void | |
553 rioputsnarf(void) | |
554 { | |
555 char *s; | |
556 | |
557 s = smprint("%.*S", nsnarf, snarf); | |
558 if(s){ | |
559 putsnarf(s); | |
560 free(s); | |
561 } | |
562 } | |
563 | |
564 void | |
565 riogetsnarf(void) | |
566 { | |
567 char *s; | |
568 int n, nb, nulls; | |
569 | |
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); | |
578 } | |
579 | |
580 /* | |
581 * Clumsy hack to make " and "" work. | |
582 * Then again, what's not a clumsy hack here in Unix land? | |
583 */ | |
584 | |
585 char adir[100]; | |
586 char thesocket[100]; | |
587 int afd; | |
588 | |
589 void listenproc(void*); | |
590 void textproc(void*); | |
591 | |
592 void | |
593 removethesocket(void) | |
594 { | |
595 if(thesocket[0]) | |
596 if(remove(thesocket) < 0) | |
597 fprint(2, "remove %s: %r\n", thesocket); | |
598 } | |
599 | |
600 void | |
601 servedevtext(void) | |
602 { | |
603 char buf[100]; | |
604 | |
605 snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid()); | |
606 | |
607 if((afd = announce(buf, adir)) < 0){ | |
608 putenv("text9term", ""); | |
609 return; | |
610 } | |
611 | |
612 putenv("text9term", buf); | |
613 proccreate(listenproc, nil, STACK); | |
614 strcpy(thesocket, buf+5); | |
615 atexit(removethesocket); | |
616 } | |
617 | |
618 void | |
619 listenproc(void *arg) | |
620 { | |
621 int fd; | |
622 char dir[100]; | |
623 | |
624 threadsetname("listen %s", thesocket); | |
625 USED(arg); | |
626 for(;;){ | |
627 fd = listen(adir, dir); | |
628 if(fd < 0){ | |
629 close(afd); | |
630 return; | |
631 } | |
632 proccreate(textproc, (void*)(uintptr)fd, STACK); | |
633 } | |
634 } | |
635 | |
636 void | |
637 textproc(void *arg) | |
638 { | |
639 int fd, i, x, n, end; | |
640 Rune r; | |
641 char buf[4096], *p, *ep; | |
642 | |
643 threadsetname("textproc"); | |
644 fd = (uintptr)arg; | |
645 p = buf; | |
646 ep = buf+sizeof buf; | |
647 if(w == nil){ | |
648 close(fd); | |
649 return; | |
650 } | |
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)) <= … | |
656 goto break2; | |
657 | |
658 if(i >= end) | |
659 break; | |
660 p = buf; | |
661 } | |
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); | |
669 } | |
670 break2: | |
671 close(fd); | |
672 } |