Skip to main content

A guide to Anzio's Passthrough Printing

Introduction

Passthrough print is the process of having a UNIX (or similar) host program print on the printer attached to your terminal (or PC running terminal emulation). Its advantages are:

  1. It prints where the user is, without requiring complicated procedures to equate printers with users.
  2. It works over direct serial, modem, multiplexor, TCP/IP, etc.
  3. It is immediate.

All versions of Anzio respond to control codes for passthrough print, as do many dumb terminals. We have recently had several questions regarding how to implement passthrough print on a UNIX host system, where such support is not already in place. Following is a very simple approach, with NO GUARANTEES.

  1. Create a shell script containing the following three lines (using your editor of choice):

tput mc5
cat $*
tput mc4

  1. Make that shell script executable. For instance, if this script is named "passprt", do

chmod +x passprt

  1. If you have "root" permission, copy the shell script somewhere that everyone can access it, such as:

cp passprt /usr/bin

To use it, simply "pipe" your output to it. From a shell level, for instance, if you have a textfile named "somefile", you can do:
        cat somefile | passprt

If you are working in an application environment such as a database or a COBOL runtime, you should be able to tell it to send its printer output to "passprt" rather than to "lp". The mechanism for doing so varies. We have provided some notes on doing this below.

How it Works

  1. The "tput mc5" line checks your TERM variable, looks up the "mc5" capability in that terminal's terminfo entry, and outputs it. "mc5" turns on passthrough print.
  2. The "cat" line simply takes the input to the shell script (what you're piping to it) and sends it to the output (your terminal). Or, if you enter

passprt myfile 

    the '$*' will get replaced with the file name(s) listed, and they'll all get "catted".
  1. The "tput mc4" sends out the code to turn off passthrough print.

This approach using "tput" is nice because it is terminal-type independent. It relies on your terminal's terminfo file containing the 'mc4' and 'mc5' codes. If it does not have these, and you know how to manipulate terminfo entries, then try adding the following codes:

  • For VT100, VT220, (SCO)ANSI, or AT386: mc4=\E[4i, mc5=\E[5i,
  • For Wyse 50, 60: mc4=^T, mc5=^R,

If your system doesn't have "tput", it probably doesn't have "terminfo". You may be able to rig something together, but you may need to hard-code the sequence for each terminal type. For instance, here is a string coded just for VT/ANSI-style:

   printf "\033[5i\c"
   cat $*
   printf "\033[4i\c"

Things to Watch For

As we said above, this is a simple approach. For instance:

  1. It doesn't preclude some other process from writing to your terminal at the same time, which text would then come out on your printout.
  2. Any keystrokes you hit during passthrough print could disrupt handshaking.
  3. Tab expansion is dependent on your "stty" setting.
  4. LF to CRLF conversion is dependent on your "stty" setting.
  5. There may be character set issues if you're not using all ASCII.
  6. You'll get into trouble if you try to send 8-bit data over a 7-bit data line (such as if parity is enabled).

Anzio Lite, AnzioWin, Passthrough Print, and the Print Wizard

As mentioned above, all versions of Anzio support passthrough print. DOS versions simply pass the characters out to a file/device, such as LPT1. In Windows, we have more flexibility, and more confusion.

In AnzioWIn and Anzio Lite, you can choose 1) what printer to print to, 2) paper size and orientation, 3) printer font, and 4) character size. The program will then print the passthrough data using these settings. Thus if you are about to print an 80-column report, you might choose a 12-point font, and if you're doing a 132-column report, a 7-point font.

But your host software may know about printer types. It may know you're printing on a LaserJet 3, and embed an escape sequence to set the character size. To keep Anzio from PRINTING that sequence instead of OBEYING it, you must set Anzio's "Low-level Print" switch on.

Effective version 10.9 of AnzioWin (not Anzio Lite), we introduce Print Wizard embedded. When this feature is turned on, it will look at the data stream coming down the passthrough print pipeline, and deal with it automatically. If the data contains escape codes (or PostScript), it will automatically print using the Low-level approach. Otherwise, the Print Wizard will determine line length and page length implied in the data, look at the paper size and orientation you've chosen, and pick font size and line-spacing automatically. This saves you (and your users) from the need to adjust these settings every time.

Notes on Specific Environments

SCO
SCO UNIX has a utility named 'lprint' which functions similarly to 'passprt' above, except that it uses termcap entries PN and PS to define the terminal's printer switching codes, rather than terminfo. So to use it, you may need to modify "/etc/termcap".

AcuCobol
AcuCobol's runtime has a capability known as 'LOCALPRINT'. Simply assign the file to either 'LOCALPRINT' or 'LOCALPRINT-C', using one of the standard file assignment techniques. This approach has the advantage that it allows an existing report program to be redirected to passthrough print without program changes or recompilation. AcuCobol has another technique involving calls to a library routine named 'C$LOCALPRINT'. This may be appropriate in cases where you want the program to always write to the passthrough channel. Because AcuCobol uses its own terminal definition file, you must ensure that your terminal's entry in that file ('/etc/a_termcap') contains the printer-on and printer-off settings.

RM/Cobol
RM/Cobol can be configured so that a particular device name will pipe to a certain program. Assume you've made the 'passprt' shell script shown above, and placed it in '/usr/bin/passprt'. Then, in the config file used by the RM runtime, add the following line:
   DEFINE-DEVICE device=PASSPRT path="/usr/bin/passprt" pipe=yes
Now assume you have a program with a file assigned to 'PRINTER'. If you do:
   PRINTER=PASSPRT;export PRINTER
the program will now print on the passthrough printer.

PICK and clones
We are researching methods in PICK and its clones. Check back for update.

Examples On Using Passthrough Print

Using passthrough printing is actually much easier than it sounds. But a lot of it depends on the capabilities of your "dumb" terminal, your terminal emulation software, or your telnet client. There are two shell script examples below of how to implement this. The first is a simple method which assumes your terminal type supports passthrough printing and is known to the host, while the second handles these for you. The last example is a C program which demonstrates passthrough printing through an application. These scripts can be downloaded from our web site (see Download : Archives).


Simple Passthrough Printing - printpt.sh

  clear                                                                  
  print "Rasmussen Software, Inc. Passthrough Print with AnzioWin \n"
  printf "  File To Print: $1 \n"                                             
  printf "\034receive quiet\035"                                           
  tput mc5                                                               
  cat $1                                                                 
  tput mc4                                                               
  printf "\034receive quiet off\035"                                       

"Smart" Passthrough Printing - passprt


# Shell script to do passthrough print, dealing with unknown TERM,
# unknown passthrough print codes, etc.
# Released to public domain by Rasmussen Software, Inc.
#
# To install:
#    Place it on UNIX(-like) system, in a directory in your PATH
#    Make it executable, with
#       chmod +x passprt
#
# To use:
#    Either pipe to it:
#       somecommand | passprt
#    or reference a file:
#       passprt somefile
#
if [ "$TERM" = "" -o "$TERM" = "unknown" ]; then
   TERM=vt220                                       # TERM unknown; assume VT220
fi
MC5=`tput mc5 2> /dev/null`                         # Code to turn on passthru
MC4=`tput mc4 2> /dev/null`                         # Code to turn off passthru
if [ "$MC4" = "" -o "$MC5" = "" ]; then             # Are MC4, MC5 defined?
   case "$TERM" in
      [wW][yY]*) MC5=`printf "\033d#\c"`;          # Wyse 50/60
                 MC4=`printf "\024\c"`;;
      [vV][iI][eE][wW]*) MC5=`echo -e "\0333\c"`;   # Viewpoint
                    MC4=`printf "\0334\c"`;;
      [vV][wW]*) MC5=`printf "\0333\c"`;           # Vwpt
                 MC4=`printf "\0334\c"`;;
      *)          MC5=`printf "\033[?5i\c"`;        # Other: assume ANSI style
                 MC4=`printf "\033[?4i\c"`;;
   esac
fi
SAVETTY=`stty -g`                                   # Save current stty settings
stty onlcr                                          # Be sure LF gets CR added
printf $MC5                                        # Turn on passthrough print
cat $*                                              # Output all files
printf $MC4                                        # Turn off passthrough print
stty $SAVETTY                                       # Restore stty settings

Passthrough Printing in C - passthru.c

/* program for passthru print */
/* To compile, use the command:
      cc -o passthru passthru.c -lcurses
   or
      cc -o passthru passthru.c -ltermcap
   whichever works
/* This is Version 1.2
   Changes in 1.1 (8-14-97):
      * If TERM undefined, assume VT220
      * If mc5, mc4 undefined, or terminfo entry not found, use VT codes
   Changes in 1.2 (8-4-2000):
      * Bug fixed in case where "write" didn't write full buffer
 
   Options:
      -r = raw mode. Turns off output post-processing - would affect
           tab expansion, CR/LF.
      -t TERMNAME = specify TERM type.
   Needs:
      * Don't set raw until we need it.
      * Is there some way to block parent process from writing to CRT
        during our interval?
      * Do we want to limit the number of characters we'll write in a
        chunk, so parent process can run?
      * Option to specify mc4, mc5
      * How to run if invoked not as a pipe
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
 
#define BOOL int
#define BUFFSZ 256
 
struct pollfd fds[1];
static unsigned long nfds = 1;
int    poll_timeout;
int    r;
BOOL char_avail(void);
BOOL char_avail(void)
{
   fds[0].fd = 0;
   fds[0].events = POLLIN;
   nfds = 1;
   poll_timeout = 0;
   r = poll(fds, nfds, poll_timeout);
   if (r       fprintf(stderr, "POLL error %d\n\r", errno);
      return(errno);
   }
   return(r > 0);
}
 
int main(int argc, char *argv[])
{
   char ch;
   struct stat sb;
   int    g;
   int    u;
   char   buf[BUFFSZ];
   char   *p;
   int    bytes_read;
   int    bytes_written;
   char   *term_name = 0;
   char   *my_prtr_on = 0;
   char   *my_prtr_off = 0;
   int    errret;
   struct termio tbuf;
   struct termio tbuf_save;
   BOOL   ioctl_ok;
   int    i;
   BOOL   use_raw = FALSE;
   extern char *optarg;
#define VT_PRTR_ON  "\033[?5i"
#define VT_PRTR_OFF "\033[?4i"
 
#ifdef DEBUG
   FILE *log;
 
   log = fopen("/usr/passthru.log", "w");
   fprintf(log, "Into passthru\n");
#endif
 
   while ((i = getopt(argc, argv, "rt:")) != EOF)            /* process options*/
      switch (i) {
      case 'r' : use_raw = TRUE;                               /* r = raw      */
                 break;
      case 't' : term_name = malloc(strlen(optarg));           /* t = TERM      */
                 strcpy(term_name, optarg);
                 break;
      }
   if (!term_name) {
      term_name = getenv("TERM");                            /* get TERM       */
      if (!term_name) {
         /* fprintf(stderr, "TERM not set\n");
         return(1);  */
         term_name = "vt220";                       /* 1.1 */
      }
   }
#ifdef DEBUG
   fprintf(log, "TERM variable = %s\n", term_name);
   if (use_raw)
      fprintf(log, "in raw mode\n");
#endif
   if (setupterm(term_name, 1, &errret) == ERR) {            /* setupterm       */
      /* if (errret == 0)
         fprintf(stderr, "Can't find terminfo entry for %s\n", term_name);
      else
         fprintf(stderr, "Can't find terminfo database\n");
      return(1);  */
      my_prtr_on = VT_PRTR_ON;  /* 1.1 */
      my_prtr_off = VT_PRTR_OFF;
   }                                                         /* get mc4, mc5   */
   if (!prtr_on || !prtr_off) {
      /* fprintf(stderr, "terminfo entry must have 'mc4' and
             'mc5' defined for passthru print\n");
      return(1);  */
      my_prtr_on = VT_PRTR_ON;
      my_prtr_off = VT_PRTR_OFF;
   } else {
      my_prtr_on = prtr_on;
      my_prtr_off = prtr_off;
   }
   if (!use_raw)
      ioctl_ok = FALSE;
   else {
      if (ioctl(1, TCGETA, &tbuf) == -1) /* may be pipe */
         ioctl_ok = FALSE;
      else {
         ioctl_ok = TRUE;
         tbuf_save = tbuf;
         tbuf.c_oflag &= ~OPOST;    /* turn off output post-processing */
         ioctl(1, TCSETAW, &tbuf);                           /* set raw        */
      }
   }
   while (1) {                                               /* main loop      */
      fds[0].fd = 0;
      fds[0].events = POLLIN;
      poll_timeout = -1;  /* wait until ready */
      r = poll(fds, nfds, poll_timeout);                     /* wait until ready */
      if (r          fprintf(stderr, "POLL error %d\n", errno);
#ifdef DEBUG
         fprintf(log, "POLL error %d\n", errno);
#endif
         return(errno);
      }
      /* write(1, PASS_ON, strlen(PASS_ON)); */
      if (ioctl_ok)
         ioctl(1, TCSETAW, &tbuf);                           /* set raw        */
      putp(my_prtr_on);                                      /* send prtr_on   */
      fflush(stdout);                                        /* flush          */
      while (char_avail()) {                                 /* while there's data */
/*         ch = getchar();
         if (ch == EOF) {
            printf(PASS_OFF);
            fflush(stdout);
            return(0);
         }
         putchar(ch);   */
         bytes_read = read(0, buf, BUFFSZ);                    /* read a chunk */
         if (bytes_read == 0) {
            putp(my_prtr_off);
            fflush(stdout);
            if (ioctl_ok)
               ioctl(1, TCSETAW, &tbuf_save);
            return(0);
         }
         p = &buf[0];
         while (bytes_read > 0) {
            bytes_written = write(1, p, bytes_read);        /* write it      */
            bytes_read -= bytes_written;
            p += bytes_written;
         }
      }
      putp(my_prtr_off);                                     /* send prtr_off  */
      fflush(stdout);                                        /* flush          */
      if (ioctl_ok)
         ioctl(1, TCSETAW, &tbuf_save);                      /* restore tty    */
 
    }
}

Copyright © 2017 Rasmussen Software, Inc. Legal Information & Privacy Policy
Send comments and suggestions to rsi@anzio.com