Home | History | Annotate | Line # | Download | only in dist
      1 /* $XTermId: print.c,v 1.180 2025/01/05 20:36:49 tom Exp $ */
      2 
      3 /*
      4  * Copyright 1997-2024,2025 by Thomas E. Dickey
      5  *
      6  *                         All Rights Reserved
      7  *
      8  * Permission is hereby granted, free of charge, to any person obtaining a
      9  * copy of this software and associated documentation files (the
     10  * "Software"), to deal in the Software without restriction, including
     11  * without limitation the rights to use, copy, modify, merge, publish,
     12  * distribute, sublicense, and/or sell copies of the Software, and to
     13  * permit persons to whom the Software is furnished to do so, subject to
     14  * the following conditions:
     15  *
     16  * The above copyright notice and this permission notice shall be included
     17  * in all copies or substantial portions of the Software.
     18  *
     19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     20  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     22  * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
     23  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     24  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     25  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     26  *
     27  * Except as contained in this notice, the name(s) of the above copyright
     28  * holders shall not be used in advertising or otherwise to promote the
     29  * sale, use or other dealings in this Software without prior written
     30  * authorization.
     31  */
     32 
     33 #include <xterm.h>
     34 #include <data.h>
     35 #include <menu.h>
     36 #include <error.h>
     37 #include <xstrings.h>
     38 
     39 #include <stdio.h>
     40 #include <sys/stat.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((const char *)a)
     53 #define Strcmp(a,b)    strcmp((const char *)a,(const char *)b)
     54 #define Strncmp(a,b,c) strncmp((const char *)a,(const char *)b,c)
     55 
     56 #define SPS PrinterOf(screen)
     57 
     58 static void charToPrinter(XtermWidget /* xw */ ,
     59 			  unsigned /* chr */ );
     60 static void printLine(XtermWidget /* xw */ ,
     61 		      int /* row */ ,
     62 		      unsigned /* chr */ ,
     63 		      PrinterFlags * /* p */ );
     64 static void send_CharSet(XtermWidget /* xw */ ,
     65 			 LineData * /* ld */ );
     66 static void send_SGR(XtermWidget /* xw */ ,
     67 		     unsigned /* attr */ ,
     68 		     unsigned /* fg */ ,
     69 		     unsigned /* bg */ );
     70 static void stringToPrinter(XtermWidget /* xw */ ,
     71 			    const char * /*str */ );
     72 
     73 #if OPT_PRINT_GRAPHICS
     74 static void setGraphicsPrintToHost(XtermWidget /* xw */ ,
     75 				   int /* enabled */ );
     76 #else
     77 #define setGraphicsPrintToHost(xw, enabled)	/* nothing */
     78 #endif
     79 
     80 static void
     81 closePrinter(XtermWidget xw)
     82 {
     83     TScreen *screen = TScreenOf(xw);
     84     if (SPS.fp != NULL) {
     85 	if (SPS.toFile) {
     86 	    fclose(SPS.fp);
     87 	    SPS.fp = NULL;
     88 	} else if (xtermHasPrinter(xw) != 0) {
     89 
     90 	    DEBUG_MSG("closePrinter\n");
     91 	    pclose(SPS.fp);
     92 	    TRACE(("closed printer, waiting...\n"));
     93 	    while (nonblocking_wait() > 0) {
     94 		;
     95 	    }
     96 	    SPS.fp = NULL;
     97 	    SPS.isOpen = False;
     98 	    TRACE(("closed printer\n"));
     99 	    DEBUG_MSG("...closePrinter (done)\n");
    100 	}
    101     }
    102 }
    103 
    104 static void
    105 printCursorLine(XtermWidget xw)
    106 {
    107     TScreen *screen = TScreenOf(xw);
    108 
    109     TRACE(("printCursorLine\n"));
    110     printLine(xw, screen->cur_row, '\n', getPrinterFlags(xw, NULL, NULL));
    111 }
    112 
    113 /*
    114  * DEC's manual doesn't document whether trailing blanks are removed, or what
    115  * happens with a line that is entirely blank.  This function prints the
    116  * characters that xterm would allow as a selection (which may include blanks).
    117  */
    118 static void
    119 printLine(XtermWidget xw, int row, unsigned chr, PrinterFlags *p)
    120 {
    121     TScreen *screen = TScreenOf(xw);
    122     int inx = ROW2INX(screen, row);
    123     LineData *ld;
    124     int last = MaxCols(screen);
    125 #if OPT_ISO_COLORS && OPT_PRINT_COLORS
    126 #define ColorOf(ld,col) (ld->color[col])
    127 #endif
    128     Pixel fg = NO_COLOR;
    129     Pixel bg = NO_COLOR;
    130 #if OPT_PRINT_COLORS
    131     Pixel last_fg = NO_COLOR;
    132     Pixel last_bg = NO_COLOR;
    133 #endif
    134 
    135     ld = getLineData(screen, inx);
    136     if (ld == NULL)
    137 	return;
    138 
    139     TRACE(("printLine(row=%d/%d, top=%d:%d, chr=%d):%s\n",
    140 	   row, ROW2INX(screen, row), screen->topline, screen->max_row, chr,
    141 	   visibleIChars(ld->charData, (unsigned) last)));
    142 
    143     while (last > 0) {
    144 	if ((ld->attribs[last - 1] & CHARDRAWN) == 0)
    145 	    last--;
    146 	else
    147 	    break;
    148     }
    149 
    150     if (last) {
    151 	int col;
    152 	int cs = CSET_IN;
    153 	int last_cs = CSET_IN;
    154 
    155 	if (p->print_attributes) {
    156 	    send_CharSet(xw, ld);
    157 	    send_SGR(xw, 0, NO_COLOR, NO_COLOR);
    158 	}
    159 	for (col = 0; col < last; col++) {
    160 	    IAttr attr = 0;
    161 	    unsigned ch = ld->charData[col];
    162 #if OPT_PRINT_COLORS
    163 	    if (screen->colorMode) {
    164 		if (p->print_attributes > 1) {
    165 		    fg = (ld->attribs[col] & FG_COLOR)
    166 			? extract_fg(xw, ColorOf(ld, col), ld->attribs[col])
    167 			: NO_COLOR;
    168 		    bg = (ld->attribs[col] & BG_COLOR)
    169 			? extract_bg(xw, ColorOf(ld, col), ld->attribs[col])
    170 			: NO_COLOR;
    171 		}
    172 	    }
    173 #endif
    174 	    if ((((ld->attribs[col] & ATTRIBUTES) != attr)
    175 #if OPT_PRINT_COLORS
    176 		 || (last_fg != fg) || (last_bg != bg)
    177 #endif
    178 		)
    179 		&& ch) {
    180 		attr = (IAttr) (ld->attribs[col] & ATTRIBUTES);
    181 #if OPT_PRINT_COLORS
    182 		last_fg = fg;
    183 		last_bg = bg;
    184 #endif
    185 		if (p->print_attributes)
    186 		    send_SGR(xw, attr, (unsigned) fg, (unsigned) bg);
    187 	    }
    188 
    189 	    if (ch == 0)
    190 		ch = ' ';
    191 
    192 #if OPT_WIDE_CHARS
    193 	    if (screen->utf8_mode)
    194 		cs = CSET_IN;
    195 	    else
    196 #endif
    197 		cs = (ch >= ' ' && ch != ANSI_DEL) ? CSET_IN : CSET_OUT;
    198 	    if (last_cs != cs) {
    199 		if (p->print_attributes) {
    200 		    charToPrinter(xw,
    201 				  (unsigned) ((cs == CSET_OUT)
    202 					      ? SHIFT_OUT
    203 					      : SHIFT_IN));
    204 		}
    205 		last_cs = cs;
    206 	    }
    207 
    208 	    /* FIXME:  we shouldn't have to map back from the
    209 	     * alternate character set, except that the
    210 	     * corresponding charset information is not encoded
    211 	     * into the CSETS array.
    212 	     */
    213 	    charToPrinter(xw,
    214 			  ((cs == CSET_OUT)
    215 			   ? (ch == ANSI_DEL ? 0x5f : (ch + 0x5f))
    216 			   : ch));
    217 	    if_OPT_WIDE_CHARS(screen, {
    218 		size_t off;
    219 		for_each_combData(off, ld) {
    220 		    ch = ld->combData[off][col];
    221 		    if (ch == 0)
    222 			break;
    223 		    charToPrinter(xw, ch);
    224 		}
    225 	    });
    226 	}
    227 	if (p->print_attributes) {
    228 	    send_SGR(xw, 0, NO_COLOR, NO_COLOR);
    229 	    if (cs != CSET_IN)
    230 		charToPrinter(xw, SHIFT_IN);
    231 	}
    232     }
    233 
    234     /* finish line (protocol for attributes needs a CR */
    235     if (p->print_attributes)
    236 	charToPrinter(xw, '\r');
    237 
    238     if (chr && !(p->printer_newline)) {
    239 	if (LineTstWrapped(ld))
    240 	    chr = '\0';
    241     }
    242 
    243     if (chr)
    244 	charToPrinter(xw, chr);
    245 
    246     return;
    247 }
    248 
    249 #define PrintNewLine() (unsigned) (((top < bot) || p->printer_newline) ? '\n' : '\0')
    250 
    251 static void
    252 printLines(XtermWidget xw, int top, int bot, PrinterFlags *p)
    253 {
    254     TRACE(("printLines, rows %d..%d\n", top, bot));
    255     while (top <= bot) {
    256 	printLine(xw, top, PrintNewLine(), p);
    257 	++top;
    258     }
    259 }
    260 
    261 void
    262 xtermPrintScreen(XtermWidget xw, Bool use_DECPEX, PrinterFlags *p)
    263 {
    264     if (XtIsRealized((Widget) xw)) {
    265 	TScreen *screen = TScreenOf(xw);
    266 	Bool extent = (use_DECPEX && p->printer_extent);
    267 	Boolean was_open = SPS.isOpen;
    268 
    269 	printLines(xw,
    270 		   extent ? 0 : screen->top_marg,
    271 		   extent ? screen->max_row : screen->bot_marg,
    272 		   p);
    273 	if (p->printer_formfeed)
    274 	    charToPrinter(xw, '\f');
    275 
    276 	if (!was_open || SPS.printer_autoclose) {
    277 	    closePrinter(xw);
    278 	}
    279     } else {
    280 	Bell(xw, XkbBI_MinorError, 0);
    281     }
    282 }
    283 
    284 /*
    285  * If p->print_everything is zero, use this behavior:
    286  * If the alternate screen is active, we'll print only that.  Otherwise, print
    287  * the normal screen plus all scrolled-back lines.  The distinction is made
    288  * because the normal screen's buffer is part of the overall scrollback buffer.
    289  *
    290  * Otherwise, decode bits:
    291  *	1 = current screen
    292  *	2 = normal screen
    293  *	4 = alternate screen
    294  *	8 = saved lines
    295  */
    296 void
    297 xtermPrintEverything(XtermWidget xw, PrinterFlags *p)
    298 {
    299     TScreen *screen = TScreenOf(xw);
    300     Boolean was_open = SPS.isOpen;
    301     int save_which = screen->whichBuf;
    302 
    303     DEBUG_MSG("xtermPrintEverything\n");
    304 
    305     if (p->print_everything) {
    306 	int done_which = 0;
    307 
    308 	if (p->print_everything & 8) {
    309 	    printLines(xw, -screen->savedlines, -(screen->topline + 1), p);
    310 	}
    311 	if (p->print_everything & 4) {
    312 	    SwitchBufPtrs(xw, 1);
    313 	    done_which |= 2;
    314 	    printLines(xw, 0, screen->max_row, p);
    315 	    SwitchBufPtrs(xw, save_which);
    316 	}
    317 	if (p->print_everything & 2) {
    318 	    SwitchBufPtrs(xw, 0);
    319 	    done_which |= 1;
    320 	    printLines(xw, 0, screen->max_row, p);
    321 	    SwitchBufPtrs(xw, save_which);
    322 	}
    323 	if (p->print_everything & 1) {
    324 	    if (!(done_which & (1 << screen->whichBuf))) {
    325 		printLines(xw, 0, screen->max_row, p);
    326 	    }
    327 	}
    328     } else {
    329 	int top = 0;
    330 	int bot = screen->max_row;
    331 	if (!screen->whichBuf) {
    332 	    top = -screen->savedlines - screen->topline;
    333 	    bot -= screen->topline;
    334 	}
    335 	printLines(xw, top, bot, p);
    336     }
    337     if (p->printer_formfeed)
    338 	charToPrinter(xw, '\f');
    339 
    340     if (!was_open || SPS.printer_autoclose) {
    341 	closePrinter(xw);
    342     }
    343 }
    344 
    345 static void
    346 send_CharSet(XtermWidget xw, LineData *ld)
    347 {
    348 #if OPT_DEC_CHRSET
    349     const char *msg = NULL;
    350 
    351     switch (GetLineDblCS(ld)) {
    352     case CSET_SWL:
    353 	msg = "\033#5";
    354 	break;
    355     case CSET_DHL_TOP:
    356 	msg = "\033#3";
    357 	break;
    358     case CSET_DHL_BOT:
    359 	msg = "\033#4";
    360 	break;
    361     case CSET_DWL:
    362 	msg = "\033#6";
    363 	break;
    364     }
    365     if (msg != NULL)
    366 	stringToPrinter(xw, msg);
    367 #else
    368     (void) xw;
    369     (void) ld;
    370 #endif /* OPT_DEC_CHRSET */
    371 }
    372 
    373 static void
    374 send_SGR(XtermWidget xw, unsigned attr, unsigned fg, unsigned bg)
    375 {
    376     char msg[80];
    377 
    378 #if OPT_ISO_COLORS && OPT_PC_COLORS
    379     if ((attr & FG_COLOR) && (fg != NO_COLOR)) {
    380 	if (TScreenOf(xw)->boldColors
    381 	    && fg > 8
    382 	    && fg < 16
    383 	    && (attr & BOLD) != 0)
    384 	    fg -= 8;
    385     }
    386 #endif
    387     strcpy(msg, "\033[");
    388     xtermFormatSGR(xw, msg + strlen(msg), attr, (int) fg, (int) bg);
    389     strcat(msg, "m");
    390     stringToPrinter(xw, msg);
    391 }
    392 
    393 /*
    394  * This implementation only knows how to write to a pipe.
    395  */
    396 static void
    397 charToPrinter(XtermWidget xw, unsigned chr)
    398 {
    399     TScreen *screen = TScreenOf(xw);
    400 
    401     if (!screen->print_rawchars) {
    402 #if OPT_WIDE_CHARS
    403 	if (screen->wide_chars && screen->utf8_mode) {
    404 	    if (chr == UCS_REPL) {
    405 		stringToPrinter(xw, screen->default_string);
    406 		return;
    407 	    }
    408 	}
    409 #endif
    410 	if (is_NON_CHAR(chr))
    411 	    return;
    412     }
    413 
    414     if (!SPS.isOpen && (SPS.toFile || xtermHasPrinter(xw))) {
    415 	switch (SPS.toFile) {
    416 	    /*
    417 	     * write to a pipe.
    418 	     */
    419 	case False:
    420 	    {
    421 		int my_pipe[2];
    422 		pid_t my_pid;
    423 
    424 		if (pipe(my_pipe))
    425 		    SysError(ERROR_FORK);
    426 		if ((my_pid = fork()) < 0)
    427 		    SysError(ERROR_FORK);
    428 
    429 		if (my_pid == 0) {
    430 		    DEBUG_MSG("charToPrinter: subprocess for printer\n");
    431 		    TRACE_CLOSE();
    432 		    close(my_pipe[1]);	/* printer is silent */
    433 		    close(screen->respond);
    434 
    435 		    close(fileno(stdout));
    436 		    dup2(fileno(stderr), 1);
    437 
    438 		    if (fileno(stderr) != 2) {
    439 			dup2(fileno(stderr), 2);
    440 			close(fileno(stderr));
    441 		    }
    442 
    443 		    /* don't want privileges! */
    444 		    if (xtermResetIds(screen) < 0)
    445 			exit(ERROR_MISC);
    446 
    447 		    SPS.fp = popen(SPS.printer_command, "w");
    448 		    if (SPS.fp != NULL) {
    449 			FILE *input;
    450 			DEBUG_MSG("charToPrinter: opened pipe to printer\n");
    451 			if ((input = fdopen(my_pipe[0], "r")) != NULL) {
    452 			    clearerr(input);
    453 
    454 			    for (;;) {
    455 				int c;
    456 
    457 				if (ferror(input)) {
    458 				    DEBUG_MSG("charToPrinter: break on ferror\n");
    459 				    break;
    460 				} else if (feof(input)) {
    461 				    DEBUG_MSG("charToPrinter: break on feof\n");
    462 				    break;
    463 				} else if ((c = fgetc(input)) == EOF) {
    464 				    DEBUG_MSG("charToPrinter: break on EOF\n");
    465 				    break;
    466 				}
    467 				fputc(c, SPS.fp);
    468 				if (isForm(c))
    469 				    fflush(SPS.fp);
    470 			    }
    471 			}
    472 			DEBUG_MSG("charToPrinter: calling pclose\n");
    473 			pclose(SPS.fp);
    474 			if (input)
    475 			    fclose(input);
    476 		    }
    477 		    exit(0);
    478 		} else {
    479 		    close(my_pipe[0]);	/* won't read from printer */
    480 		    if ((SPS.fp = fdopen(my_pipe[1], "w")) != NULL) {
    481 			DEBUG_MSG("charToPrinter: opened printer in parent\n");
    482 			TRACE(("opened printer from pid %d/%d\n",
    483 			       (int) getpid(), (int) my_pid));
    484 		    } else {
    485 			TRACE(("failed to open printer:%s\n", strerror(errno)));
    486 			DEBUG_MSG("charToPrinter: could not open in parent\n");
    487 		    }
    488 		}
    489 	    }
    490 	    break;
    491 	case True:
    492 	    TRACE(("opening \"%s\" as printer output\n", SPS.printer_command));
    493 	    SPS.fp = fopen(SPS.printer_command, "w");
    494 	    break;
    495 	}
    496 	SPS.isOpen = True;
    497     }
    498     if (SPS.fp != NULL) {
    499 #if OPT_WIDE_CHARS
    500 	if (chr > 127) {
    501 	    Char temp[10];
    502 	    *convertToUTF8(temp, chr) = 0;
    503 	    fputs((char *) temp, SPS.fp);
    504 	} else
    505 #endif
    506 	    fputc((int) chr, SPS.fp);
    507 	if (isForm(chr))
    508 	    fflush(SPS.fp);
    509     }
    510 }
    511 
    512 static void
    513 stringToPrinter(XtermWidget xw, const char *str)
    514 {
    515     while (*str)
    516 	charToPrinter(xw, CharOf(*str++));
    517 }
    518 
    519 /*
    520  * This module implements the MC (Media Copy) and related printing control
    521  * sequences for VTxxx emulation.  This is based on the description in the
    522  * VT330/VT340 Programmer Reference Manual EK-VT3XX-TP-001 (Digital Equipment
    523  * Corp., March 1987).
    524  */
    525 void
    526 xtermMediaControl(XtermWidget xw, int param, int private_seq)
    527 {
    528     TRACE(("MediaCopy param=%d, private=%d\n", param, private_seq));
    529 
    530     if (private_seq) {
    531 	switch (param) {
    532 	case -1:
    533 	case 0:		/* VT125 */
    534 	    setGraphicsPrintToHost(xw, 0);	/* graphics to printer */
    535 	    break;
    536 	case 1:
    537 	    printCursorLine(xw);
    538 	    break;
    539 	case 2:		/* VT125 */
    540 	    setGraphicsPrintToHost(xw, 1);	/* graphics to host */
    541 	    break;
    542 	case 4:
    543 	    setPrinterControlMode(xw, 0);	/* autoprint disable */
    544 	    break;
    545 	case 5:
    546 	    setPrinterControlMode(xw, 1);	/* autoprint enable */
    547 	    break;
    548 	case 10:		/* VT320 */
    549 	    /* print whole screen, across sessions */
    550 	    xtermPrintScreen(xw, False, getPrinterFlags(xw, NULL, NULL));
    551 	    break;
    552 	case 11:		/* VT320 */
    553 	    /* print all pages in current session */
    554 	    xtermPrintEverything(xw, getPrinterFlags(xw, NULL, NULL));
    555 	    break;
    556 	}
    557     } else {
    558 	switch (param) {
    559 	case -1:
    560 	case 0:
    561 	    xtermPrintScreen(xw, True, getPrinterFlags(xw, NULL, NULL));
    562 	    break;
    563 	case 4:
    564 	    setPrinterControlMode(xw, 0);	/* printer controller mode off */
    565 	    break;
    566 	case 5:
    567 	    setPrinterControlMode(xw, 2);	/* printer controller mode on */
    568 	    break;
    569 #if OPT_SCREEN_DUMPS
    570 	case 10:
    571 	    xtermDumpHtml(xw);
    572 	    break;
    573 	case 11:
    574 	    xtermDumpSvg(xw);
    575 	    break;
    576 #endif
    577 	}
    578     }
    579 }
    580 
    581 /*
    582  * When in autoprint mode, the printer prints a line from the screen when you
    583  * move the cursor off that line with an LF, FF, or VT character, or an
    584  * autowrap occurs.  The printed line ends with a CR and the character (LF, FF
    585  * or VT) that moved the cursor off the previous line.
    586  */
    587 void
    588 xtermAutoPrint(XtermWidget xw, unsigned chr)
    589 {
    590     TScreen *screen = TScreenOf(xw);
    591 
    592     if (SPS.printer_controlmode == 1) {
    593 	TRACE(("AutoPrint %d\n", chr));
    594 	printLine(xw, screen->cursorp.row, chr, getPrinterFlags(xw, NULL, NULL));
    595 	if (SPS.fp != NULL)
    596 	    fflush(SPS.fp);
    597     }
    598 }
    599 
    600 /*
    601  * When in printer controller mode, the terminal sends received characters to
    602  * the printer without displaying them on the screen. The terminal sends all
    603  * characters and control sequences to the printer, except NUL, XON, XOFF, and
    604  * the printer controller sequences.
    605  *
    606  * This function eats characters, returning 0 as long as it must buffer or
    607  * divert to the printer.  We're only invoked here when in printer controller
    608  * mode, and handle the exit from that mode.
    609  */
    610 #define LB '['
    611 
    612 int
    613 xtermPrinterControl(XtermWidget xw, int chr)
    614 {
    615     TScreen *screen = TScreenOf(xw);
    616     /* *INDENT-OFF* */
    617     static const struct {
    618 	const Char seq[5];
    619 	int active;
    620     } tbl[] = {
    621 	{ { ANSI_CSI, '5', 'i'      }, 2 },
    622 	{ { ANSI_CSI, '4', 'i'      }, 0 },
    623 	{ { ANSI_ESC, LB,  '5', 'i' }, 2 },
    624 	{ { ANSI_ESC, LB,  '4', 'i' }, 0 },
    625     };
    626     /* *INDENT-ON* */
    627 
    628     static Char bfr[10];
    629     static size_t length;
    630     size_t n;
    631 
    632     TRACE(("In printer:%04X\n", chr));
    633 
    634     switch (chr) {
    635     case 0:
    636     case CTRL('Q'):
    637     case CTRL('S'):
    638 	return 0;		/* ignored by application */
    639 
    640     case ANSI_CSI:
    641     case ANSI_ESC:
    642     case '[':
    643     case '4':
    644     case '5':
    645     case 'i':
    646 	bfr[length++] = CharOf(chr);
    647 	for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); n++) {
    648 	    size_t len = Strlen(tbl[n].seq);
    649 
    650 	    if (length == len
    651 		&& Strcmp(bfr, tbl[n].seq) == 0) {
    652 		setPrinterControlMode(xw, tbl[n].active);
    653 		if (SPS.printer_autoclose
    654 		    && SPS.printer_controlmode == 0)
    655 		    closePrinter(xw);
    656 		length = 0;
    657 		return 0;
    658 	    } else if (len > length
    659 		       && Strncmp(bfr, tbl[n].seq, length) == 0) {
    660 		return 0;
    661 	    }
    662 	}
    663 	length--;
    664 
    665 	/* FALLTHRU */
    666 
    667     default:
    668 	for (n = 0; n < length; n++)
    669 	    charToPrinter(xw, bfr[n]);
    670 	bfr[0] = CharOf(chr);
    671 	length = 1;
    672 	return 0;
    673     }
    674 }
    675 
    676 /*
    677  * If there is no printer command, we will ignore printer controls.
    678  *
    679  * If we do have a printer command, we still have to verify that it will
    680  * (perhaps) work if we pass it to popen().  At a minimum, the program
    681  * must exist and be executable.  If not, warn and disable the feature.
    682  */
    683 Bool
    684 xtermHasPrinter(XtermWidget xw)
    685 {
    686     TScreen *screen = TScreenOf(xw);
    687     Bool result = SPS.printer_checked;
    688 
    689     if (strlen(SPS.printer_command) != 0 && !result) {
    690 	char **argv = x_splitargs(SPS.printer_command);
    691 	if (argv) {
    692 	    if (argv[0]) {
    693 		char *myShell = xtermFindShell(argv[0], False);
    694 		if (myShell == NULL) {
    695 		    xtermWarning("No program found for printerCommand: %s\n", SPS.printer_command);
    696 		    SPS.printer_command = x_strdup("");
    697 		} else {
    698 		    free(myShell);
    699 		    SPS.printer_checked = True;
    700 		    result = True;
    701 		}
    702 	    }
    703 	    x_freeargs(argv);
    704 	}
    705 	TRACE(("xtermHasPrinter:%d\n", result));
    706     }
    707 
    708     return result;
    709 }
    710 
    711 #if OPT_PRINT_GRAPHICS
    712 static void
    713 setGraphicsPrintToHost(XtermWidget xw, int enabled)
    714 {
    715     TScreen *screen = TScreenOf(xw);
    716 
    717     TRACE(("graphics print to host enabled=%d\n", enabled));
    718     screen->graphics_print_to_host = (Boolean) enabled;
    719 }
    720 #endif
    721 
    722 #define showPrinterControlMode(mode) \
    723 		(((mode) == 0) \
    724 		 ? "normal" \
    725 		 : ((mode) == 1 \
    726 		    ? "autoprint" \
    727 		    : "printer controller"))
    728 
    729 void
    730 setPrinterControlMode(XtermWidget xw, int mode)
    731 {
    732     TScreen *screen = TScreenOf(xw);
    733 
    734     if (xtermHasPrinter(xw)
    735 	&& SPS.printer_controlmode != mode) {
    736 	TRACE(("%s %s mode\n",
    737 	       (mode
    738 		? "set"
    739 		: "reset"),
    740 	       (mode
    741 		? showPrinterControlMode(mode)
    742 		: showPrinterControlMode(SPS.printer_controlmode))));
    743 	SPS.printer_controlmode = mode;
    744 	update_print_redir();
    745     }
    746 }
    747 
    748 PrinterFlags *
    749 getPrinterFlags(XtermWidget xw, String *params, Cardinal *param_count)
    750 {
    751     /* *INDENT-OFF* */
    752     static const struct {
    753 	const char *name;
    754 	unsigned    offset;
    755 	int	    value;
    756     } table[] = {
    757 	{ "noFormFeed", XtOffsetOf(PrinterFlags, printer_formfeed), 0 },
    758 	{ "FormFeed",	XtOffsetOf(PrinterFlags, printer_formfeed), 1 },
    759 	{ "noNewLine",	XtOffsetOf(PrinterFlags, printer_newline),  0 },
    760 	{ "NewLine",	XtOffsetOf(PrinterFlags, printer_newline),  1 },
    761 	{ "noAttrs",	XtOffsetOf(PrinterFlags, print_attributes), 0 },
    762 	{ "monoAttrs",	XtOffsetOf(PrinterFlags, print_attributes), 1 },
    763 	{ "colorAttrs", XtOffsetOf(PrinterFlags, print_attributes), 2 },
    764     };
    765     /* *INDENT-ON* */
    766 
    767     TScreen *screen = TScreenOf(xw);
    768     PrinterFlags *result = &(screen->printer_flags);
    769 
    770     TRACE(("getPrinterFlags %d params\n", param_count ? *param_count : 0));
    771 
    772     result->printer_extent = SPS.printer_extent;
    773     result->printer_formfeed = SPS.printer_formfeed;
    774     result->printer_newline = SPS.printer_newline;
    775     result->print_attributes = SPS.print_attributes;
    776     result->print_everything = SPS.print_everything;
    777 
    778     if (param_count != NULL && *param_count != 0) {
    779 	Cardinal j;
    780 	unsigned k;
    781 	for (j = 0; j < *param_count; ++j) {
    782 	    TRACE(("param%d:%s\n", j, params[j]));
    783 	    for (k = 0; k < XtNumber(table); ++k) {
    784 		if (!x_strcasecmp(params[j], table[k].name)) {
    785 		    int *ptr = (int *) (void *) ((char *) result + table[k].offset);
    786 		    TRACE(("...PrinterFlags(%s) %d->%d\n",
    787 			   table[k].name,
    788 			   *ptr,
    789 			   table[k].value));
    790 		    *ptr = table[k].value;
    791 		    break;
    792 		}
    793 	    }
    794 	}
    795     }
    796 
    797     return result;
    798 }
    799 
    800 /*
    801  * Print a timestamped copy of everything.
    802  */
    803 void
    804 xtermPrintImmediately(XtermWidget xw, String filename, int opts, int attrs)
    805 {
    806     TScreen *screen = TScreenOf(xw);
    807     PrinterState save_state = screen->printer_state;
    808     char *my_filename = malloc(TIMESTAMP_LEN + strlen(filename));
    809 
    810     if (my_filename != NULL) {
    811 	mode_t save_umask = umask(0177);
    812 
    813 	timestamp_filename(my_filename, filename);
    814 	SPS.fp = NULL;
    815 	SPS.isOpen = False;
    816 	SPS.toFile = True;
    817 	SPS.printer_command = my_filename;
    818 	SPS.printer_autoclose = True;
    819 	SPS.printer_formfeed = False;
    820 	SPS.printer_newline = True;
    821 	SPS.print_attributes = attrs;
    822 	SPS.print_everything = opts;
    823 	xtermPrintEverything(xw, getPrinterFlags(xw, NULL, NULL));
    824 
    825 	umask(save_umask);
    826 	screen->printer_state = save_state;
    827 	free(my_filename);
    828     }
    829 }
    830 
    831 void
    832 xtermPrintOnXError(XtermWidget xw, int n)
    833 {
    834 #if OPT_PRINT_ON_EXIT
    835     /*
    836      * The user may have requested that the contents of the screen will be
    837      * written to a file if an X error occurs.
    838      */
    839     if (TScreenOf(xw)->write_error && !IsEmpty(resource.printFileOnXError)) {
    840 	Boolean printIt = False;
    841 
    842 	switch (n) {
    843 	case ERROR_XERROR:
    844 	    /* FALLTHRU */
    845 	case ERROR_XIOERROR:
    846 	    /* FALLTHRU */
    847 	case ERROR_ICEERROR:
    848 	    printIt = True;
    849 	    break;
    850 	}
    851 
    852 	if (printIt) {
    853 	    xtermPrintImmediately(xw,
    854 				  resource.printFileOnXError,
    855 				  resource.printOptsOnXError,
    856 				  resource.printModeOnXError);
    857 	}
    858     }
    859 #else
    860     (void) xw;
    861     (void) n;
    862 #endif
    863 }
    864