For common configurations, you can probably ignore this section
entirely - instead, you should jump straight to the Vendor Solutions
section below, or better yet, your vendor's documentation. Most Linux
distributions supply one or more "idiot-proof" tools to do everything
described here for common printers.
If your vendor's tool's results are not satisfactory, or you'd like
the ability to interactively control printing options when you print,
then you should use PDQ; I recommend PDQ in most cases.
PDQ can be configured by either the superuser or by a joeuser. Root's
changes are made to /etc/printrc, and affect everyone, while
joeuser can only modify his personal .printrc. Everything
applies to both types of configuration.
If PDQ is not available prepackaged for your distribution, you should
obtain the source distribution from the
PDQ web page and
compile it yourself. It is an easy compile, but you must first be
sure to have installed the various GTK development library packages,
the C library development package, the gcc compiler, make, and
possibly a few other development things.
Drivers and Interfaces
PDQ lets users select a printer to print to. A printer is defined in
PDQ as the combination of a "driver" and an "interface". Both drivers
and interfaces are, in fact, merely snippets of text in the PDQ
configuration file.
A PDQ interface says everything about how to ship data out to a
printer. The most common interfaces, which are predefined in the PDQ
distribution's example printrc file, are:
local-port
A local port interface speaks to a parallel or serial
port on the machine PDQ is running on. Using this interface, PDQ
can print directly to
your parallel port. Note that if you have a multiuser system this
can cause confusion, and if you have a network the local-port
interface will only apply to one system. In those cases, you can
define a raw unfiltered lpd queue for the port and print to the
system's lpd daemon exactly the same way from all systems and
accounts without any troubles. This interface has a device name
argument; the typical value would be /dev/lp0.
bsd-lpd
A bsd lpd interface speaks over the network to an LPD
daemon or LPD-speaking networked printer. PDQ supports job
submission, cancellation, and queries to LPD interfaces. This
interface has hostname and queuename arguments.
appletalk
The appletalk interface allows you to print to
printers over the Appletalk network; if you have a printer plugged
into your Mac this is the way to go. This interface needs to have
the Netatalk package installed to work.
A PDQ driver says everything about how to massage print data into a
format that a particular printer can handle. For Postscript printers,
this will include conversion from ascii into Postscript; for
non-Postscript printers this will include conversion from Postscript
into the printer's language with Ghostscript.
If one of PDQ's included driver specifications doesn't fit your
printer, then read the section below on how to write your own.
Defining Printers
To define a printer in PDQ:
First check that you've got suitable driver and interface
declarations in the system or your personal printrc.
If you want to define the printer in /etc/printrc (for all
users), then su to root.
Run xpdq, and select Printer->Add printer. This "wizard"
will walk you through the selection of a driver and interface.
That's really all there is to it; most of the work lies in finding or
creating a suitable driver specification if you can't find one
premade.
Creating a PDQ Driver Declaration
Here I'll walk through an example of how to make a PDQ driver
declaration. Before you try that, though, there are several places to
look for existing driver specs:
PDQ itself comes with a small collection of prewritten driver
files.
This HOWTO's
database includes a program called "PDQ-O-Matic" which
will generate a PDQ specification from the information in the
database. With a bit of fiddling, this may suit. This is the
easy path if you have a non-Postscript printer.
I've written a tool called ppdtopdq which takes a
Postscript Printer Definition file and converts it into a PDQ
driver specification. This is the obvious path if you have a
Postscript printer. Mail me for a copy.
There are several places to look for the information needed to write
your own PDQ driver:
The PDQ driver specification syntax is quite rich, and is fully
documented in the
printrc(5) man page.
The PDQ distribution includes a few example files. Look in
particular at the Epson Stylus file, which demonstrates the
structure of the definition for a Ghostscript-driven printer.
The
Printing HOWTO Database includes raw Linux driver
information for over 400 printers. This will tell you what
options to give Ghostscript, or what extra program to run on
the Ghostscript output.
If you have to create your own driver specification, or if you enhance
one from the PDQ distribution or one of the PDQ driver generator
programs mentioned above, please share your creation with the
world! Send it to me (gtaylor+pht@picante.com), and I'll
make sure that it gets found by future PDQ users with your type of
printer.
Now, let's walk through the writing of a driver specification for a
printer listed in the Printing HOWTO's database as working, but for
which you can't find a PDQ driver spec. I'll use the Canon BJC-210 as
the example printer.
First, we look at the
database entry for this printer. Note that it is supported
"perfectly", so we can expect to get comparable results (or better) to
Windows users. The important information is in three places in the
entry:
Driver
The last line in the Works?/Language/Driver column tells
us one driver that works with this printer. More importantly,
this name is a link to the driver's home page.
Notes
The human-readable notes will often contain useful
information. For some printers, there is a More Info link, which
usually refers to a web page run by a user with this printer, or
to the driver's home page.
Driver List
Most printers have a list of driver command data.
This is the most important part.
A PDQ driver spec has two logical functions: user interaction, and
print job processing. These are represented in the file in three
places:
Option Declarations
These define what options the user can set,
and declare PDQ variables for later parts of the driver to use.
Language Filters
These process the print job from whatever
format it arrived in (typically Postscript or ASCII) into a
language the printer can understand (for example, PCL). Option
values are available here, as well as in the output filter.
Output Filter
This final filter bundles up the printer data
regardless of input type; often printer options are set here.
The database's documentation tells us that a "Ghostscript" driver
type's text is a set of options for Ghostscript, less the "usual"
options like -q or the file specifying options.
So, as far as the user is concerned, the BJC-210 supports one useful
option: the user should pick color or black-and-white. Let's declare
that as choice option called "MODE":
option {
var = "MODE"
desc = "Print Mode"
# default_choice "Color" # uncomment to default to color
choice "BW" {
# The value part assigns to the variable MODE whatever you
# want. Here we'll assign the text that varies between the
# two Ghostscript option sets for the two modes.
value = "bj200"
help = "Fast black printing with the black cartridge."
desc = "Black-only"
}
choice "Color" {
value = "bjc600"
help = "Full-color printing."
desc = "Color"
}
}
With the above choice declarations, the user will see a Color or BW
choice in the driver options dialog when he prints from xpdq. In the
command-line pdq tool, he may specify -oBW or
-oColor. The default can be set from xpdq, or declared above
with the default_choice keyword.
Language Filtering
PDQ normally identifies its input with the file(1) command. For each
type returned by file that you want to handle, you provide a
language_driver clause. The clause consists mostly of a
script to process the printjob language, in any (!) scripting language
you wish (the default is the usual Bourne shell).
In our case, we want to print Postscript and ASCII on our BJC-210.
This needs two language drivers: one to run Ghostscript for Postscript
jobs, and one to add carriage returns to ASCII jobs:
# The first language_driver in the file that matches what file(1)
# says is what gets used.
language_driver ps {
# file(1) returns "PostScript document text conforming at..."
filetype_regx = "postscript"
convert_exec = {
gs -sDEVICE=$MODE -r360x360 \ # gs options from the database
-q -dNOPAUSE -dBATCH -dSAFER \ # the "usual" Ghostscript options
-sOutputFile=$OUTPUT $INPUT # process INPUT into file OUTPUT
# Those last two lines will often be the same for gs-supported
# printers. The gs... line, however, will be different for each
# printer.
}
}
# We declare text after postscript, because the command "file" will
# often describe a postscript file as text (which it is).
language_driver text {
# No filetype_regx; we match the driver's name: "text"
convert_exec = {#!/usr/bin/perl
# a Perl program, just because we can!
my ($in, $out) = ($ENV{'INPUT'}, $ENV{'OUTPUT'});
open INPUT, "$in";
open OUTPUT, ">$out";
while(<INPUT>) {
chomp;
print OUTPUT, "$_\r\n";
}
}
}
That's it! While other printers may need output filtering (as
described in the next section), the above clauses are it for the
BJC-210. We just wrap them all up in a named driver clause:
driver canon-bjc210-0.1 {
option {
var = "MODE"
desc = "Print Mode"
# default_choice "Color" # uncomment to default to color
choice "BW" {
# The value part assigns to the variable MODE whatever you
# want. Here we'll assign the text that varies between the
# two Ghostscript option sets for the two modes.
value = "bj200"
help = "Fast black printing with the black cartridge."
desc = "Black-only"
}
choice "Color" {
value = "bjc600"
help = "Full-color printing."
desc = "Color"
}
}
# The first language_driver in the file that matches what file(1)
# says is what gets used.
language_driver ps {
# file(1) returns "PostScript document text conforming at..."
filetype_regx = "postscript"
convert_exec = {
gs -sDEVICE=$MODE -r360x360 \ # gs options from the database
-q -dNOPAUSE -dBATCH -dSAFER \ # the "usual" Ghostscript options
-sOutputFile=$OUTPUT $INPUT # process INPUT into file OUTPUT
# Those last two lines will often be the same for gs-supported
# printers. The gs... line, however, will be different for each
# printer.
}
}
# We declare text after postscript, because the command "file" will
# often describe a postscript file as text (which it is).
language_driver text {
# No filetype_regx; we match the driver's name: "text"
convert_exec = {#!/usr/bin/perl
# a Perl program, just because we can!
my ($in, $out) = ($ENV{'INPUT'}, $ENV{'OUTPUT'});
open INPUT, "$in";
open OUTPUT, ">$out";
while(<INPUT>) {
chomp;
print OUTPUT, "$_\r\n";
}
}
}
}
Output Filtering
If you want to prepend or append something to all printjobs, or do
some sort of transformation on all the data of all types, then it
belongs in the filter_exec clause. Our little Canon doesn't
require such a clause, but just to have an example, here's a
simple illustration showing how to support duplexing and resolution
choice on a Laserjet or clone that speaks PJL:
driver generic-ljet4-with-duplex-0.1 {
# First, two option clauses for the user-selectable things:
option {
var = "DUPLEX_MODE"
desc = "Duplex Mode"
default_choice = "SIMPLEX"
choice "SIMPLEX" {
value = "OFF"
desc = "One-sided prints"
}
choice "DUPLEX" {
value = "ON"
desc = "Two-sided prints"
}
}
option {
var = "GS_RES"
desc = "Resolution"
default_choice = "DPI600"
choice "DPI300" {
value = "-r300x300"
desc = "300 dpi"
}
choice "DPI600" {
value = "-r600x600"
desc = "600 dpi"
}
}
# Now, we handle Postscript input with Ghostscript's ljet4 driver:
language_driver ps {
filetype_regx = "postscript"
convert_exec = {
gs -sDEVICE=ljet4 $GS_RES \
-q -dNOPAUSE -dBATCH -dSAFER \
-sOutputFile=$OUTPUT $INPUT
}
}
# Finally, we wrap the job in PJL commands:
filter_exec {
# requires echo with escape code ability...
echo -ne '\33%-12345X' > $OUTPUT
echo "@PJL SET DUPLEX=$DUPLEX_MODE" >> $OUTPUT
# You can add additional @PJL commands like the above line here.
# Be sure to always append (>>) to the output file!
cat $INPUT >> $OUTPUT
echo -ne '\33%-12345X' >> $OUTPUT
}
}
Most Linux systems ship with LPD. This section describes a very basic
setup for LPD; further sections detail the creation of complex filters
and network configuration.
Traditional lpd configuration
The minimal setup for lpd results in a system that can queue files and
print them. It will not pay any attention to wether or not your
printer will understand them, and will probably not let you produce
attractive output. Nevertheless, it is the first step to
understanding, so read on!
Basically, to add a print queue to lpd, you must add an entry in
/etc/printcap, and make the new spool directory under
/var/spool/lpd.
An entry in /etc/printcap looks like:
# LOCAL djet500
lp|dj|deskjet:\
:sd=/var/spool/lpd/dj:\
:mx#0:\
:lp=/dev/lp0:\
:sh:
This defines a spool called lp, dj, or deskjet, spooled
in the directory /var/spool/lpd/dj, with no per-job maximum size
limit, which prints to the device /dev/lp0, and which does
not have a
banner page (with the name of the person who printed, etc)
added to the front of the print job.
The above looks very simple, but there a catch - unless I send in
files a DeskJet 500 can understand, this DeskJet will print strange
things. For example, sending an ordinary Unix text file to a deskjet
results in literally interpreted newlines, and gets me:
This is line one.
This is line two.
This is line three.
ad nauseam. Printing a PostScript file to this spool would get a
beautiful listing of the PostScript commands, printed out with this
"staircase effect", but no useful output.
Clearly more is needed, and this is the purpose of filtering. The
more observant of you who read the printcap man page might have
noticed the spool attributes if and of. Well, if, or
the input filter, is just what we need here.
If we write a small shell script called filter that adds carriage
returns before newlines, the staircasing can be eliminated. So we
have to add in an if line to our printcap entry above:
#!perl
# The above line should really have the whole path to perl
# This script must be executable: chmod 755 filter
while(<STDIN>){chop $_; print "$_\r\n";};
# You might also want to end with a form feed: print "\f";
If we were to do the above, we'd have a spool to which we could print
regular Unix text files and get meaningful results. (Yes, there are
four million better ways to write this filter, but few so
illustrative. You are encouraged to do this more efficiently.)
The only remaining problem is that printing plain text is really not
too hot - surely it would be better to be able to print PostScript and
other formatted or graphic types of output. Well, yes, it would, and
it's easy to do. The method is simply an extention of the above
linefeed-fixing filter. If you write a filter than can accept
arbitrary file types as input and produce DeskJet-kosher output for
each case, then you've got a clever print spooler indeed!
Such a filter is called a magic filter. Don't bother writing one
yourself unless you print strange things - there are a good many
written for you already on the net. APS Filter is among the best, or
your Linux distribution may have a printer setup tool that makes this
all really easy.
There's one catch to such filters: some older version of lpd don't run
the if filter for remote printers, and some do. The version of
lpd with modern Linux distributions, and FreeBSD does; most commercial
unices that still ship lpd have a version that does not. See the
section on network printing later in this document for more
information on this.
Accounting
Some installations need to keep track of who prints how much; this
section summarizes methods for doing this.
Regular LPD provides very little to help you with accouting. You can
specify the name of an accounting file in the af= printcap attribute,
but this is merely passed as an argument to your if= filter. It's up
to you to make your if= filter write entries to the accounting file,
and up to you to process the accounting file later (the traditional
format is mainly useful for line printers, and is nontrivial to parse
in Perl, so there's no reason to preserve it).
Ghostscript provides a PageCount operator that you can use to count
the number of pages in each job; basically you just tack a few lines
of postscript onto the end of the job to write an accounting file
entry; for the best example of this see the file unix-lpr.sh in the
Ghostscript source distribution.
Note that the unix-lpr implementation of accounting writes to a file
from the Ghostscript interpreter, and is thus incompatible with the
recommended -dSAFER option. A better solution might be to query the
printer with a PJL command after each job, or to write a postscript
snippet that prints the pagecount on stdout, where it can be captured
without having to write to a file.
The LPRng print spooler includes an HP-specific sample implementation
of accounting; I assume that it queries the printer with PJL.
Large Installations
Large installations, by which I mean networks including more than two
printers or hosts, have special needs. Here is a description of one
possible arrangement.
Each printer should have a single point of control, where an
administrator can pause, reorder, or redirect the queue. To
implement this, have everyone printing to a local server, which
will then queue jobs and direct them to the proper printer.
Use LPRng, at least on servers; the BSD LPD is too buggy for
"real" use.
Client systems should not have unique printing configurations.
To implement this, use LPRng's extended printcap syntax so that
you have one printcap to use everywhere.
Print queues should not be named for make or model; name print
queues for something sensible like location (floor2_nw) or
capability (color_transparency). Three years from now, when
a printer breaks, you will be able to replace it with a
different make or model without causing confusion.
Operate a web page which shows detailed information on each
printer, including location, capabilities, etc. Consider
having it show the queue. Complex networked environments are
unmanagable for users without proper documentation.
On Unix systems, use PDQ to allow selection of print job
attributes such as duplex or paper size, and to force users to
run all Ghostscript processing under the proper user ID.
On Windows and Apple systems, use either the platform-specific
drivers everywhere (Samba supports the Windows
automagical driver-download mechanism) or use generic
Postscript drivers everywhere. Do not mix and match;
primitive word processors often produce different output when
the installed printer driver changes; users cannot deal with
output that vaires depending on the particular client/printer
pair.
If at all possible, buy a large-volume printer for large-volume
printing. If on a budget, use LPRng's multiple printers/one
queue facility and assign a babysitter; printers are complex
mechanical devices that will often jam and run out of paper in
such configurations.
Do not feel that printers must be plugged into workstations;
Ethernet "print servers" now cost under $100. The ability to
locate printers anywhere you can network is a big improvement
over forced location near a host; locate printers in sensible,
central locations.
File Permissions
By popular demand, I include below a listing of the permissions on
interesting files on my system. There are a number of better ways to
do this, ideally using only SGID binaries and not making everything
SUID root, but this is how my system came out of the box, and it works
for me. (Quite frankly, if your vendor can't even ship a working lpd
you're in for a rough ride).
Lpd must currently be run as root so that it can bind to the
low-numbered lp service port. It should probably become UID lp.lp
or something after binding, but I don't think it does. Bummer.
PDQ uses a different, non-daemon-centric scheme, so it has different
programs. The only SUID root programs are the lpd interface programs
lpd_cancel, lpd_print, and lpd_status;
these are SUID because actual Unix print servers require print
requests to originate from a priviledged port. If the only printers
for which you use PDQ's bsd-lpd interface are networked print servers
(like the HP JetDirect or Lexmark's MarkNet adapters) then you do not
need the suid bit on these programs.