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