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!reznick@csusac.ecs.csus.edu.
|