Cover V01, I04
Sidebar 1
Sidebar 2


Setting Up a Printer's terminfo Definition

Larry Reznick

An Absence of Formfeeds

The first thing I did after installing a new version of UNIX was to set up the printer. Nothing special. The parallel printer is one of the easiest devices to install. Plug it in, check the link of /dev/lpX, give a few parameters to lpadmin, run accept, and finally run enable. Using lpstat -t along the way will let you know what you might have forgotten up to any point. The final test? Send a short file to the spooler. If lp can get the file out, then, usually, nothing has been overlooked.

So, I printed a file and the banner came out, followed immediately by the file -- no formfeed between them and no formfeed after the file. My previous experience with BSD had never produced that, and other experiences with SCO had given me no such problems. But, this time, I was using Esix's SVR4, and SVR4 does some things that I'm still getting used to. Fair enough. There is probably a good explanation for it.

The first place I looked was the interface file. The print spooler mechanism uses an interface file, a filter to be found in /usr/spool/lp/model. One of the files in there, usually the one named standard, gets installed in /usr/spool/lp/admins/lp/interfaces and has its name changed to the name you give to the printer. Using the lpadmin -m option selects the name. When in doubt, use standard, even though other files in the /usr/spool/lp/model directory that have been modified for specific printers might be better choices. The standard model supports everything documented for lp -o, including page length, width, lines-per-inch (lpi), and characters-per-inch (cpi). Furthermore, you can add options of your own to support special printer features. In the past, I had modified SCO's interface file to add the options I wanted to support, but while I was doing that, I had the feeling that there was another way.

It turns out that the standard interface file changes the lpi and cpi of the printer by getting information from terminfo. I knew I was getting closer since my experience with BSD was its use of printcap, a termcap for printers. Terminfo was the updated version delivered with System V, and I had built terminfo definitions before, although for terminals. Terminfo can be used to define printers, too. Part 2 of the terminfo(4) man page is devoted to printer capabilities.

To output any codes defined in the terminfo file, use the tput(1) program. Since terminfo's capabilities have well-documented names (as have termcap's, except that terminfo's names are not all two-letter abbreviations), tput can be used with those names and any relevant parameters to output the specific codes for the printer. In the standard interface file, a variable FF is set to the terminfo formfeed code for the printer. The Bourne shell script syntax is

FF=`tput ff`

Logic in the interface file provides for the lack of the formfeed, and while that logic may not have been working right, I first wanted to find out what terminfo definition it was using. I could then check that to see if the formfeed definition was missing.

A quick rereading of the lpadmin(1M) man page revealed a -T option, which specifies the terminfo name to be used for the printer being setup. It says that the default terminfo name will be unknown. unknown is not the lack of a state of being, but actually the name of a terminfo definition. I needed to look into that.

When data is spooled to the printer, the interface file receives data intended for the printer on stdin (standard input) and is supposed to pass it through to stdout (standard output). When talking to the interface file, the spooler thinks it is piping to a terminal set up with the printer's characteristics. Those characteristics are defined by the terminfo setting, just as with any other terminal, by setting the terminal's name in the TERM environment variable. The interface file thinks it is sending to the printer, so it sends out any initializing and other optional setup codes before it sends the rest of the spooled data. The -T option tells the spooler which terminfo setting to give to the TERM variable before delivering the data to the interface file.

As far as I could find out, the -T option is relatively new to System V. In my old SVR2 manuals, lpadmin does not show that option. However, SCO UNIX SVR3.2 (not their Xenix, but their UNIX) has it, so it was probably introduced with SVR3 and has been carried on into SVR4. Xenix appears not to support it, although the SV-compatible Xenix would have both the lp-style spooler and terminfo. So, to set the codes up properly for the interface file, I had to build a terminfo definition for my printer and then use the -T option to tell lp which definition to use.

One of the most useful utilities to know about when working with terminfo is infocmp(1M). When executed without parameters, it writes the terminfo definition for the current $TERM variable, telling you all about your terminal setting. When given the name of a terminal known to terminfo, it outputs that terminal's definition. If more than one terminal name is given, infocmp will compare the two terminal definitions and show only the differences between them. The option I use most of the time is with a single terminfo name. Using infocmp unknown revealed the problem: the terminfo definition, unknown, has no formfeed provision. In fact, it has very little of anything in it. If I wanted to deal intelligently with my printer's features, I had to find a terminfo definition for it, or write one myself.

A Temporary Fix

The terminfo definitions are in /usr/lib/terminfo. (Technically, that is a symbolic link to /usr/share/lib/terminfo to keep compatibility with older versions of UNIX.) In that directory are a number of one-character subdirectory names, either letters or digits, in which are collected all the terminfo definition files with names beginning with those characters. I was looking for a Panasonic printer, so I looked in the directories named P and p. No luck. It appeared that I would have to write my own terminfo file, but while browsing through those directories, I found quite a few other candidates that could serve as models. Most notably, there were several files for Epson, C-Itoh, HP, and IBM Proprinter that I could study, and I found a reasonable makeshift definition that could be used to correct the current problem: a terminfo file named printer. That one had a formfeed definition. By using lpadmin -T printer for my printer's name, I could solve the problem for now, and take my time to do the terminfo definition that fully supports my printer's features.

The Setup

Now that the printer had formfeeds and everybody was happy again, I started concentrating on the terminfo definition. The best book I know of on terminfo is the O'Reilly book, Termcap and Terminfo, but the focus there is on terminals, not printers, so it was back to the UNIX man pages. The definition of terminfo(4) is extensive, but the printer section is a bit obtuse. For instance, when talking about resolution, they refer to steps, but there is not much information on exactly what a step is. After reading through that section carefully and studying existing terminfo definitions of printers I was familiar with, I decided that they could mean pin diameter, which is usually defined in the printer manual's specification pages. (With some graphic printers, the pin diameters can overlap due to the horizontal or vertical motion of the head, but that, too, is usually defined in the printer manual. Also, not all printers actually use pins, but the concept of a dot motion can be applied to many printers that do not use pins.)

For now, I was not going to support a lot of dot-graphic features, although that might be fun to play with sometime in the future. I did want to get primary text features supported so that users could take advantage of bold, underlining, and various lpi and cpi settings. Many of the features terminfo uses for terminals are useful for printers, too, so it became a job of going through the list of terminfo features and selecting the ones relevant to this printer.

terminfo has three different types of definitions: Booleans, which identify a feature that either exists or does not, such as whether an underline character overstrikes (ul) or whether this is a hardcopy terminal (hc); Numbers, which cite a quantity available for a certain feature, such as the number of columns (cols), number of pins (npins), or the internal print buffer's size (bufsz); and finally, Strings.

Most of a terminfo definition is composed of strings. The strings can be as simple as a single character, such as specifying that it has a bell (bel=^G) or that the carriage return character is as expected (cr=\r). Notice that these use the control and escaped character notations to keep the symbols visible and maintainable. For most printers, including mine, the most important escaped character to know about was the ESC character itself. It is represented byE.

With my terminfo man page next to me, my printer manual in one hand, and a notepad, I noted every Boolean, Number, and String that seemed appropriate and translated from printer manual to terminfo notation (it is a dirty job, and most of the time, a system administrator has to do it).

Most definitions are simple -- a single code here, and another code there. A few, like the initialization codes sgr0, is2, and is1, require several codes, all combined together, to shut off everything that might have been enabled by prior operations. These are simply single codes concatenated together into a single string definition, so they might look more complicated than they are. To understand them, examine each code -- usually aE code followed by an option character and possibly a parameter number for most printers -- individually. The tricky ones are those codes that allow a selection of any of several possible settings.

The lpi and cpi settings are good examples of complex string settings. To support all the options my printer offered (why not support them all, since I am diving into this level of detail anyway), I had to offer 10, 12, 15, and 17 cpi (pitch), and any number of point-size for the lpi (72 points per inch), so I set up 2, 3, 4, 6, 8, and 12 lpi even though 6 and 8 tend to be used most often. (Notice that all those numbers divide evenly into 72, though there is no strict requirement that they must, other than the need to divide 11-inch paper into a number of lines without any fractions.) Since strings are supposed to have a single well-defined value, how can multiple possible values be assigned to a single string?

terminfo supports a rudimentary programming language. On the one hand, the language is stack-oriented, so it uses Reverse Polish Notation (RPN) operations. That simplifies the implementation of the language but it might be confusing for some people. On the other hand, the language offers all the features one would need to accomplish most tasks for taking parameters and selecting appropriate codes to send to the printer. The operations supported by the terminfo string language include the expected push (%p and a number to push a parameter, or %'c' to push constant character c, or %{n} to push constant decimal n) and pop (various actions that pop and perform the action), setting of dynamic and static variables (%P or %g and a letter to set-to-pop or get-and-push, where lowercase letters are dynamic variables and uppercase letters are static); and various arithmetic, logic, and comparison operators. There is even a rudimentary equivalent to printf for formatting decimal, octal, hexadecimal, and string output.

The most immediately useful part of the language for me in implementing cpi and lpi was the conditional test. An "if...then...else" operation is provided that allows testing of multiple possible values to select the particular codes to output. Since the cpi=10 might come along, one parameter will appear. So, I test which parameter value it was this time and send the appropriate code. Take the following example:

%? %p1 %{10} %= %tEw0

The example is separated into its component language parts, but it will be all crammed together without spaces, all on a single line, in the actual terminfo entry. The "%?" says "if" and the "%t" says "then", so everything in between must be the conditional test. "%p1" says "push parameter 1," so whatever that may be, it is now on the stack. "%{10}" says "push a 10 on the stack," and finally, "%=" says "pop the last two parameters and compare them for equality." So, the whole thing says, "if parameter 1 is equal to 10, then output ESC w 0." After that would come "%e" meaning "else," which could have either another conditional expression using another logical operator, or the last body of code. So, the final form of the code is:

%? %p1 %{10} %= %t \Ew0
%e %p1 %{12} %= %t \Ew1
%e %p1 %{15} %= %t \Ew2
%e %p1 %{17} %= %t \Ew3 %;,

Again, the spaces are just for readability here. They must be eliminated in the terminfo definition file, and all of this must be on a single line -- no matter how long that line is. The "%;" terminates the entire conditional syntax, and the trailing comma comes after every terminfo entry without exception. (If you are ever trying to understand a complex terminfo entry, try breaking it down into this format with your text editor. It is amazing how simple a little formatting can make things seem.)

Once all the definitions are decided on, put them into a file named for the printer being defined. I usually put those into /usr/local/develop/term and named this file p1000.src, since the printer is a Panasonic KX-P1000. The first line of any terminfo definition contains the names (including aliases) of the terminal or printer. One terminfo entry will be created for each name no matter how many names there are (these should be hard links so as to take up little file space). Each alias is separated from the next by a vertical bar ("|" -- the pipe symbol). Generally, try to create at least two aliases, each a single word, followed by a long descriptive name. I used

p1000|panaprinter|Panasonic KX-P1000, 

for mine, since the p1000 defines it uniquely, the word "panaprinter" is used in the printer's documentation as the general name of the printer, and the last name is the long name identifying the brand. The rest of the lines must all be indented with at least a single space or tab (using a tab will be more obvious) and terminated with a comma. If you do not use the tab or space, the terminfo compiler will think each capability is actually the definition of another terminal or printer. This will require you to clean up a lot of files, including those with control characters in the names! Better not to make this kind of mistake in the first place. To keep things simple and make debugging easier, I suggest having each single capability defined on its own line.

Once the file is completed, it is time to install using the tic (terminfo compiler) command. Before committing yourself to installing in the /usr/share/lib/terminfo directories, you might want to create a terminfo directory of your own for testing. Make one in your home directory, say call it ti. Then, set the TERMINFO environment variable to that directory's path. When tic is executed, if it sees the TERMINFO variable, it installs in that directory instead of in /usr/share/lib/terminfo. That same environment variable is used by infocmp and other programs, so if you make a mistake, you are not screwing up the rest of the system's terminfo directories and you can still test with the various programs available. The tic program can be executed with a -v (verbose) option to show what it is doing and a -c (check) option to tell you about errors. Once everything is checked, go ahead and install it. If everything works well, unset the TERMINFO variable and install it system-wide.

Once I did that for my p1000 definition file, I tried the lp -o cpi=17 and got the compressed characters I was hoping for, making 132-column lines appear on 80-column paper, which works nicely for certain reports. Setting up terminfo for printers is no more tedious than for terminals (which is fairly tedious work), but provides the underpinnings for the many programs that utilize terminal-independent operation. Now, I can write programs that use the special capabilities my printer offers and be sure those programs will be reasonably portable. Next, I want to look into the interface file to find out how to get all the underlining and boldfacing features embedded in the man pages to use those printer codes.

About the Author

Larry Reznick has been programming professionally since 1978. He is currently working on systems programming in UNIX and DOS. He teaches C language courses at American River College in Sacramento and is the owner of Rezolution Technical Books. He can be reached via email at: rezbook!