Chapter 7. Scanner-Specific Options

This chapter describes the implementation of scanner-specific graphical options panels.

The following major topics are discussed in this chapter:

Overview

Most scanners have capabilities that would not be available to application programs through the generic scanner API alone. A scanner-specific graphical options panel program can be developed to provide access to these capabilities.

A scanner-specific options program is run by a scanner application and communicates directly with a scanner driver, bypassing the generic scanner API. It should provide user interface elements (using Motif or a similar toolkit) for the scanner features it supports that are not supported by the generic scanner API. It should also communicate appropriate settings to the scanner driver.

The scanner driver, in turn, must respond to commands from its corresponding scanner-specific options program.

Options Program and the Scanner Driver Interface

The options program executes driver commands by writing a command number and the arguments to that command onto a pipe. The scanner driver reads the command and arguments from the pipe, then calls the options function for that command. After the options function returns, the driver writes the results of the function back to the options program.

Developers of new scanner drivers and options programs need not worry about the low-level communication between these programs; this is all taken care of in libscan.a. They do need to ensure that the scanner drivers and options programs interpret commands and arguments consistently. This is most easily accomplished by providing a common header file that the driver and the scanner options program share. This header file should contain #defines for the scanner-specific commands and typedefs for the arguments, and return values of these commands. sclopt.h, for example, is included by both the HP ScanJet scanner driver and the HP ScanJet options panel:

/*
 * sclopt.h
 * Stuff for scl scanner-specific options
 */

#define SCL_GETOPTS (SCN_SCANSPECIFIC + 0)
#define SCL_SETOPTS (SCN_SCANSPECIFIC + 1)

#define DITH_COURSE 0
#define DITH_FINE 1
#define DITH_BAYER 2
#define DITH_VERTICAL 3

typedef struct tag_sclopt {
   int intensity;                    /* Intensity of image */
   int minIntensity, maxIntensity;   /* Intensity bounds; used */
                                     /*  in GETOPTS only */
   int contrast;                     /* Image contrast */
   int minContrast, maxContrast;     /* Contrast bounds; used */
                                     /*  in GETOPTS only */
   int bwDither;                     /* if nonzero, dither  */
                                     /* black and white data */
   int bwDitherPattern;              /* specify black & white */
                                     /* dither pattern */
} SCLOPT;

sclopt.h defines two HP ScanJet-specific commands: SCL_GETOPTS and SCL_SETOPTS. Scanner-specific commands must be numbered consecutively, starting with SCN_SCANSPECIFIC and increasing monotonically from there. SCN_SCANSPECIFIC is defined in /usr/include/scanipc.h.

The SCLOPT structure is used to pass information between the HP ScanJet scanner driver and the HP ScanJet options panel. This structure is the return type of the SCL_GETOPTS command and the argument type of the SCL_SETOPTS command.

“Scanner Driver's Perspective” below describes how the scanner driver uses these #defines and typedefs. “Options Program's Perspective” describes how the scanner-specific options program uses them.

Scanner Driver's Perspective

The scanner driver implements scanner-specific options by providing a table of functions to the main.c module. In OpenScanner(), the scan.c module should set the options field of the SCANINFO struct to an array of functions implementing the scanner-specific options, and the noptions field to the number of such options. The order of the functions in the options table must correspond to the numerical order of the commands implemented (the cmd argument, below).

Each function in this array must have the following prototype:

void optionfunc(int cmd, SCARG *arg, SCRES *res);

cmd is the command number for this scanner-specific option. SCARG is defined in the file /usr/include/scandrv.h as follows:

typedef struct tag_scarg {
    void *data;
    int len;
} SCARG;

arg->data points to the arguments passed in by the scanner-specific options program, and arg->len is the number of bytes pointed to by arg->data.

SCRES is defined in the file /usr/include/scandrv.h as follows:

typedef struct tag_scres {
   void *data;
   int len;
   void *freeparam;
   void (*free)(void *param, void *data);
   int errno;
   char *errmsg;
} SCRES;

res->data should be set to point to the results of the scanner-specific option, and res->len should be set to point to the number of bytes in res->data. If an error occurs, res->errno should be set to one of the values from sys/errno.h or scanner.h. If res->free is nonzero, it is called with res->freeparam as its first argument and res->data as its second argument, after res->data has been transferred to the scanner-specific options program. This can be used to free memory that was dynamically allocated to temporarily hold the results.

For example, here is the code from the ScanJet driver that implements the HP ScanJet options:

static SCLOPT scanOptions;

static void
GetOptions(int cmd, SCARG *arg, SCRES *res)
{
   res->data = &scanOptions;
   res->len = sizeof scanOptions;
}

static void
SetOptions(int cmd, SCARG *arg, SCRES *res)
{
   scanOptions = *(SCLOPT *)arg->data;
}

SCANFUNC opttable[] = {
   GetOptions,
   SetOptions,
};

In OpenScanner(), the options member of the SCANINFO struct is set to opttable, and the noptions member is set to 2. Note that the order of the functions in opttable corresponds to the numerical order of the commands they implement.

SCANFUNC is a typedef from /usr/include/scandrv.h:

typedef void (*SCANFUNC)(int cmd, SCARG *arg, SCRES *res);


Note: The actual code in the ScanJet driver is slightly more complex; error checking has been eliminated from this example code so as not to obscure the basic functionality.

The SetOptions() function should verify that the options passed to it are valid and set res->errno to a value from the /usr/include/sys/errno.h directory or /usr/include/scanner.h if they are not. Also, the ScanJet function SetOptions() will fail if the scanner is currently scanning, because verifying that the options are valid would interrupt the scan. So, in this case, SetOptions() should set res->errno to SCEBUSY and return.

Options Program's Perspective

The scanner-specific options program is executed by libscan.a when the scanner application calls the function SCOptions(). The program is executed with command-line arguments that, when passed to the libscan.a function SCGetScanOpt(), enable a connection to be established with the scanner driver.

One of the first things that a scanner-specific options program must do, then, is call SCGetScanOpt(). This function has the following prototype (from /usr/include/scanipc.h):

SCANOPT * SCGetScanOpt(int *argc, char *argv[]);

The scanner-specific options program communicates with the scanner driver by making calls to the function SCScanOpt(), which has the following prototype (from /usr/include/scanipc.h):

int SCScanOpt(SCANOPT *s, int cmd, void *args, int arglen,
    void *res, int reslen);

The first argument to SCScanOpt() is the pointer returned by SCGetScanOpt(), above. The cmd argument is one of the command #defines from the common header file shared with the scanner driver. args is a pointer to the arguments to this command, and arglen is the number of bytes pointed to by args. This corresponds to arg->data and arg->len in the scanner driver option function for cmd.

res points to space for receiving the results of the command, and reslen is the maximum number of bytes to copy into res. The data copied into res corresponds to res->data in the scanner driver option function for cmd.

The following example code was distilled from the ScanJet options program. The ScanJet options program is a Motif program; please refer to the X and Xt Motif documentation set (see “Related Publications” in the “About This Guide” section of this manual for the full names and order numbers) for information about the non-scanning portions of the code below.

static Widget toplevel;
static SCANOPT *scan;
static SCLOPT scanOptions;
static XtAppContext appContext;

int
main(int argc, char *argv[])
{
   toplevel = XtAppInitialize(&appContext, "SJIIcOpt", NULL, 0,
              (unsigned int *)&argc, argv, fallBackResources,
              NULL, 0);

   scan = SCGetScanOpt(&argc, argv);

   if (!scan) {
       InitError(toplevel, appContext,SCErrorString(SCerrno));
   }
    
   if (SCScanOpt(scan, SCL_GETOPTS, NULL, 0,
       &scanOptions, sizeof scanOptions) < 0) {
       InitError(toplevel, appContext, SCErrorString(SCerrno));
   }

    /* ... create widgets corresponding to options ... */

   XtRealizeWidget(toplevel);
   XtAppMainLoop(appContext);
   return 0;
}

main() calls XtAppInitialize(3Xt) to initialize the X toolkit, and then calls SCGetScanOpt() to get the connection to the scanner driver. Then it calls SCScanOpt() to get the current options settings from the scanner driver.

If anything goes wrong, main() calls a function (not shown here, but part of the ScanJet options program) called InitError(), which transforms the application into a message dialog containing the error message passed as the third argument.

The ScanJet options program has an OK button that the user presses after setting up the options desired. Below is an excerpt from the callback function for that OK button.

static void
OKCallback(Widget w, XtPointer client, 
           XmAnyCallbackStruct *cb)
{
   /* ... get settings from widgets, 
          put them into scanOptions 
   ... */

   if (SCScanOpt(scan, SCL_SETOPTS, &scanOptions, 
       sizeof scanOptions, NULL, 0) < 0) {
           PostError(toplevel, SCErrorString(SCerrno), 0);
           return;
   }
   exit(0);
}

Again note the error check; PostError() is another function from the ScanJet options program, which displays a message dialog containing an error message.

Also note that we call exit() if the call to SCScanOpt() succeeds. This is because the scanner options program appears to the user to be a dialog box associated with the scanning application that executed it. The ScanJet options program also provides an Apply button for changing scanner-specific settings without dismissing the scanner-specific options program.

Installation and Testing

After you have written your scanner driver and scanner options program, copy your scanner driver to the directory /usr/lib/scan/drv, and the options program to the directory /usr/lib/scan/opt. The driver and options program must have the same base name in order for scanners, the scanner installation tool, to recognize that they go together.

Next, run scanners to install your scanner. If it was already installed before you copied your options program to /usr/lib/scan/opt, you must delete the scanner first (using the “Delete...” command on the Scanner menu).

Now you can run gscan to test your driver and options program. The “Scanner Specific Options...” command on the Parameters menu should bring up your options program.