print.c revision a1f3da82
1/* $XTermId: print.c,v 1.121 2011/02/09 10:11:44 tom Exp $ */ 2 3/************************************************************ 4 5Copyright 1997-2010,2011 by Thomas E. Dickey 6 7 All Rights Reserved 8 9Permission is hereby granted, free of charge, to any person obtaining a 10copy of this software and associated documentation files (the 11"Software"), to deal in the Software without restriction, including 12without limitation the rights to use, copy, modify, merge, publish, 13distribute, sublicense, and/or sell copies of the Software, and to 14permit persons to whom the Software is furnished to do so, subject to 15the following conditions: 16 17The above copyright notice and this permission notice shall be included 18in all copies or substantial portions of the Software. 19 20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY 24CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 28Except as contained in this notice, the name(s) of the above copyright 29holders shall not be used in advertising or otherwise to promote the 30sale, use or other dealings in this Software without prior written 31authorization. 32 33********************************************************/ 34 35#include <xterm.h> 36#include <data.h> 37#include <menu.h> 38#include <error.h> 39#include <xstrings.h> 40 41#include <stdio.h> 42 43#undef CTRL 44#define CTRL(c) ((c) & 0x1f) 45 46#define SHIFT_IN '\017' 47#define SHIFT_OUT '\016' 48 49#define CSET_IN 'A' 50#define CSET_OUT '0' 51 52#define isForm(c) ((c) == '\r' || (c) == '\n' || (c) == '\f') 53#define Strlen(a) strlen((char *)a) 54#define Strcmp(a,b) strcmp((char *)a,(char *)b) 55#define Strncmp(a,b,c) strncmp((char *)a,(char *)b,c) 56 57#ifdef VMS 58#define VMS_TEMP_PRINT_FILE "sys$scratch:xterm_print.txt" 59#endif 60 61static void charToPrinter(XtermWidget /* xw */ , 62 unsigned /* chr */ ); 63static void printLine(XtermWidget /* xw */ , 64 int /* row */ , 65 unsigned /* chr */ , 66 PrinterFlags * /* p */ ); 67static void send_CharSet(XtermWidget /* xw */ , 68 LineData * /* ld */ ); 69static void send_SGR(XtermWidget /* xw */ , 70 unsigned /* attr */ , 71 unsigned /* fg */ , 72 unsigned /* bg */ ); 73static void stringToPrinter(XtermWidget /* xw */ , 74 const char * /*str */ ); 75 76static FILE *Printer; 77static pid_t Printer_pid; 78static int initialized; 79 80static void 81closePrinter(XtermWidget xw GCC_UNUSED) 82{ 83 if (xtermHasPrinter(xw) != 0) { 84#ifdef VMS 85 TScreen *screen = TScreenOf(xw); 86 87 char pcommand[256]; 88 (void) sprintf(pcommand, "%s %s;", 89 screen->printer_command, 90 VMS_TEMP_PRINT_FILE); 91#endif 92 93 if (Printer != 0) { 94 fclose(Printer); 95 TRACE(("closed printer, waiting...\n")); 96#ifdef VMS /* This is a quick hack, really should use 97 spawn and check status or system services 98 and go straight to the queue */ 99 (void) system(pcommand); 100#else /* VMS */ 101 while (nonblocking_wait() > 0) 102#endif /* VMS */ 103 ; 104 Printer = 0; 105 initialized = 0; 106 TRACE(("closed printer\n")); 107 } 108 } 109} 110 111static void 112printCursorLine(XtermWidget xw) 113{ 114 TScreen *screen = TScreenOf(xw); 115 116 TRACE(("printCursorLine\n")); 117 printLine(xw, screen->cur_row, '\n', getPrinterFlags(xw, NULL, 0)); 118} 119 120#define NO_COLOR ((unsigned)-1) 121 122/* 123 * DEC's manual doesn't document whether trailing blanks are removed, or what 124 * happens with a line that is entirely blank. This function prints the 125 * characters that xterm would allow as a selection (which may include blanks). 126 */ 127static void 128printLine(XtermWidget xw, int row, unsigned chr, PrinterFlags * p) 129{ 130 TScreen *screen = TScreenOf(xw); 131 int inx = ROW2INX(screen, row); 132 LineData *ld; 133 Char attr = 0; 134 unsigned ch; 135 int last = MaxCols(screen); 136 int col; 137#if OPT_ISO_COLORS && OPT_PRINT_COLORS 138#define ColorOf(ld,col) (ld->color[col]) 139#endif 140 unsigned fg = NO_COLOR, last_fg = NO_COLOR; 141 unsigned bg = NO_COLOR, last_bg = NO_COLOR; 142 int cs = CSET_IN; 143 int last_cs = CSET_IN; 144 145 ld = getLineData(screen, inx); 146 if (ld == 0) 147 return; 148 149 TRACE(("printLine(row=%d/%d, top=%d:%d, chr=%d):%s\n", 150 row, ROW2INX(screen, row), screen->topline, screen->max_row, chr, 151 visibleIChars(ld->charData, (unsigned) last))); 152 153 while (last > 0) { 154 if ((ld->attribs[last - 1] & CHARDRAWN) == 0) 155 last--; 156 else 157 break; 158 } 159 if (last) { 160 if (p->print_attributes) { 161 send_CharSet(xw, ld); 162 send_SGR(xw, 0, NO_COLOR, NO_COLOR); 163 } 164 for (col = 0; col < last; col++) { 165 ch = ld->charData[col]; 166#if OPT_PRINT_COLORS 167 if (screen->colorMode) { 168 if (p->print_attributes > 1) { 169 fg = (ld->attribs[col] & FG_COLOR) 170 ? extract_fg(xw, ColorOf(ld, col), ld->attribs[col]) 171 : NO_COLOR; 172 bg = (ld->attribs[col] & BG_COLOR) 173 ? extract_bg(xw, ColorOf(ld, col), ld->attribs[col]) 174 : NO_COLOR; 175 } 176 } 177#endif 178 if ((((ld->attribs[col] & SGR_MASK) != attr) 179#if OPT_PRINT_COLORS 180 || (last_fg != fg) || (last_bg != bg) 181#endif 182 ) 183 && ch) { 184 attr = CharOf(ld->attribs[col] & SGR_MASK); 185#if OPT_PRINT_COLORS 186 last_fg = fg; 187 last_bg = bg; 188#endif 189 if (p->print_attributes) 190 send_SGR(xw, attr, fg, bg); 191 } 192 193 if (ch == 0) 194 ch = ' '; 195 196#if OPT_WIDE_CHARS 197 if (screen->utf8_mode) 198 cs = CSET_IN; 199 else 200#endif 201 cs = (ch >= ' ' && ch != ANSI_DEL) ? CSET_IN : CSET_OUT; 202 if (last_cs != cs) { 203 if (p->print_attributes) { 204 charToPrinter(xw, 205 (unsigned) ((cs == CSET_OUT) 206 ? SHIFT_OUT 207 : SHIFT_IN)); 208 } 209 last_cs = cs; 210 } 211 212 /* FIXME: we shouldn't have to map back from the 213 * alternate character set, except that the 214 * corresponding charset information is not encoded 215 * into the CSETS array. 216 */ 217 charToPrinter(xw, 218 ((cs == CSET_OUT) 219 ? (ch == ANSI_DEL ? 0x5f : (ch + 0x5f)) 220 : ch)); 221 if_OPT_WIDE_CHARS(screen, { 222 size_t off; 223 for_each_combData(off, ld) { 224 ch = ld->combData[off][col]; 225 if (ch == 0) 226 break; 227 charToPrinter(xw, ch); 228 } 229 }); 230 } 231 if (p->print_attributes) { 232 send_SGR(xw, 0, NO_COLOR, NO_COLOR); 233 if (cs != CSET_IN) 234 charToPrinter(xw, SHIFT_IN); 235 } 236 } 237 238 /* finish line (protocol for attributes needs a CR */ 239 if (p->print_attributes) 240 charToPrinter(xw, '\r'); 241 242 if (chr && !(p->printer_newline)) { 243 if (LineTstWrapped(ld)) 244 chr = '\0'; 245 } 246 247 if (chr) 248 charToPrinter(xw, chr); 249 250 return; 251} 252 253#define PrintNewLine() (unsigned) (((top < bot) || p->printer_newline) ? '\n' : '\0') 254 255void 256xtermPrintScreen(XtermWidget xw, Bool use_DECPEX, PrinterFlags * p) 257{ 258 if (XtIsRealized((Widget) xw)) { 259 TScreen *screen = TScreenOf(xw); 260 Bool extent = (use_DECPEX && p->printer_extent); 261 int top = extent ? 0 : screen->top_marg; 262 int bot = extent ? screen->max_row : screen->bot_marg; 263 int was_open = initialized; 264 265 TRACE(("xtermPrintScreen, rows %d..%d\n", top, bot)); 266 267 while (top <= bot) { 268 printLine(xw, top, PrintNewLine(), p); 269 ++top; 270 } 271 if (p->printer_formfeed) 272 charToPrinter(xw, '\f'); 273 274 if (!was_open || screen->printer_autoclose) { 275 closePrinter(xw); 276 } 277 } else { 278 Bell(xw, XkbBI_MinorError, 0); 279 } 280} 281 282/* 283 * If the alternate screen is active, we'll print only that. Otherwise, print 284 * the normal screen plus all scrolled-back lines. The distinction is made 285 * because the normal screen's buffer is part of the overall scrollback buffer. 286 */ 287void 288xtermPrintEverything(XtermWidget xw, PrinterFlags * p) 289{ 290 TScreen *screen = TScreenOf(xw); 291 int top = 0; 292 int bot = screen->max_row; 293 int was_open = initialized; 294 295 if (!screen->whichBuf) { 296 top = -screen->savedlines - screen->topline; 297 bot -= screen->topline; 298 } 299 300 TRACE(("xtermPrintEverything, rows %d..%d\n", top, bot)); 301 while (top <= bot) { 302 printLine(xw, top, PrintNewLine(), p); 303 ++top; 304 } 305 if (p->printer_formfeed) 306 charToPrinter(xw, '\f'); 307 308 if (!was_open || screen->printer_autoclose) { 309 closePrinter(xw); 310 } 311} 312 313static void 314send_CharSet(XtermWidget xw, LineData * ld) 315{ 316#if OPT_DEC_CHRSET 317 const char *msg = 0; 318 319 switch (GetLineDblCS(ld)) { 320 case CSET_SWL: 321 msg = "\033#5"; 322 break; 323 case CSET_DHL_TOP: 324 msg = "\033#3"; 325 break; 326 case CSET_DHL_BOT: 327 msg = "\033#4"; 328 break; 329 case CSET_DWL: 330 msg = "\033#6"; 331 break; 332 } 333 if (msg != 0) 334 stringToPrinter(xw, msg); 335#else 336 (void) xw; 337 (void) ld; 338#endif /* OPT_DEC_CHRSET */ 339} 340 341static void 342send_SGR(XtermWidget xw, unsigned attr, unsigned fg, unsigned bg) 343{ 344 char msg[80]; 345 strcpy(msg, "\033[0"); 346 if (attr & BOLD) 347 strcat(msg, ";1"); 348 if (attr & UNDERLINE) 349 strcat(msg, ";4"); /* typo? DEC documents this as '2' */ 350 if (attr & BLINK) 351 strcat(msg, ";5"); 352 if (attr & INVERSE) /* typo? DEC documents this as invisible */ 353 strcat(msg, ";7"); 354#if OPT_PRINT_COLORS 355 if (bg != NO_COLOR) { 356 sprintf(msg + strlen(msg), ";%u", (bg < 8) ? (40 + bg) : (92 + bg)); 357 } 358 if (fg != NO_COLOR) { 359#if OPT_PC_COLORS 360 if (TScreenOf(xw)->boldColors 361 && fg > 8 362 && (attr & BOLD) != 0) 363 fg -= 8; 364#endif 365 sprintf(msg + strlen(msg), ";%u", (fg < 8) ? (30 + fg) : (82 + fg)); 366 } 367#else 368 (void) bg; 369 (void) fg; 370#endif 371 strcat(msg, "m"); 372 stringToPrinter(xw, msg); 373} 374 375/* 376 * This implementation only knows how to write to a pipe. 377 */ 378static void 379charToPrinter(XtermWidget xw, unsigned chr) 380{ 381 TScreen *screen = TScreenOf(xw); 382 383 if (!initialized && xtermHasPrinter(xw)) { 384#if defined(VMS) 385 /* 386 * This implementation only knows how to write to a file. When the 387 * file is closed the print command executes. Print command must be of 388 * the form: 389 * print/que=name/delete [/otherflags]. 390 */ 391 Printer = fopen(VMS_TEMP_PRINT_FILE, "w"); 392#else 393 /* 394 * This implementation only knows how to write to a pipe. 395 */ 396 FILE *input; 397 int my_pipe[2]; 398 int c; 399 400 if (pipe(my_pipe)) 401 SysError(ERROR_FORK); 402 if ((Printer_pid = fork()) < 0) 403 SysError(ERROR_FORK); 404 405 if (Printer_pid == 0) { 406 TRACE_CLOSE(); 407 close(my_pipe[1]); /* printer is silent */ 408 close(screen->respond); 409 410 close(fileno(stdout)); 411 dup2(fileno(stderr), 1); 412 413 if (fileno(stderr) != 2) { 414 dup2(fileno(stderr), 2); 415 close(fileno(stderr)); 416 } 417 418 /* don't want privileges! */ 419 if (xtermResetIds(screen) < 0) 420 exit(1); 421 422 Printer = popen(screen->printer_command, "w"); 423 input = fdopen(my_pipe[0], "r"); 424 while ((c = fgetc(input)) != EOF) { 425 fputc(c, Printer); 426 if (isForm(c)) 427 fflush(Printer); 428 } 429 pclose(Printer); 430 exit(0); 431 } else { 432 close(my_pipe[0]); /* won't read from printer */ 433 Printer = fdopen(my_pipe[1], "w"); 434 TRACE(("opened printer from pid %d/%d\n", 435 (int) getpid(), (int) Printer_pid)); 436 } 437#endif 438 initialized++; 439 } 440 if (Printer != 0) { 441#if OPT_WIDE_CHARS 442 if (chr > 127) { 443 Char temp[10]; 444 *convertToUTF8(temp, chr) = 0; 445 fputs((char *) temp, Printer); 446 } else 447#endif 448 fputc((int) chr, Printer); 449 if (isForm(chr)) 450 fflush(Printer); 451 } 452} 453 454static void 455stringToPrinter(XtermWidget xw, const char *str) 456{ 457 while (*str) 458 charToPrinter(xw, CharOf(*str++)); 459} 460 461/* 462 * This module implements the MC (Media Copy) and related printing control 463 * sequences for VTxxx emulation. This is based on the description in the 464 * VT330/VT340 Programmer Reference Manual EK-VT3XX-TP-001 (Digital Equipment 465 * Corp., March 1987). 466 */ 467void 468xtermMediaControl(XtermWidget xw, int param, int private_seq) 469{ 470 TRACE(("MediaCopy param=%d, private=%d\n", param, private_seq)); 471 472 if (private_seq) { 473 switch (param) { 474 case 1: 475 printCursorLine(xw); 476 break; 477 case 4: 478 setPrinterControlMode(xw, 0); 479 break; 480 case 5: 481 setPrinterControlMode(xw, 1); 482 break; 483 case 10: /* VT320 */ 484 xtermPrintScreen(xw, False, getPrinterFlags(xw, NULL, 0)); 485 break; 486 case 11: /* VT320 */ 487 xtermPrintEverything(xw, getPrinterFlags(xw, NULL, 0)); 488 break; 489 } 490 } else { 491 switch (param) { 492 case -1: 493 case 0: 494 xtermPrintScreen(xw, True, getPrinterFlags(xw, NULL, 0)); 495 break; 496 case 4: 497 setPrinterControlMode(xw, 0); 498 break; 499 case 5: 500 setPrinterControlMode(xw, 2); 501 break; 502 } 503 } 504} 505 506/* 507 * When in autoprint mode, the printer prints a line from the screen when you 508 * move the cursor off that line with an LF, FF, or VT character, or an 509 * autowrap occurs. The printed line ends with a CR and the character (LF, FF 510 * or VT) that moved the cursor off the previous line. 511 */ 512void 513xtermAutoPrint(XtermWidget xw, unsigned chr) 514{ 515 TScreen *screen = TScreenOf(xw); 516 517 if (screen->printer_controlmode == 1) { 518 TRACE(("AutoPrint %d\n", chr)); 519 printLine(xw, screen->cursorp.row, chr, getPrinterFlags(xw, NULL, 0)); 520 if (Printer != 0) 521 fflush(Printer); 522 } 523} 524 525/* 526 * When in printer controller mode, the terminal sends received characters to 527 * the printer without displaying them on the screen. The terminal sends all 528 * characters and control sequences to the printer, except NUL, XON, XOFF, and 529 * the printer controller sequences. 530 * 531 * This function eats characters, returning 0 as long as it must buffer or 532 * divert to the printer. We're only invoked here when in printer controller 533 * mode, and handle the exit from that mode. 534 */ 535#define LB '[' 536 537int 538xtermPrinterControl(XtermWidget xw, int chr) 539{ 540 TScreen *screen = TScreenOf(xw); 541 /* *INDENT-OFF* */ 542 static struct { 543 Char seq[5]; 544 int active; 545 } tbl[] = { 546 { { ANSI_CSI, '5', 'i' }, 2 }, 547 { { ANSI_CSI, '4', 'i' }, 0 }, 548 { { ANSI_ESC, LB, '5', 'i' }, 2 }, 549 { { ANSI_ESC, LB, '4', 'i' }, 0 }, 550 }; 551 /* *INDENT-ON* */ 552 553 static Char bfr[10]; 554 static size_t length; 555 size_t n; 556 557 TRACE(("In printer:%04X\n", chr)); 558 559 switch (chr) { 560 case 0: 561 case CTRL('Q'): 562 case CTRL('S'): 563 return 0; /* ignored by application */ 564 565 case ANSI_CSI: 566 case ANSI_ESC: 567 case '[': 568 case '4': 569 case '5': 570 case 'i': 571 bfr[length++] = CharOf(chr); 572 for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); n++) { 573 size_t len = Strlen(tbl[n].seq); 574 575 if (length == len 576 && Strcmp(bfr, tbl[n].seq) == 0) { 577 setPrinterControlMode(xw, tbl[n].active); 578 if (screen->printer_autoclose 579 && screen->printer_controlmode == 0) 580 closePrinter(xw); 581 length = 0; 582 return 0; 583 } else if (len > length 584 && Strncmp(bfr, tbl[n].seq, length) == 0) { 585 return 0; 586 } 587 } 588 length--; 589 590 /* FALLTHRU */ 591 592 default: 593 for (n = 0; n < length; n++) 594 charToPrinter(xw, bfr[n]); 595 bfr[0] = CharOf(chr); 596 length = 1; 597 return 0; 598 } 599} 600 601/* 602 * If there is no printer command, we will ignore printer controls. 603 */ 604Bool 605xtermHasPrinter(XtermWidget xw) 606{ 607 TScreen *screen = TScreenOf(xw); 608 609 return (strlen(screen->printer_command) != 0); 610} 611 612#define showPrinterControlMode(mode) \ 613 (((mode) == 0) \ 614 ? "normal" \ 615 : ((mode) == 1 \ 616 ? "autoprint" \ 617 : "printer controller")) 618 619void 620setPrinterControlMode(XtermWidget xw, int mode) 621{ 622 TScreen *screen = TScreenOf(xw); 623 624 if (xtermHasPrinter(xw) 625 && screen->printer_controlmode != mode) { 626 TRACE(("%s %s mode\n", 627 (mode 628 ? "set" 629 : "reset"), 630 (mode 631 ? showPrinterControlMode(mode) 632 : showPrinterControlMode(screen->printer_controlmode)))); 633 screen->printer_controlmode = mode; 634 update_print_redir(); 635 } 636} 637 638PrinterFlags * 639getPrinterFlags(XtermWidget xw, String * params, Cardinal *param_count) 640{ 641 /* *INDENT-OFF* */ 642 static const struct { 643 const char *name; 644 unsigned offset; 645 int value; 646 } table[] = { 647 { "noFormFeed", XtOffsetOf(PrinterFlags, printer_formfeed), 0 }, 648 { "FormFeed", XtOffsetOf(PrinterFlags, printer_formfeed), 1 }, 649 { "noNewLine", XtOffsetOf(PrinterFlags, printer_newline), 0 }, 650 { "NewLine", XtOffsetOf(PrinterFlags, printer_newline), 1 }, 651 { "noAttrs", XtOffsetOf(PrinterFlags, print_attributes), 0 }, 652 { "monoAttrs", XtOffsetOf(PrinterFlags, print_attributes), 1 }, 653 { "colorAttrs", XtOffsetOf(PrinterFlags, print_attributes), 2 }, 654 }; 655 /* *INDENT-ON* */ 656 657 TScreen *screen = TScreenOf(xw); 658 PrinterFlags *result = &(screen->printer_flags); 659 660 TRACE(("getPrinterFlags %d params\n", param_count ? *param_count : 0)); 661 662 result->printer_extent = screen->printer_extent; 663 result->printer_formfeed = screen->printer_formfeed; 664 result->printer_newline = screen->printer_newline; 665 result->print_attributes = screen->print_attributes; 666 667 if (param_count != 0 && *param_count != 0) { 668 Cardinal j; 669 unsigned k; 670 for (j = 0; j < *param_count; ++j) { 671 TRACE(("param%d:%s\n", j, params[j])); 672 for (k = 0; k < XtNumber(table); ++k) { 673 if (!x_strcasecmp(params[j], table[k].name)) { 674 int *ptr = (int *) (void *) ((char *) result + table[k].offset); 675 TRACE(("...PrinterFlags(%s) %d->%d\n", 676 table[k].name, 677 *ptr, 678 table[k].value)); 679 *ptr = table[k].value; 680 break; 681 } 682 } 683 } 684 } 685 686 return result; 687} 688