initial commit
[profile/ivi/xterm.git] / print.c
1 /* $XTermId: print.c,v 1.121 2011/02/09 10:11:44 tom Exp $ */
2
3 /************************************************************
4
5 Copyright 1997-2010,2011 by Thomas E. Dickey
6
7                         All Rights Reserved
8
9 Permission is hereby granted, free of charge, to any person obtaining a
10 copy of this software and associated documentation files (the
11 "Software"), to deal in the Software without restriction, including
12 without limitation the rights to use, copy, modify, merge, publish,
13 distribute, sublicense, and/or sell copies of the Software, and to
14 permit persons to whom the Software is furnished to do so, subject to
15 the following conditions:
16
17 The above copyright notice and this permission notice shall be included
18 in all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
24 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 Except as contained in this notice, the name(s) of the above copyright
29 holders shall not be used in advertising or otherwise to promote the
30 sale, use or other dealings in this Software without prior written
31 authorization.
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
61 static void charToPrinter(XtermWidget /* xw */ ,
62                           unsigned /* chr */ );
63 static void printLine(XtermWidget /* xw */ ,
64                       int /* row */ ,
65                       unsigned /* chr */ ,
66                       PrinterFlags * /* p */ );
67 static void send_CharSet(XtermWidget /* xw */ ,
68                          LineData * /* ld */ );
69 static void send_SGR(XtermWidget /* xw */ ,
70                      unsigned /* attr */ ,
71                      unsigned /* fg */ ,
72                      unsigned /* bg */ );
73 static void stringToPrinter(XtermWidget /* xw */ ,
74                             const char * /*str */ );
75
76 static FILE *Printer;
77 static pid_t Printer_pid;
78 static int initialized;
79
80 static void
81 closePrinter(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
111 static void
112 printCursorLine(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  */
127 static void
128 printLine(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
255 void
256 xtermPrintScreen(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  */
287 void
288 xtermPrintEverything(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
313 static void
314 send_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
341 static void
342 send_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  */
378 static void
379 charToPrinter(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
454 static void
455 stringToPrinter(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  */
467 void
468 xtermMediaControl(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  */
512 void
513 xtermAutoPrint(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
537 int
538 xtermPrinterControl(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  */
604 Bool
605 xtermHasPrinter(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
619 void
620 setPrinterControlMode(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
638 PrinterFlags *
639 getPrinterFlags(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 }