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
58static void charToPrinter(XtermWidget /* xw */ ,
59			  unsigned /* chr */ );
60static void printLine(XtermWidget /* xw */ ,
61		      int /* row */ ,
62		      unsigned /* chr */ ,
63		      PrinterFlags * /* p */ );
64static void send_CharSet(XtermWidget /* xw */ ,
65			 LineData * /* ld */ );
66static void send_SGR(XtermWidget /* xw */ ,
67		     unsigned /* attr */ ,
68		     unsigned /* fg */ ,
69		     unsigned /* bg */ );
70static void stringToPrinter(XtermWidget /* xw */ ,
71			    const char * /*str */ );
72
73#if OPT_PRINT_GRAPHICS
74static void setGraphicsPrintToHost(XtermWidget /* xw */ ,
75				   int /* enabled */ );
76#else
77#define setGraphicsPrintToHost(xw, enabled)	/* nothing */
78#endif
79
80static void
81closePrinter(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
104static void
105printCursorLine(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 */
118static void
119printLine(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
251static void
252printLines(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
261void
262xtermPrintScreen(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 */
296void
297xtermPrintEverything(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
345static void
346send_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
373static void
374send_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 */
396static void
397charToPrinter(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
512static void
513stringToPrinter(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 */
525void
526xtermMediaControl(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 */
587void
588xtermAutoPrint(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
612int
613xtermPrinterControl(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 */
683Bool
684xtermHasPrinter(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
712static void
713setGraphicsPrintToHost(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
729void
730setPrinterControlMode(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
748PrinterFlags *
749getPrinterFlags(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 */
803void
804xtermPrintImmediately(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
831void
832xtermPrintOnXError(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