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