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