1 /* 2 * the actual viewer that handles screen stuff 3 */ 4 5 #include <u.h> 6 #include <libc.h> 7 #include <draw.h> 8 #include <cursor.h> 9 #include <event.h> 10 #include <bio.h> 11 #include <plumb.h> 12 #include <ctype.h> 13 #include <keyboard.h> 14 #include "page.h" 15 16 Document *doc; 17 Image *im; 18 int page; 19 int upside = 0; 20 21 Rectangle ulrange; /* the upper left corner of the image must be in this rectangle */ 22 Point ul; /* the upper left corner of the image is at this point on the screen */ 23 24 Point pclip(Point, Rectangle); 25 Rectangle mkrange(Rectangle screenr, Rectangle imr); 26 void redraw(Image*); 27 28 Cursor reading={ 29 {-1, -1}, 30 {0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 31 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 32 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 33 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, }, 34 {0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 35 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 36 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 37 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, } 38 }; 39 40 enum { 41 Left = 1, 42 Middle = 2, 43 Right = 4, 44 45 RMenu = 3, 46 }; 47 48 int 49 max(int a, int b) 50 { 51 return a > b ? a : b; 52 } 53 54 int 55 min(int a, int b) 56 { 57 return a < b ? a : b; 58 } 59 60 61 char* 62 menugen(int n) 63 { 64 static char menustr[32]; 65 char *p; 66 int len; 67 68 if(n == doc->npage) 69 return "exit"; 70 if(n > doc->npage) 71 return nil; 72 73 if(reverse) 74 n = doc->npage-1-n; 75 76 p = doc->pagename(doc, n); 77 len = (sizeof menustr)-2; 78 79 if(strlen(p) > len && strrchr(p, '/')) 80 p = strrchr(p, '/')+1; 81 if(strlen(p) > len) 82 p = p+strlen(p)-len; 83 84 strcpy(menustr+1, p); 85 if(page == n) 86 menustr[0] = '>'; 87 else 88 menustr[0] = ' '; 89 return menustr; 90 } 91 92 void 93 showpage(int page, Menu *m) 94 { 95 if(doc->fwdonly) 96 m->lasthit = 0; /* this page */ 97 else 98 m->lasthit = reverse ? doc->npage-1-page : page; 99 100 esetcursor(&reading); 101 freeimage(im); 102 if((page < 0 || page >= doc->npage) && !doc->fwdonly){ 103 im = nil; 104 return; 105 } 106 im = doc->drawpage(doc, page); 107 if(im == nil) { 108 if(doc->fwdonly) /* this is how we know we're out of pages */ 109 wexits(0); 110 111 im = xallocimage(display, Rect(0,0,50,50), GREY1, 1, DBlack); 112 if(im == nil) { 113 fprint(2, "out of memory: %r\n"); 114 wexits("memory"); 115 } 116 string(im, ZP, display->white, ZP, display->defaultfont, "?"); 117 }else if(resizing){ 118 resize(Dx(im->r), Dy(im->r)); 119 } 120 if(upside) 121 rot180(im); 122 123 esetcursor(nil); 124 // ul = ulrange.min; 125 126 redraw(screen); 127 flushimage(display, 1); 128 } 129 130 char* 131 writebitmap(void) 132 { 133 char basename[64]; 134 char name[64+30]; 135 static char result[200]; 136 char *p, *q; 137 int fd; 138 139 if(im == nil) 140 return "no image"; 141 142 memset(basename, 0, sizeof basename); 143 if(doc->docname) 144 strncpy(basename, doc->docname, sizeof(basename)-1); 145 else if((p = menugen(page)) && p[0] != '\0') 146 strncpy(basename, p+1, sizeof(basename)-1); 147 148 if(basename[0]) { 149 if(q = strrchr(basename, '/')) 150 q++; 151 else 152 q = basename; 153 if(p = strchr(q, '.')) 154 *p = 0; 155 156 memset(name, 0, sizeof name); 157 snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1); 158 if(access(name, 0) >= 0) { 159 strcat(name, "XXXX"); 160 mktemp(name); 161 } 162 if(access(name, 0) >= 0) 163 return "couldn't think of a name for bitmap"; 164 } else { 165 strcpy(name, "bitXXXX"); 166 mktemp(name); 167 if(access(name, 0) >= 0) 168 return "couldn't think of a name for bitmap"; 169 } 170 171 if((fd = create(name, OWRITE, 0666)) < 0) { 172 snprint(result, sizeof result, "cannot create %s: %r", name); 173 return result; 174 } 175 176 if(writeimage(fd, im, 0) < 0) { 177 snprint(result, sizeof result, "cannot writeimage: %r"); 178 close(fd); 179 return result; 180 } 181 close(fd); 182 183 snprint(result, sizeof result, "wrote %s", name); 184 return result; 185 } 186 187 static void translate(Point); 188 189 static int 190 showdata(Plumbmsg *msg) 191 { 192 char *s; 193 194 s = plumblookup(msg->attr, "action"); 195 return s && strcmp(s, "showdata")==0; 196 } 197 198 void 199 viewer(Document *dd) 200 { 201 int i, fd, n, oldpage; 202 int nxt; 203 Menu menu; 204 Mouse m; 205 Event e; 206 Point dxy, oxy, xy0; 207 Rectangle r; 208 char *fwditems[] = { "this page", "next page", "exit", 0 }; 209 char *s; 210 enum { Eplumb = 4 }; 211 Plumbmsg *pm; 212 213 doc = dd; /* save global for menuhit */ 214 ul = screen->r.min; 215 einit(Emouse|Ekeyboard); 216 if(doc->addpage != nil) 217 eplumb(Eplumb, "image"); 218 219 esetcursor(&reading); 220 r.min = ZP; 221 222 /* 223 * im is a global pointer to the current image. 224 * eventually, i think we will have a layer between 225 * the display routines and the ps/pdf/whatever routines 226 * to perhaps cache and handle images of different 227 * sizes, etc. 228 */ 229 im = 0; 230 page = reverse ? doc->npage-1 : 0; 231 232 if(doc->fwdonly) { 233 menu.item = fwditems; 234 menu.gen = 0; 235 menu.lasthit = 0; 236 } else { 237 menu.item = 0; 238 menu.gen = menugen; 239 menu.lasthit = 0; 240 } 241 242 showpage(page, &menu); 243 esetcursor(nil); 244 245 nxt = 0; 246 for(;;) { 247 /* 248 * throughout, if doc->fwdonly is set, we restrict the functionality 249 * a fair amount. we don't care about doc->npage anymore, and 250 * all that can be done is select the next page. 251 */ 252 switch(eread(Emouse|Ekeyboard|Eplumb, &e)){ 253 case Ekeyboard: 254 if(e.kbdc <= 0xFF && isdigit(e.kbdc)) { 255 nxt = nxt*10+e.kbdc-'0'; 256 break; 257 } else if(e.kbdc != '\n') 258 nxt = 0; 259 switch(e.kbdc) { 260 case 'r': /* reverse page order */ 261 if(doc->fwdonly) 262 break; 263 reverse = !reverse; 264 menu.lasthit = doc->npage-1-menu.lasthit; 265 266 /* 267 * the theory is that if we are reversing the 268 * document order and are on the first or last 269 * page then we're just starting and really want 270 * to view the other end. maybe the if 271 * should be dropped and this should happen always. 272 */ 273 if(page == 0 || page == doc->npage-1) { 274 page = doc->npage-1-page; 275 showpage(page, &menu); 276 } 277 break; 278 case 'w': /* write bitmap of current screen */ 279 esetcursor(&reading); 280 s = writebitmap(); 281 if(s) 282 string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP, 283 display->defaultfont, s); 284 esetcursor(nil); 285 flushimage(display, 1); 286 break; 287 case 'd': /* remove image from working set */ 288 if(doc->rmpage && page < doc->npage) { 289 if(doc->rmpage(doc, page) >= 0) { 290 if(doc->npage < 0) 291 wexits(0); 292 if(page >= doc->npage) 293 page = doc->npage-1; 294 showpage(page, &menu); 295 } 296 } 297 break; 298 case 'q': 299 case 0x04: /* ctrl-d */ 300 wexits(0); 301 case 'u': 302 if(im==nil) 303 break; 304 esetcursor(&reading); 305 rot180(im); 306 esetcursor(nil); 307 upside = !upside; 308 redraw(screen); 309 flushimage(display, 1); 310 break; 311 case '-': 312 case '\b': 313 case Kup: /* up arrow */ 314 if(page > 0 && !doc->fwdonly) { 315 --page; 316 showpage(page, &menu); 317 } 318 break; 319 case '\n': 320 if(nxt) { 321 nxt--; 322 if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly) 323 showpage(page=nxt, &menu); 324 nxt = 0; 325 break; 326 } 327 /* fall through */ 328 default: 329 if(doc->npage && ++page >= doc->npage && !doc->fwdonly) 330 wexits(0); 331 showpage(page, &menu); 332 break; 333 } 334 break; 335 336 case Emouse: 337 m = e.mouse; 338 switch(m.buttons){ 339 case Left: 340 oxy = m.xy; 341 xy0 = oxy; 342 do { 343 dxy = subpt(m.xy, oxy); 344 oxy = m.xy; 345 translate(dxy); 346 m = emouse(); 347 } while(m.buttons == Left); 348 if(m.buttons) { 349 dxy = subpt(xy0, oxy); 350 translate(dxy); 351 } 352 break; 353 354 case Middle: 355 do 356 m = emouse(); 357 while(m.buttons == Middle); 358 if(m.buttons) 359 break; 360 361 if(doc->npage == 0) 362 break; 363 364 if(reverse) 365 page--; 366 else 367 page++; 368 369 if((page >= doc->npage || page < 0) && !doc->fwdonly) 370 return; 371 372 showpage(page, &menu); 373 nxt = 0; 374 break; 375 376 case Right: 377 if(doc->npage == 0) 378 break; 379 380 oldpage = page; 381 n = emenuhit(RMenu, &m, &menu); 382 if(n == -1) 383 break; 384 385 if(doc->fwdonly) { 386 switch(n){ 387 case 0: /* this page */ 388 break; 389 case 1: /* next page */ 390 showpage(++page, &menu); 391 break; 392 case 2: /* exit */ 393 return; 394 } 395 break; 396 } 397 398 if(n == doc->npage) 399 return; 400 else 401 page = reverse ? doc->npage-1-n : n; 402 403 if(oldpage != page) 404 showpage(page, &menu); 405 nxt = 0; 406 break; 407 } 408 break; 409 410 case Eplumb: 411 pm = e.v; 412 if(pm->ndata <= 0){ 413 plumbfree(pm); 414 break; 415 } 416 if(showdata(pm)) { 417 s = estrdup("/tmp/pageplumbXXXXXXX"); 418 fd = opentemp(s); 419 write(fd, pm->data, pm->ndata); 420 /* lose fd reference on purpose; the file is open ORCLOSE */ 421 } else if(pm->data[0] == '/') { 422 s = estrdup(pm->data); 423 } else { 424 s = emalloc(strlen(pm->wdir)+1+pm->ndata+1); 425 sprint(s, "%s/%s", pm->wdir, pm->data); 426 cleanname(s); 427 } 428 if((i = doc->addpage(doc, s)) >= 0) { 429 page = i; 430 showpage(page, &menu); 431 } 432 free(s); 433 plumbfree(pm); 434 break; 435 } 436 } 437 } 438 439 Image *gray; 440 441 /* 442 * A draw operation that touches only the area contained in bot but not in top. 443 * mp and sp get aligned with bot.min. 444 */ 445 static void 446 gendrawdiff(Image *dst, Rectangle bot, Rectangle top, 447 Image *src, Point sp, Image *mask, Point mp) 448 { 449 Rectangle r; 450 Point origin; 451 Point delta; 452 453 if(Dx(bot)*Dy(bot) == 0) 454 return; 455 456 /* no points in bot - top */ 457 if(rectinrect(bot, top)) 458 return; 459 460 /* bot - top ≡ bot */ 461 if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){ 462 gendraw(dst, bot, src, sp, mask, mp); 463 return; 464 } 465 466 origin = bot.min; 467 /* split bot into rectangles that don't intersect top */ 468 /* left side */ 469 if(bot.min.x < top.min.x){ 470 r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y); 471 delta = subpt(r.min, origin); 472 gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta)); 473 bot.min.x = top.min.x; 474 } 475 476 /* right side */ 477 if(bot.max.x > top.max.x){ 478 r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y); 479 delta = subpt(r.min, origin); 480 gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta)); 481 bot.max.x = top.max.x; 482 } 483 484 /* top */ 485 if(bot.min.y < top.min.y){ 486 r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y); 487 delta = subpt(r.min, origin); 488 gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta)); 489 bot.min.y = top.min.y; 490 } 491 492 /* bottom */ 493 if(bot.max.y > top.max.y){ 494 r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y); 495 delta = subpt(r.min, origin); 496 gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta)); 497 bot.max.y = top.max.y; 498 } 499 } 500 501 static void 502 drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p) 503 { 504 gendrawdiff(dst, bot, top, src, p, mask, p); 505 } 506 507 /* 508 * Translate the image in the window by delta. 509 */ 510 static void 511 translate(Point delta) 512 { 513 Point u; 514 Rectangle r, or; 515 516 if(im == nil) 517 return; 518 519 u = pclip(addpt(ul, delta), ulrange); 520 delta = subpt(u, ul); 521 if(delta.x == 0 && delta.y == 0) 522 return; 523 524 /* 525 * The upper left corner of the image is currently at ul. 526 * We want to move it to u. 527 */ 528 or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul); 529 r = rectaddpt(or, delta); 530 531 draw(screen, r, screen, nil, ul); 532 ul = u; 533 534 /* fill in gray where image used to be but isn't. */ 535 drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP); 536 537 /* fill in black border */ 538 drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP); 539 540 /* fill in image where it used to be off the screen. */ 541 if(rectclip(&or, screen->r)) 542 drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min); 543 else 544 draw(screen, r, im, nil, im->r.min); 545 flushimage(display, 1); 546 } 547 548 void 549 redraw(Image *screen) 550 { 551 Rectangle r; 552 553 if(im == nil) 554 return; 555 556 ulrange.max = screen->r.max; 557 ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r))); 558 559 ul = pclip(ul, ulrange); 560 draw(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min))); 561 562 if(im->repl) 563 return; 564 565 /* fill in any outer edges */ 566 /* black border */ 567 r = rectaddpt(im->r, subpt(ul, im->r.min)); 568 border(screen, r, -2, display->black, ZP); 569 r.min = subpt(r.min, Pt(2,2)); 570 r.max = addpt(r.max, Pt(2,2)); 571 572 /* gray for the rest */ 573 if(gray == nil) { 574 gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF); 575 if(gray == nil) { 576 fprint(2, "g out of memory: %r\n"); 577 wexits("mem"); 578 } 579 } 580 border(screen, r, -4000, gray, ZP); 581 // flushimage(display, 0); 582 } 583 584 void 585 eresized(int new) 586 { 587 Rectangle r; 588 r = screen->r; 589 if(new && getwindow(display, Refnone) < 0) 590 fprint(2,"can't reattach to window"); 591 ul = addpt(ul, subpt(screen->r.min, r.min)); 592 redraw(screen); 593 } 594 595 /* clip p to be in r */ 596 Point 597 pclip(Point p, Rectangle r) 598 { 599 if(p.x < r.min.x) 600 p.x = r.min.x; 601 else if(p.x >= r.max.x) 602 p.x = r.max.x-1; 603 604 if(p.y < r.min.y) 605 p.y = r.min.y; 606 else if(p.y >= r.max.y) 607 p.y = r.max.y-1; 608 609 return p; 610 } 611 612 /* 613 * resize is perhaps a misnomer. 614 * this really just grows the window to be at least dx across 615 * and dy high. if the window hits the bottom or right edge, 616 * it is backed up until it hits the top or left edge. 617 */ 618 void 619 resize(int dx, int dy) 620 { 621 static Rectangle sr; 622 Rectangle r, or; 623 624 dx += 2*Borderwidth; 625 dy += 2*Borderwidth; 626 if(wctlfd < 0){ 627 wctlfd = open("/dev/wctl", OWRITE); 628 if(wctlfd < 0) 629 return; 630 } 631 632 r = insetrect(screen->r, -Borderwidth); 633 if(Dx(r) >= dx && Dy(r) >= dy) 634 return; 635 636 if(Dx(sr)*Dy(sr) == 0) 637 sr = screenrect(); 638 639 or = r; 640 641 r.max.x = max(r.min.x+dx, r.max.x); 642 r.max.y = max(r.min.y+dy, r.max.y); 643 if(r.max.x > sr.max.x){ 644 if(Dx(r) > Dx(sr)){ 645 r.min.x = 0; 646 r.max.x = sr.max.x; 647 }else 648 r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0)); 649 } 650 if(r.max.y > sr.max.y){ 651 if(Dy(r) > Dy(sr)){ 652 r.min.y = 0; 653 r.max.y = sr.max.y; 654 }else 655 r = rectaddpt(r, Pt(0, sr.max.y-r.max.y)); 656 } 657 658 /* 659 * Sometimes we can't actually grow the window big enough, 660 * and resizing it to the same shape makes it flash. 661 */ 662 if(Dx(r) == Dx(or) && Dy(r) == Dy(or)) 663 return; 664 665 fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n", 666 r.min.x, r.min.y, r.max.x, r.max.y); 667 } 668 669 /* 670 * If we allocimage after a resize but before flushing the draw buffer, 671 * we won't have seen the reshape event, and we won't have called 672 * getwindow, and allocimage will fail. So we flushimage before every alloc. 673 */ 674 Image* 675 xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val) 676 { 677 flushimage(display, 0); 678 return allocimage(d, r, chan, repl, val); 679 } 680 681 /* all code below this line should be in the library, but is stolen from colors instead */ 682 static char* 683 rdenv(char *name) 684 { 685 char *v; 686 int fd, size; 687 688 fd = open(name, OREAD); 689 if(fd < 0) 690 return 0; 691 size = seek(fd, 0, 2); 692 v = malloc(size+1); 693 if(v == 0){ 694 fprint(2, "page: can't malloc: %r\n"); 695 wexits("no mem"); 696 } 697 seek(fd, 0, 0); 698 read(fd, v, size); 699 v[size] = 0; 700 close(fd); 701 return v; 702 } 703 704 void 705 newwin(void) 706 { 707 char *srv, *mntsrv; 708 char spec[100]; 709 int srvfd, cons, pid; 710 711 switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){ 712 case -1: 713 fprint(2, "page: can't fork: %r\n"); 714 wexits("no fork"); 715 case 0: 716 break; 717 default: 718 wexits(0); 719 } 720 721 srv = rdenv("/env/wsys"); 722 if(srv == 0){ 723 mntsrv = rdenv("/mnt/term/env/wsys"); 724 if(mntsrv == 0){ 725 fprint(2, "page: can't find $wsys\n"); 726 wexits("srv"); 727 } 728 srv = malloc(strlen(mntsrv)+10); 729 sprint(srv, "/mnt/term%s", mntsrv); 730 free(mntsrv); 731 pid = 0; /* can't send notes to remote processes! */ 732 }else 733 pid = getpid(); 734 srvfd = open(srv, ORDWR); 735 free(srv); 736 if(srvfd == -1){ 737 fprint(2, "page: can't open %s: %r\n", srv); 738 wexits("no srv"); 739 } 740 sprint(spec, "new -pid %d", pid); 741 if(mount(srvfd, "/mnt/wsys", 0, spec) == -1){ 742 fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec); 743 wexits("no mount"); 744 } 745 close(srvfd); 746 unmount("/mnt/acme", "/dev"); 747 bind("/mnt/wsys", "/dev", MBEFORE); 748 cons = open("/dev/cons", OREAD); 749 if(cons==-1){ 750 NoCons: 751 fprint(2, "page: can't open /dev/cons: %r"); 752 wexits("no cons"); 753 } 754 dup(cons, 0); 755 close(cons); 756 cons = open("/dev/cons", OWRITE); 757 if(cons==-1) 758 goto NoCons; 759 dup(cons, 1); 760 dup(cons, 2); 761 close(cons); 762 // wctlfd = open("/dev/wctl", OWRITE); 763 } 764 765 Rectangle 766 screenrect(void) 767 { 768 int fd; 769 char buf[12*5]; 770 771 fd = open("/dev/screen", OREAD); 772 if(fd == -1) 773 fd=open("/mnt/term/dev/screen", OREAD); 774 if(fd == -1){ 775 fprint(2, "page: can't open /dev/screen: %r\n"); 776 wexits("window read"); 777 } 778 if(read(fd, buf, sizeof buf) != sizeof buf){ 779 fprint(2, "page: can't read /dev/screen: %r\n"); 780 wexits("screen read"); 781 } 782 close(fd); 783 return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48)); 784 } 785 786