print.c revision 956cc18d
1/* $XTermId: print.c,v 1.109 2009/09/10 09:06:30 tom Exp $ */ 2 3/************************************************************ 4 5Copyright 1997-2007,2009 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 40#include <stdio.h> 41 42#undef CTRL 43#define CTRL(c) ((c) & 0x1f) 44 45#define SHIFT_IN '\017' 46#define SHIFT_OUT '\016' 47 48#define CSET_IN 'A' 49#define CSET_OUT '0' 50 51#define isForm(c) ((c) == '\r' || (c) == '\n' || (c) == '\f') 52#define Strlen(a) strlen((char *)a) 53#define Strcmp(a,b) strcmp((char *)a,(char *)b) 54#define Strncmp(a,b,c) strncmp((char *)a,(char *)b,c) 55 56#ifdef VMS 57#define VMS_TEMP_PRINT_FILE "sys$scratch:xterm_print.txt" 58#endif 59 60static void charToPrinter(XtermWidget /* xw */ , 61 unsigned /* chr */ ); 62static void printLine(XtermWidget /* xw */ , 63 int /* row */ , 64 unsigned /* chr */ ); 65static void send_CharSet(XtermWidget /* xw */ , 66 LineData * /* ld */ ); 67static void send_SGR(XtermWidget /* xw */ , 68 unsigned /* attr */ , 69 unsigned /* fg */ , 70 unsigned /* bg */ ); 71static void stringToPrinter(XtermWidget /* xw */ , 72 char * /*str */ ); 73 74static FILE *Printer; 75static pid_t Printer_pid; 76static int initialized; 77 78static void 79closePrinter(XtermWidget xw GCC_UNUSED) 80{ 81 if (xtermHasPrinter(xw) != 0) { 82#ifdef VMS 83 TScreen *screen = TScreenOf(xw); 84 85 char pcommand[256]; 86 (void) sprintf(pcommand, "%s %s;", 87 screen->printer_command, 88 VMS_TEMP_PRINT_FILE); 89#endif 90 91 if (Printer != 0) { 92 fclose(Printer); 93 TRACE(("closed printer, waiting...\n")); 94#ifdef VMS /* This is a quick hack, really should use 95 spawn and check status or system services 96 and go straight to the queue */ 97 (void) system(pcommand); 98#else /* VMS */ 99 while (nonblocking_wait() > 0) 100#endif /* VMS */ 101 ; 102 Printer = 0; 103 initialized = 0; 104 TRACE(("closed printer\n")); 105 } 106 } 107} 108 109static void 110printCursorLine(XtermWidget xw) 111{ 112 TScreen *screen = TScreenOf(xw); 113 114 TRACE(("printCursorLine\n")); 115 printLine(xw, screen->cur_row, '\n'); 116} 117 118#define NO_COLOR ((unsigned)-1) 119 120/* 121 * DEC's manual doesn't document whether trailing blanks are removed, or what 122 * happens with a line that is entirely blank. This function prints the 123 * characters that xterm would allow as a selection (which may include blanks). 124 */ 125static void 126printLine(XtermWidget xw, int row, unsigned chr) 127{ 128 TScreen *screen = TScreenOf(xw); 129 int inx = ROW2INX(screen, row); 130 LineData *ld; 131 Char attr = 0; 132 unsigned ch; 133 int last = MaxCols(screen); 134 int col; 135#if OPT_ISO_COLORS && OPT_PRINT_COLORS 136 CellColor *fb = 0; 137#define ColorOf(ld,col) (ld->color[col]) 138#endif 139 unsigned fg = NO_COLOR, last_fg = NO_COLOR; 140 unsigned bg = NO_COLOR, last_bg = NO_COLOR; 141 int cs = CSET_IN; 142 int last_cs = CSET_IN; 143 144 ld = getLineData(screen, inx); 145 TRACE(("printLine(row=%d/%d, top=%d:%d, chr=%d):%s\n", 146 row, ROW2INX(screen, row), screen->topline, screen->max_row, chr, 147 visibleIChars(ld->charData, (unsigned) last))); 148 149 if_OPT_ISO_COLORS(screen, { 150 fb = ld->color; 151 }); 152 while (last > 0) { 153 if ((ld->attribs[last - 1] & CHARDRAWN) == 0) 154 last--; 155 else 156 break; 157 } 158 if (last) { 159 if (screen->print_attributes) { 160 send_CharSet(xw, ld); 161 send_SGR(xw, 0, NO_COLOR, NO_COLOR); 162 } 163 for (col = 0; col < last; col++) { 164 ch = ld->charData[col]; 165#if OPT_PRINT_COLORS 166 if (screen->colorMode) { 167 if (screen->print_attributes > 1) { 168 fg = (ld->attribs[col] & FG_COLOR) 169 ? extract_fg(xw, ColorOf(ld, col), ld->attribs[col]) 170 : NO_COLOR; 171 bg = (ld->attribs[col] & BG_COLOR) 172 ? extract_bg(xw, ColorOf(ld, col), ld->attribs[col]) 173 : NO_COLOR; 174 } 175 } 176#endif 177 if ((((ld->attribs[col] & SGR_MASK) != attr) 178#if OPT_PRINT_COLORS 179 || (last_fg != fg) || (last_bg != bg) 180#endif 181 ) 182 && ch) { 183 attr = CharOf(ld->attribs[col] & SGR_MASK); 184 last_fg = fg; 185 last_bg = bg; 186 if (screen->print_attributes) 187 send_SGR(xw, attr, fg, bg); 188 } 189 190 if (ch == 0) 191 ch = ' '; 192 193#if OPT_WIDE_CHARS 194 if (screen->utf8_mode) 195 cs = CSET_IN; 196 else 197#endif 198 cs = (ch >= ' ' && ch != ANSI_DEL) ? CSET_IN : CSET_OUT; 199 if (last_cs != cs) { 200 if (screen->print_attributes) { 201 charToPrinter(xw, 202 (unsigned) ((cs == CSET_OUT) 203 ? SHIFT_OUT 204 : SHIFT_IN)); 205 } 206 last_cs = cs; 207 } 208 209 /* FIXME: we shouldn't have to map back from the 210 * alternate character set, except that the 211 * corresponding charset information is not encoded 212 * into the CSETS array. 213 */ 214 charToPrinter(xw, 215 ((cs == CSET_OUT) 216 ? (ch == ANSI_DEL ? 0x5f : (ch + 0x5f)) 217 : ch)); 218 if_OPT_WIDE_CHARS(screen, { 219 size_t off; 220 for_each_combData(off, ld) { 221 ch = ld->combData[off][col]; 222 if (ch == 0) 223 break; 224 charToPrinter(xw, ch); 225 } 226 }); 227 } 228 if (screen->print_attributes) { 229 send_SGR(xw, 0, NO_COLOR, NO_COLOR); 230 if (cs != CSET_IN) 231 charToPrinter(xw, SHIFT_IN); 232 } 233 } 234 if (screen->print_attributes) 235 charToPrinter(xw, '\r'); 236 charToPrinter(xw, chr); 237 238 return; 239} 240 241void 242xtermPrintScreen(XtermWidget xw, Bool use_DECPEX) 243{ 244 if (XtIsRealized((Widget) xw)) { 245 TScreen *screen = TScreenOf(xw); 246 Bool extent = (use_DECPEX && screen->printer_extent); 247 int top = extent ? 0 : screen->top_marg; 248 int bot = extent ? screen->max_row : screen->bot_marg; 249 int was_open = initialized; 250 251 TRACE(("xtermPrintScreen, rows %d..%d\n", top, bot)); 252 253 while (top <= bot) 254 printLine(xw, top++, '\n'); 255 if (screen->printer_formfeed) 256 charToPrinter(xw, '\f'); 257 258 if (!was_open || screen->printer_autoclose) { 259 closePrinter(xw); 260 } 261 } else { 262 Bell(XkbBI_MinorError, 0); 263 } 264} 265 266/* 267 * If the alternate screen is active, we'll print only that. Otherwise, print 268 * the normal screen plus all scrolled-back lines. The distinction is made 269 * because the normal screen's buffer is part of the overall scrollback buffer. 270 */ 271void 272xtermPrintEverything(XtermWidget xw) 273{ 274 TScreen *screen = TScreenOf(xw); 275 int top = 0; 276 int bot = screen->max_row; 277 int was_open = initialized; 278 279 if (!screen->whichBuf) { 280 top = -screen->savedlines - screen->topline; 281 bot -= screen->topline; 282 } 283 284 TRACE(("xtermPrintEverything, rows %d..%d\n", top, bot)); 285 while (top <= bot) 286 printLine(xw, top++, '\n'); 287 if (screen->printer_formfeed) 288 charToPrinter(xw, '\f'); 289 290 if (!was_open || screen->printer_autoclose) { 291 closePrinter(xw); 292 } 293} 294 295static void 296send_CharSet(XtermWidget xw, LineData * ld) 297{ 298#if OPT_DEC_CHRSET 299 char *msg = 0; 300 301 switch (GetLineDblCS(ld)) { 302 case CSET_SWL: 303 msg = "\033#5"; 304 break; 305 case CSET_DHL_TOP: 306 msg = "\033#3"; 307 break; 308 case CSET_DHL_BOT: 309 msg = "\033#4"; 310 break; 311 case CSET_DWL: 312 msg = "\033#6"; 313 break; 314 } 315 if (msg != 0) 316 stringToPrinter(xw, msg); 317#else 318 (void) xw; 319 (void) ld; 320#endif /* OPT_DEC_CHRSET */ 321} 322 323static void 324send_SGR(XtermWidget xw, unsigned attr, unsigned fg, unsigned bg) 325{ 326 char msg[80]; 327 strcpy(msg, "\033[0"); 328 if (attr & BOLD) 329 strcat(msg, ";1"); 330 if (attr & UNDERLINE) 331 strcat(msg, ";4"); /* typo? DEC documents this as '2' */ 332 if (attr & BLINK) 333 strcat(msg, ";5"); 334 if (attr & INVERSE) /* typo? DEC documents this as invisible */ 335 strcat(msg, ";7"); 336#if OPT_PRINT_COLORS 337 if (bg != NO_COLOR) { 338 sprintf(msg + strlen(msg), ";%u", (bg < 8) ? (40 + bg) : (92 + bg)); 339 } 340 if (fg != NO_COLOR) { 341#if OPT_PC_COLORS 342 if (xw->screen.boldColors 343 && fg > 8 344 && (attr & BOLD) != 0) 345 fg -= 8; 346#endif 347 sprintf(msg + strlen(msg), ";%u", (fg < 8) ? (30 + fg) : (82 + fg)); 348 } 349#else 350 (void) bg; 351 (void) fg; 352#endif 353 strcat(msg, "m"); 354 stringToPrinter(xw, msg); 355} 356 357/* 358 * This implementation only knows how to write to a pipe. 359 */ 360static void 361charToPrinter(XtermWidget xw, unsigned chr) 362{ 363 TScreen *screen = TScreenOf(xw); 364 365 if (!initialized && xtermHasPrinter(xw)) { 366#if defined(VMS) 367 /* 368 * This implementation only knows how to write to a file. When the 369 * file is closed the print command executes. Print command must be of 370 * the form: 371 * print/que=name/delete [/otherflags]. 372 */ 373 Printer = fopen(VMS_TEMP_PRINT_FILE, "w"); 374#else 375 /* 376 * This implementation only knows how to write to a pipe. 377 */ 378 FILE *input; 379 int my_pipe[2]; 380 int c; 381 382 if (pipe(my_pipe)) 383 SysError(ERROR_FORK); 384 if ((Printer_pid = fork()) < 0) 385 SysError(ERROR_FORK); 386 387 if (Printer_pid == 0) { 388 TRACE(((char *) 0)); 389 close(my_pipe[1]); /* printer is silent */ 390 close(screen->respond); 391 392 close(fileno(stdout)); 393 dup2(fileno(stderr), 1); 394 395 if (fileno(stderr) != 2) { 396 dup2(fileno(stderr), 2); 397 close(fileno(stderr)); 398 } 399 400 /* don't want privileges! */ 401 if (xtermResetIds(screen) < 0) 402 exit(1); 403 404 Printer = popen(screen->printer_command, "w"); 405 input = fdopen(my_pipe[0], "r"); 406 while ((c = fgetc(input)) != EOF) { 407 fputc(c, Printer); 408 if (isForm(c)) 409 fflush(Printer); 410 } 411 pclose(Printer); 412 exit(0); 413 } else { 414 close(my_pipe[0]); /* won't read from printer */ 415 Printer = fdopen(my_pipe[1], "w"); 416 TRACE(("opened printer from pid %d/%d\n", 417 (int) getpid(), Printer_pid)); 418 } 419#endif 420 initialized++; 421 } 422 if (Printer != 0) { 423#if OPT_WIDE_CHARS 424 if (chr > 127) { 425 Char temp[10]; 426 *convertToUTF8(temp, chr) = 0; 427 fputs((char *) temp, Printer); 428 } else 429#endif 430 fputc((int) chr, Printer); 431 if (isForm(chr)) 432 fflush(Printer); 433 } 434} 435 436static void 437stringToPrinter(XtermWidget xw, char *str) 438{ 439 while (*str) 440 charToPrinter(xw, CharOf(*str++)); 441} 442 443/* 444 * This module implements the MC (Media Copy) and related printing control 445 * sequences for VTxxx emulation. This is based on the description in the 446 * VT330/VT340 Programmer Reference Manual EK-VT3XX-TP-001 (Digital Equipment 447 * Corp., March 1987). 448 */ 449void 450xtermMediaControl(XtermWidget xw, int param, int private_seq) 451{ 452 TRACE(("MediaCopy param=%d, private=%d\n", param, private_seq)); 453 454 if (private_seq) { 455 switch (param) { 456 case 1: 457 printCursorLine(xw); 458 break; 459 case 4: 460 setPrinterControlMode(xw, 0); 461 break; 462 case 5: 463 setPrinterControlMode(xw, 1); 464 break; 465 case 10: /* VT320 */ 466 xtermPrintScreen(xw, False); 467 break; 468 case 11: /* VT320 */ 469 xtermPrintEverything(xw); 470 break; 471 } 472 } else { 473 switch (param) { 474 case -1: 475 case 0: 476 xtermPrintScreen(xw, True); 477 break; 478 case 4: 479 setPrinterControlMode(xw, 0); 480 break; 481 case 5: 482 setPrinterControlMode(xw, 2); 483 break; 484 } 485 } 486} 487 488/* 489 * When in autoprint mode, the printer prints a line from the screen when you 490 * move the cursor off that line with an LF, FF, or VT character, or an 491 * autowrap occurs. The printed line ends with a CR and the character (LF, FF 492 * or VT) that moved the cursor off the previous line. 493 */ 494void 495xtermAutoPrint(XtermWidget xw, unsigned chr) 496{ 497 TScreen *screen = TScreenOf(xw); 498 499 if (screen->printer_controlmode == 1) { 500 TRACE(("AutoPrint %d\n", chr)); 501 printLine(xw, screen->cursorp.row, chr); 502 if (Printer != 0) 503 fflush(Printer); 504 } 505} 506 507/* 508 * When in printer controller mode, the terminal sends received characters to 509 * the printer without displaying them on the screen. The terminal sends all 510 * characters and control sequences to the printer, except NUL, XON, XOFF, and 511 * the printer controller sequences. 512 * 513 * This function eats characters, returning 0 as long as it must buffer or 514 * divert to the printer. We're only invoked here when in printer controller 515 * mode, and handle the exit from that mode. 516 */ 517#define LB '[' 518 519int 520xtermPrinterControl(XtermWidget xw, int chr) 521{ 522 TScreen *screen = TScreenOf(xw); 523 /* *INDENT-OFF* */ 524 static struct { 525 Char seq[5]; 526 int active; 527 } tbl[] = { 528 { { ANSI_CSI, '5', 'i' }, 2 }, 529 { { ANSI_CSI, '4', 'i' }, 0 }, 530 { { ANSI_ESC, LB, '5', 'i' }, 2 }, 531 { { ANSI_ESC, LB, '4', 'i' }, 0 }, 532 }; 533 /* *INDENT-ON* */ 534 535 static Char bfr[10]; 536 static size_t length; 537 size_t n; 538 539 TRACE(("In printer:%04X\n", chr)); 540 541 switch (chr) { 542 case 0: 543 case CTRL('Q'): 544 case CTRL('S'): 545 return 0; /* ignored by application */ 546 547 case ANSI_CSI: 548 case ANSI_ESC: 549 case '[': 550 case '4': 551 case '5': 552 case 'i': 553 bfr[length++] = CharOf(chr); 554 for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); n++) { 555 size_t len = Strlen(tbl[n].seq); 556 557 if (length == len 558 && Strcmp(bfr, tbl[n].seq) == 0) { 559 setPrinterControlMode(xw, tbl[n].active); 560 if (screen->printer_autoclose 561 && screen->printer_controlmode == 0) 562 closePrinter(xw); 563 length = 0; 564 return 0; 565 } else if (len > length 566 && Strncmp(bfr, tbl[n].seq, length) == 0) { 567 return 0; 568 } 569 } 570 length--; 571 572 /* FALLTHRU */ 573 574 default: 575 for (n = 0; n < length; n++) 576 charToPrinter(xw, bfr[n]); 577 bfr[0] = CharOf(chr); 578 length = 1; 579 return 0; 580 } 581} 582 583/* 584 * If there is no printer command, we will ignore printer controls. 585 */ 586Bool 587xtermHasPrinter(XtermWidget xw) 588{ 589 TScreen *screen = TScreenOf(xw); 590 591 return (strlen(screen->printer_command) != 0); 592} 593 594#define showPrinterControlMode(mode) \ 595 (((mode) == 0) \ 596 ? "normal" \ 597 : ((mode) == 1 \ 598 ? "autoprint" \ 599 : "printer controller")) 600 601void 602setPrinterControlMode(XtermWidget xw, int mode) 603{ 604 if (xtermHasPrinter(xw) 605 && xw->screen.printer_controlmode != mode) { 606 TRACE(("%s %s mode\n", 607 (mode 608 ? "set" 609 : "reset"), 610 (mode 611 ? showPrinterControlMode(mode) 612 : showPrinterControlMode(xw->screen.printer_controlmode)))); 613 xw->screen.printer_controlmode = mode; 614 update_print_redir(); 615 } 616} 617