GSoC 2019 Work Product: Printer Applications Framework
Github | Email | LinkedIn | View ProjectDheeraj |
Relevant Project Links
- Printer Applications Framework
- Printer Application Snap
- dheeraj::ippsample(Relevant codebase from this repository will be merged into the Framework repository in the future.)
What is a Printer Application?
Printer Application is a daemon which detects the supported printers and advertizes those printers on the localhost as an IPP Everywhere printer. Printer Applications are a new form of printer drivers to allow sandboxed and OS-distribution-independent packaging.
Why do we need Printer Applications?
The current printing system relies on printer driver packages to add support for non-driverless printers. Canonical is now moving towards sandboxed or snapped package distribution system. In a sandboxed cups package, we cannot modify directory contents once it is snapped. Our system is no more modular. We cannot choose which printer driver package to install. Printer Applications address this problem of modularity and give us the same freedom as in the case of printer drivers.
Printer Application Snaps are Linux/ OS distribution independent. The printer manufacturer (or any driver developer) only needs to put up a Printer Application snap in the Snap Store and users of all distributions can install and use it. So the manufacturer saves a lot of time and money for testing and packaging for all the different distributions.
How a Printer Application Works?
Working of a Printer Application can be divided into 4 parts:
- Device Detection
- PPD Searching
- IPP Eveprinter Manager
- IPP Eveprinter Command
server/detection.c, server/server.c and server/server.h
We have to detect two types of devices - local printers(connected using usb,tty and parallel ports) and network printers(non driverless network printers). For local printers, we are using udev to detect any hardware change on usb, tty and parallel ports.
monitor_devices detects local printers. Whenever we detect any change on usb we increment corresponding value in
enum child_signal describes an event and its corresponding index in the pending_signals array.
monitor_avahi_devices detects network printers and the corresponding value is incremented in the
monitor_avahi_devices are run as seperate threads. The
pending_signals array is processed in the main thread. The
server:: main function every 10 seconds check if any value of
pending_signals is non-zero.
If any value is non-zero then
get_devices function is called with the corresponding index.
get_devices function generates include/exclude scheme based on the index number and call the
deviced utility. This utility is based on the
cups-deviced utility but is simpler. The
deviced utility gives us all the available printers(filtered using the include/exclude). This list is stored in
temp_devices array and it is then compared with the
con_devices array. The
con_devices array maintains all the printers which have corresponding
ippeveprinter active on the localhost. If we have to add a printer, PPD is searched. If we have to remove a printer, IPP eveprinter manager is called.
To search for PPD file, we are using CUPS's
cups-driverd utility with a minor modification(to search for PPD files in the snap package instead of LSB folders). This modified
cups-driverd utility is imported from dheeraj135:ippsample. This ippsample repository have support for PPD files, have modified cups-driverd code and have support for accepting only pwg-raster docformats. Currently, we are using device's manufacturer-make and device's ieee 1284 ID string for searching a suitable PPD file. If we don't find any PPD file for a printer, then we cannot support this printer and it is ignored. If we find PPD for the printer, the PPD file is copied to
IPP Eveprinter Manager
This part contains two functions -
kill_ippeveprinter. If we find a new printer in the
temp_devices array which is not present in the
con_devices array and we have PPD file for this printer then
start_ippeveprinter function is called. If we have a printer in
con_devices which is not present in
temp_devices and the backend of this printer was invoked by the
deviced utility then we have to remove this printer and
kill_ippeveprinter function is called.
start_ippeveprinter function first calls the
get_port function to find a free port between the range 8000-9000. Please note that this function is prone to race condition. I was not able to find a function which can do this operation atomically. This port is used when invoking the ippeveprinter utility from the dheeraj135:ippsample repository. For each printer in
con_devices we maintain the process id of this invoked
kill_ippeveprinter function sends SIGINT signal to the process id of the ippeveprinter to be killed.
IPP Eveprinter Command
ippprint.c and mime_type.c
Whenever a print job is submitted to the ippeveprinter, it calls the
ippprint command. This ippprint command is responsible for applying the filter chain and sending the print job to the backend. First, we have to determine the filter chain.
mime_type.c have code for this finding the filter chain. We read all the
.types file and maintain all available formats in the
aval_types array. This way we assign an index to each type. Next we initialize
mime_database with the
mime_database->filter_graph maintains an adjacency list of all available conversion. Next, we read all the
.convs files and populate the
mime_database->filter_graph adjacency list. Please note that we are not doing any kind of check to check whether a particular filter is available or not. This check will be added in future revisions.
Now, whenever we get a print job, we initialize
mime_database as described above. Please note that, whenever we get a print job, the PPD file of the printer is also taken into consideration to use the cupsFilter and cupsFilter2 lines. So, native printer docformat is also added to the
aval_types array. Now, we know the print job's document format and we know the destination format(printer's native docformat). We use Dijkstra to find the lowest weight path or you can say lowest weight filter chain. This filter chain is stored in the
Next, we generate full paths of these filters. When generating the full path, we make sure that filter is executable and permissions are correct. The filters with full names are stored in the
filterfullname array. Please note that null filters(-) are ignored when generating the full paths.
Next, we apply the filter chain. A series of pipes are created, environment variables
OUTFORMAT is set. The final file is stored as
/var/snap/$SNAP_NAME/common/printjob.XXXXXX, the last 6 X are set by the
This file is used to invoke the
print_job function, the
print_job function in turn send the job to the backend and the backend is responsible for communicating with the printer and printing the job.
Expectations from the project’s implementation:
- Implementation should be generic and can be easily used to create Printer Applications for drivers like HPLIP, foo2zjs, Gutenprint, SpliX etc. - Printer Applications should be easily packageable into snaps.
The Printer Application Framework is generic and using it is very similiar to installing a printer driver package in a custom directory. The Framework uses same directory structure as cups, so no major changes are required.
Printer Applications can be easily snaped as proved by the next section and the sample snaps at Printer Application Snap repository.
- Use language to select PPD Files.
- If the framework fails due to some error, this error should be reported on CUPS's web interface.
How to use this framework?
Head on to the Printer Application Snap repository. The master branch contains the base
snap/local/snapcraft.yaml.in file for using the framework. You can generate
snap/snapcraft.yaml by running the
./configure script. Currently, it allows to select between all_docformats and master branch of ippsample repo. If your printer can print pdf/pclm files directly, please use all_docformats branch and if your printer requires raster input please use the master branch.
You need to add the part(s) to install the printer driver package on top of the framework. First please check different branches of Printer Application Snap repository to see examples of different printer application snaps.
Whenever you are writing snap for your application, please make sure of the following things:
- All the run time dependencies are met using the
stage-packageor by adding new parts.
- When building the printer driver package, use cups directories as provided by
cups-config(or the standard locations like CUPS_SERVERBIN=
- When installing the printer driver package, your package should not ignore
- If your printer driver package uses some symlinks, please make sure they are relative and don't point to absolute paths. If they point to absolute paths, please make sure these absolute paths point to correct location after they are installed. If you have any problem with the symlinks, please check the hplip snap.
- Your driver package should use
CUPS_*environment variables whenever provided.
- If your driver package needs someplace to store data, please use the path provided by the
- Please note that if your package installs some file in
/some/path/to/file, after the snap is installed the file will be placed at
- If your printer driver package uses a language other than C/C++, you may have to provide appropriate environment variable so that it can locate required files and libraries.
- The filters will be located at
$SNAP/usr/lib/cups/filters, similarly CUPS_SERVERROOT is
If your program is accessing a location outside the snap, it can be corrected in any of the following ways:
- Write a patch and apply it using the
- Provide some environment variable, which your printer driver package understands.(FOOMATICDB in case of foomatic).
Words of Thanks
I would like to thank my mentor @tillkamppeter for his continuous guidance before and throughout the summer. I would also like to thank @akashs-india and Aveek Basu for helping me to get started with cups and cups-filters. Special thanks to @Vishal1541 for motivating me to participate in GSoC.