9term.c (11203B)
1 #include <u.h> 2 #include <pwd.h> 3 #include <signal.h> 4 #include <libc.h> 5 #include <ctype.h> 6 #include <draw.h> 7 #include <thread.h> 8 #include <mouse.h> 9 #include <cursor.h> 10 #include <keyboard.h> 11 #include <frame.h> 12 #include <plumb.h> 13 #include <complete.h> 14 #define Extern 15 #include "dat.h" 16 #include "fns.h" 17 #include "term.h" 18 19 const char *termprog = "9term"; 20 int use9wm; 21 int mainpid; 22 int mousepid; 23 int plumbfd; 24 int rcpid; 25 int rcfd; 26 int sfd; 27 Window *w; 28 char *fontname; 29 30 void derror(Display*, char*); 31 void mousethread(void*); 32 void keyboardthread(void*); 33 void winclosethread(void*); 34 void deletethread(void*); 35 void rcoutputproc(void*); 36 void rcinputproc(void*); 37 void hangupnote(void*, char*); 38 void resizethread(void*); 39 void servedevtext(void); 40 41 int errorshouldabort = 0; 42 int cooked; 43 44 void 45 usage(void) 46 { 47 fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n"); 48 threadexitsall("usage"); 49 } 50 51 int 52 threadmaybackground(void) 53 { 54 return 1; 55 } 56 57 void 58 threadmain(int argc, char *argv[]) 59 { 60 char *p, *env; 61 62 rfork(RFNOTEG); 63 font = nil; 64 _wantfocuschanges = 1; 65 mainpid = getpid(); 66 messagesize = 8192; 67 68 threadmaybackground(); 69 70 env = getenv("__CFBundleIdentifier"); 71 if(env != nil && strcmp(env, "com.swtch.9term") == 0) { 72 // Being invoked as $PLAN9/mac/9term.app. 73 // Set $SHELL and daemonize to let parent exit. 74 // This makes sure that each click on 9term 75 // brings up a new window. 76 extern void _threaddaemonize(void); 77 struct passwd *pw; 78 79 unsetenv("__CFBundleIdentifier"); 80 pw = getpwuid(getuid()); 81 if(pw != nil && pw->pw_shell != nil) 82 setenv("SHELL", pw->pw_shell, 1); 83 loginshell = TRUE; 84 //_threaddaemonize(); 85 } 86 87 ARGBEGIN{ 88 default: 89 usage(); 90 case 'l': 91 loginshell = TRUE; 92 break; 93 case 'f': 94 fontname = EARGF(usage()); 95 break; 96 case 's': 97 scrolling = TRUE; 98 break; 99 case 'c': 100 cooked = TRUE; 101 break; 102 case 'w': /* started from rio or 9wm */ 103 use9wm = TRUE; 104 break; 105 case 'W': 106 winsize = EARGF(usage()); 107 break; 108 }ARGEND 109 110 if(fontname) 111 putenv("font", fontname); 112 113 p = getenv("tabstop"); 114 if(p == 0) 115 p = getenv("TABSTOP"); 116 if(p && maxtab <= 0) 117 maxtab = strtoul(p, 0, 0); 118 if(maxtab <= 0) 119 maxtab = 4; 120 free(p); 121 122 startdir = "."; 123 124 if(initdraw(derror, fontname, "9term") < 0) 125 sysfatal("initdraw: %r"); 126 127 notify(hangupnote); 128 noteenable("sys: child"); 129 130 mousectl = initmouse(nil, screen); 131 if(mousectl == nil) 132 error("cannot find mouse"); 133 keyboardctl = initkeyboard(nil); 134 if(keyboardctl == nil) 135 error("cannot find keyboard"); 136 mouse = &mousectl->m; 137 138 winclosechan = chancreate(sizeof(Window*), 0); 139 deletechan = chancreate(sizeof(char*), 0); 140 141 timerinit(); 142 servedevtext(); 143 rcpid = rcstart(argc, argv, &rcfd, &sfd); 144 w = new(screen, FALSE, scrolling, rcpid, ".", nil, nil); 145 146 threadcreate(keyboardthread, nil, STACK); 147 threadcreate(mousethread, nil, STACK); 148 threadcreate(resizethread, nil, STACK); 149 150 proccreate(rcoutputproc, nil, STACK); 151 proccreate(rcinputproc, nil, STACK); 152 } 153 154 void 155 derror(Display *d, char *errorstr) 156 { 157 USED(d); 158 error(errorstr); 159 } 160 161 void 162 hangupnote(void *a, char *msg) 163 { 164 if(getpid() != mainpid) 165 noted(NDFLT); 166 if(strcmp(msg, "hangup") == 0){ 167 postnote(PNPROC, rcpid, "hangup"); 168 noted(NDFLT); 169 } 170 if(strstr(msg, "child")){ 171 char buf[128]; 172 int n; 173 174 n = awaitnohang(buf, sizeof buf-1); 175 if(n > 0){ 176 buf[n] = 0; 177 if(atoi(buf) == rcpid) 178 threadexitsall(0); 179 } 180 noted(NCONT); 181 } 182 noted(NDFLT); 183 } 184 185 void 186 keyboardthread(void *v) 187 { 188 Rune buf[2][20], *rp; 189 int i, n; 190 191 USED(v); 192 threadsetname("keyboardthread"); 193 n = 0; 194 for(;;){ 195 rp = buf[n]; 196 n = 1-n; 197 recv(keyboardctl->c, rp); 198 for(i=1; i<nelem(buf[0])-1; i++) 199 if(nbrecv(keyboardctl->c, rp+i) <= 0) 200 break; 201 rp[i] = L'\0'; 202 sendp(w->ck, rp); 203 } 204 } 205 206 void 207 resizethread(void *v) 208 { 209 Point p; 210 211 USED(v); 212 213 for(;;){ 214 p = stringsize(display->defaultfont, "0"); 215 if(p.x && p.y) 216 updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x, 217 Dx(screen->r), Dy(screen->r)); 218 wresize(w, screen, 0); 219 flushimage(display, 1); 220 if(recv(mousectl->resizec, nil) != 1) 221 break; 222 if(getwindow(display, Refnone) < 0) 223 sysfatal("can't reattach to window"); 224 } 225 } 226 227 void 228 mousethread(void *v) 229 { 230 int sending; 231 Mouse tmp; 232 233 USED(v); 234 235 sending = FALSE; 236 threadsetname("mousethread"); 237 while(readmouse(mousectl) >= 0){ 238 if(sending){ 239 Send: 240 /* send to window */ 241 if(mouse->buttons == 0) 242 sending = FALSE; 243 else 244 wsetcursor(w, 0); 245 tmp = mousectl->m; 246 send(w->mc.c, &tmp); 247 continue; 248 } 249 if((mouse->buttons&(1|8|16)) || ptinrect(mouse->xy, w->scrollr)){ 250 sending = TRUE; 251 goto Send; 252 }else if(mouse->buttons&2) 253 button2menu(w); 254 else 255 bouncemouse(mouse); 256 } 257 } 258 259 void 260 wborder(Window *w, int type) 261 { 262 } 263 264 Window* 265 wpointto(Point pt) 266 { 267 return w; 268 } 269 270 Window* 271 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv) 272 { 273 Window *w; 274 Mousectl *mc; 275 Channel *cm, *ck, *cctl; 276 277 if(i == nil) 278 return nil; 279 cm = chancreate(sizeof(Mouse), 0); 280 ck = chancreate(sizeof(Rune*), 0); 281 cctl = chancreate(sizeof(Wctlmesg), 4); 282 if(cm==nil || ck==nil || cctl==nil) 283 error("new: channel alloc failed"); 284 mc = emalloc(sizeof(Mousectl)); 285 *mc = *mousectl; 286 /* mc->image = i; */ 287 mc->c = cm; 288 w = wmk(i, mc, ck, cctl, scrollit); 289 free(mc); /* wmk copies *mc */ 290 window = erealloc(window, ++nwindow*sizeof(Window*)); 291 window[nwindow-1] = w; 292 if(hideit){ 293 hidden[nhidden++] = w; 294 w->screenr = ZR; 295 } 296 threadcreate(winctl, w, STACK); 297 if(!hideit) 298 wcurrent(w); 299 flushimage(display, 1); 300 wsetpid(w, pid, 1); 301 wsetname(w); 302 if(dir) 303 w->dir = estrdup(dir); 304 return w; 305 } 306 307 /* 308 * Button 2 menu. Extra entry for always cook 309 */ 310 311 enum 312 { 313 Cut, 314 Paste, 315 Snarf, 316 Plumb, 317 Look, 318 Send, 319 Scroll, 320 Cook 321 }; 322 323 char *menu2str[] = { 324 "cut", 325 "paste", 326 "snarf", 327 "plumb", 328 "look", 329 "send", 330 "cook", 331 "scroll", 332 nil 333 }; 334 335 336 Menu menu2 = 337 { 338 menu2str 339 }; 340 341 Rune newline[] = { '\n' }; 342 343 void 344 button2menu(Window *w) 345 { 346 if(w->deleted) 347 return; 348 incref(&w->ref); 349 if(w->scrolling) 350 menu2str[Scroll] = "noscroll"; 351 else 352 menu2str[Scroll] = "scroll"; 353 if(cooked) 354 menu2str[Cook] = "nocook"; 355 else 356 menu2str[Cook] = "cook"; 357 358 switch(menuhit(2, mousectl, &menu2, wscreen)){ 359 case Cut: 360 wsnarf(w); 361 wcut(w); 362 wscrdraw(w); 363 break; 364 365 case Snarf: 366 wsnarf(w); 367 break; 368 369 case Paste: 370 riogetsnarf(); 371 wpaste(w); 372 wscrdraw(w); 373 break; 374 375 case Plumb: 376 wplumb(w); 377 break; 378 379 case Look: 380 wlook(w); 381 break; 382 383 case Send: 384 riogetsnarf(); 385 wsnarf(w); 386 if(nsnarf == 0) 387 break; 388 if(w->rawing){ 389 waddraw(w, snarf, nsnarf); 390 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') 391 waddraw(w, newline, 1); 392 }else{ 393 winsert(w, snarf, nsnarf, w->nr); 394 if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004') 395 winsert(w, newline, 1, w->nr); 396 } 397 wsetselect(w, w->nr, w->nr); 398 wshow(w, w->nr); 399 break; 400 401 case Scroll: 402 if(w->scrolling ^= 1) 403 wshow(w, w->nr); 404 break; 405 case Cook: 406 cooked ^= 1; 407 break; 408 } 409 wclose(w); 410 wsendctlmesg(w, Wakeup, ZR, nil); 411 flushimage(display, 1); 412 } 413 414 int 415 rawon(void) 416 { 417 return !cooked && !isecho(sfd); 418 } 419 420 /* 421 * I/O with child rc. 422 */ 423 424 int label(Rune*, int); 425 426 void 427 rcoutputproc(void *arg) 428 { 429 int i, cnt, n, nb, nr; 430 static char data[9000]; 431 Conswritemesg cwm; 432 Rune *r; 433 Stringpair pair; 434 435 i = 0; 436 cnt = 0; 437 for(;;){ 438 /* XXX Let typing have a go -- maybe there's a rubout waiting. */ 439 i = 1-i; 440 n = read(rcfd, data+cnt, sizeof data-cnt); 441 if(n <= 0){ 442 if(n < 0) 443 fprint(2, "9term: rc read error: %r\n"); 444 threadexitsall("eof on rc output"); 445 } 446 n = echocancel(data+cnt, n); 447 if(n == 0) 448 continue; 449 cnt += n; 450 r = runemalloc(cnt); 451 cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil); 452 /* approach end of buffer */ 453 while(fullrune(data+nb, cnt-nb)){ 454 nb += chartorune(&r[nr], data+nb); 455 if(r[nr]) 456 nr++; 457 } 458 if(nb < cnt) 459 memmove(data, data+nb, cnt-nb); 460 cnt -= nb; 461 462 nr = label(r, nr); 463 if(nr == 0) 464 continue; 465 466 recv(w->conswrite, &cwm); 467 pair.s = r; 468 pair.ns = nr; 469 send(cwm.cw, &pair); 470 } 471 } 472 473 void 474 winterrupt(Window *w) 475 { 476 char rubout[1]; 477 478 USED(w); 479 rubout[0] = getintr(sfd); 480 write(rcfd, rubout, 1); 481 } 482 483 int 484 intrc(void) 485 { 486 return getintr(sfd); 487 } 488 489 /* 490 * Process in-band messages about window title changes. 491 * The messages are of the form: 492 * 493 * \033];xxx\007 494 * 495 * where xxx is the new directory. This format was chosen 496 * because it changes the label on xterm windows. 497 */ 498 int 499 label(Rune *sr, int n) 500 { 501 Rune *sl, *el, *er, *r; 502 char *p, *dir; 503 504 er = sr+n; 505 for(r=er-1; r>=sr; r--) 506 if(*r == '\007') 507 break; 508 if(r < sr) 509 return n; 510 511 el = r+1; 512 for(sl=el-3; sl>=sr; sl--) 513 if(sl[0]=='\033' && sl[1]==']' && sl[2]==';') 514 break; 515 if(sl < sr) 516 return n; 517 518 dir = smprint("%.*S", (el-1)-(sl+3), sl+3); 519 if(dir){ 520 if(strcmp(dir, "*9term-hold+") == 0) { 521 w->holding = 1; 522 wrepaint(w); 523 flushimage(display, 1); 524 } else { 525 drawsetlabel(dir); 526 free(w->dir); 527 w->dir = dir; 528 } 529 } 530 531 /* remove trailing /-sysname if present */ 532 p = strrchr(dir, '/'); 533 if(p && *(p+1) == '-'){ 534 if(p == dir) 535 p++; 536 *p = 0; 537 } 538 539 runemove(sl, el, er-el); 540 n -= (el-sl); 541 return n; 542 } 543 544 void 545 rcinputproc(void *arg) 546 { 547 static char data[9000]; 548 Consreadmesg crm; 549 Channel *c1, *c2; 550 Stringpair pair; 551 552 for(;;){ 553 recv(w->consread, &crm); 554 c1 = crm.c1; 555 c2 = crm.c2; 556 557 pair.s = data; 558 pair.ns = sizeof data; 559 send(c1, &pair); 560 recv(c2, &pair); 561 562 if(isecho(sfd)) 563 echoed(pair.s, pair.ns); 564 if(write(rcfd, pair.s, pair.ns) < 0) 565 threadexitsall(nil); 566 } 567 } 568 569 /* 570 * Snarf buffer - rio uses runes internally 571 */ 572 void 573 rioputsnarf(void) 574 { 575 char *s; 576 577 s = smprint("%.*S", nsnarf, snarf); 578 if(s){ 579 putsnarf(s); 580 free(s); 581 } 582 } 583 584 void 585 riogetsnarf(void) 586 { 587 char *s; 588 int n, nb, nulls; 589 590 s = getsnarf(); 591 if(s == nil) 592 return; 593 n = strlen(s)+1; 594 free(snarf); 595 snarf = runemalloc(n); 596 cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls); 597 free(s); 598 } 599 600 /* 601 * Clumsy hack to make " and "" work. 602 * Then again, what's not a clumsy hack here in Unix land? 603 */ 604 605 char adir[100]; 606 char thesocket[100]; 607 int afd; 608 609 void listenproc(void*); 610 void textproc(void*); 611 612 void 613 removethesocket(void) 614 { 615 if(thesocket[0]) 616 if(remove(thesocket) < 0) 617 fprint(2, "remove %s: %r\n", thesocket); 618 } 619 620 void 621 servedevtext(void) 622 { 623 char buf[100]; 624 625 snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid()); 626 627 if((afd = announce(buf, adir)) < 0){ 628 putenv("text9term", ""); 629 return; 630 } 631 632 putenv("text9term", buf); 633 proccreate(listenproc, nil, STACK); 634 strcpy(thesocket, buf+5); 635 atexit(removethesocket); 636 } 637 638 void 639 listenproc(void *arg) 640 { 641 int fd; 642 char dir[100]; 643 644 threadsetname("listen %s", thesocket); 645 USED(arg); 646 for(;;){ 647 fd = listen(adir, dir); 648 if(fd < 0){ 649 close(afd); 650 return; 651 } 652 proccreate(textproc, (void*)(uintptr)fd, STACK); 653 } 654 } 655 656 void 657 textproc(void *arg) 658 { 659 int fd, i, x, n, end; 660 Rune r; 661 char buf[4096], *p, *ep; 662 663 threadsetname("textproc"); 664 fd = (uintptr)arg; 665 p = buf; 666 ep = buf+sizeof buf; 667 if(w == nil){ 668 close(fd); 669 return; 670 } 671 end = w->org+w->nr; /* avoid possible output loop */ 672 for(i=w->org;; i++){ 673 if(i >= end || ep-p < UTFmax){ 674 for(x=0; x<p-buf; x+=n) 675 if((n = write(fd, buf+x, (p-x)-buf)) <= 0) 676 goto break2; 677 678 if(i >= end) 679 break; 680 p = buf; 681 } 682 if(i < w->org) 683 i = w->org; 684 r = w->r[i-w->org]; 685 if(r < Runeself) 686 *p++ = r; 687 else 688 p += runetochar(p, &r); 689 } 690 break2: 691 close(fd); 692 }