/* sane - Scanner Access Now Easy.

   Copyright (C) 1997, 1998, 2001 Franck Schnefra, Michel Roelofs,
   Emmanuel Blot, Mikko Tyolajarvi, David Mosberger-Tang, Wolfgang Goeller,
   Petter Reinholdtsen, Gary Plewa, Sebastien Sable, Mikael Magnusson,
   Oliver Schwartz and Kevin Charter

   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This file is a component of the implementation of a backend for many
   of the the AGFA SnapScan and Acer Vuego/Prisa flatbed scanners. */


/* $Id: snapscan.c,v 1.48 2004/12/09 23:21:48 oliver-guest Exp $
   SANE SnapScan backend */

#include "../include/sane/config.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_thread.h"

#ifndef PATH_MAX
#define PATH_MAX        1024
#endif

#define EXPECTED_MAJOR       1
#define MINOR_VERSION        4
#define BUILD               42

#define BACKEND_NAME snapscan

#ifdef __GNUC__
#define UNUSEDARG __attribute__ ((unused))
#else
#define UNUSEDARG
#endif

#include "../include/sane/sanei_backend.h"
#include "../include/sane/saneopts.h"

#include "snapscan.h"

#define MIN(x,y) ((x)<(y) ? (x) : (y))
#define MAX(x,y) ((x)>(y) ? (x) : (y))
#define LIMIT(x,min,max) MIN(MAX(x, min), max)

#ifdef INOPERATIVE
#define P_200_TO_255(per) SANE_UNFIX(255.0*((per + 100)/200.0))
#endif

#include "../include/sane/sanei_config.h"

/* debug levels */
#define DL_INFO        10
#define DL_MINOR_INFO  15
#define DL_MAJOR_ERROR 1
#define DL_MINOR_ERROR 2
#define DL_DATA_TRACE  50
#define DL_CALL_TRACE  30
#define DL_VERBOSE     20

#define CHECK_STATUS(s,caller,cmd) \
if ((s) != SANE_STATUS_GOOD) { DBG(DL_MAJOR_ERROR, "%s: %s command failed: %s\n", caller, (cmd), sane_strstatus(s)); return s; }

/*----- internal scanner operations -----*/

/* hardware configuration byte masks */

#define HCFG_ADC         0x80         /* AD converter 1 ==> 10bit, 0 ==> 8bit */
#define HCFG_ADF         0x40         /* automatic document feeder */
#define HCFG_TPO         0x20         /* transparency option */
#define HCFG_RB          0x10         /* ring buffer */
#define HCFG_HT16        0x08         /* 16x16 halftone matrices */
#define HCFG_HT8         0x04         /* 8x8 halftone matrices */
#define HCFG_SRA         0x02         /* scanline row average (high-speed colour) */
#define HCFG_CAL_ALLOWED 0x01         /* 1 ==> calibration allowed */

#define HCFG_HT   0x0C                /* support halftone matrices at all */

#define MM_PER_IN 25.4                /* # millimetres per inch */
#define IN_PER_MM 0.03937        /* # inches per millimetre  */

#ifndef SANE_I18N
#define SANE_I18N(text) text
#endif

/* authorization stuff */
static SANE_Auth_Callback auth = NULL;
#if UNUSED
static SANE_Char username[SANE_MAX_USERNAME_LEN];
static SANE_Char password[SANE_MAX_PASSWORD_LEN];
#endif

/* function prototypes */

static void gamma_n (double gamma, int brightness, int contrast,
                     u_char *buf, int length);
static void gamma_to_sane (int length, u_char *in, SANE_Int *out);

static size_t max_string_size(SANE_String_Const strings[]);

/* inline functions */
static inline SnapScan_Mode actual_mode (SnapScan_Scanner *pss)
{
    if (pss->preview == SANE_TRUE)
        return pss->preview_mode;
    return pss->mode;
}

static inline int is_colour_mode (SnapScan_Mode m)
{
    return (m == MD_COLOUR) || (m == MD_BILEVELCOLOUR);
}

static inline int calibration_line_length(SnapScan_Scanner *pss)
{
    int pos_factor = pss->actual_res;
    int pixel_length;

    switch (pss->pdev->model)
    {
    case PRISA5000:
        pos_factor = 600;
        break;
    case PERFECTION1270:
    case PERFECTION1670:
        pos_factor = 800;
        break;
    case PERFECTION2480:
        pos_factor = 800;
        break;
    default:
        break;
    }
    pixel_length = pos_factor * 8.5;

    if(is_colour_mode(actual_mode(pss))) {
        return 3 * pixel_length;
    } else {
        return pixel_length;
    }
}

/*----- global data structures and access utilities -----*/

/* available device list */

static SnapScan_Device *first_device = NULL;        /* device list head */
static SANE_Int n_devices = 0;                           /* the device count */
static SANE_Char *default_firmware_filename;
static SANE_Bool cancelRead;

/* list returned from sane_get_devices() */
static const SANE_Device **get_devices_list = NULL;

/* external routines */
#include "snapscan-scsi.c"
#include "snapscan-sources.c"
#include "snapscan-usb.c"
#include "snapscan-options.c"

/* Initialize gamma tables */
static SANE_Status alloc_gamma_tables(SnapScan_Scanner * ps)
{
    static const char me[] = "alloc_gamma_tables";

    ps->gamma_length = 1 << ps->bpp;
    DBG (DL_MINOR_INFO, "%s: using 4*%d bytes for gamma table\n",
         me,
         ps->gamma_length);

    ps->gamma_tables =
        (SANE_Int *) malloc(4 * ps->gamma_length * sizeof(SANE_Int));

    if (!ps->gamma_tables)
    {
        return SANE_STATUS_NO_MEM;
    }

    ps->gamma_table_gs = &ps->gamma_tables[0 * ps->gamma_length];
    ps->gamma_table_r = &ps->gamma_tables[1 * ps->gamma_length];
    ps->gamma_table_g = &ps->gamma_tables[2 * ps->gamma_length];
    ps->gamma_table_b = &ps->gamma_tables[3 * ps->gamma_length];

    return SANE_STATUS_GOOD;
}

static SANE_Status init_gamma(SnapScan_Scanner * ps)
{
    u_char *gamma;

    gamma = (u_char*) malloc(ps->gamma_length * sizeof(u_char));

    if (!gamma)
    {
        return SANE_STATUS_NO_MEM;
    }

    /* Default tables */
    gamma_n (SANE_UNFIX(ps->gamma_gs), ps->bright, ps->contrast, gamma, ps->bpp);
    gamma_to_sane (ps->gamma_length, gamma, ps->gamma_table_gs);

    gamma_n (SANE_UNFIX(ps->gamma_r), ps->bright, ps->contrast, gamma, ps->bpp);
    gamma_to_sane (ps->gamma_length, gamma, ps->gamma_table_r);

    gamma_n (SANE_UNFIX(ps->gamma_g), ps->bright, ps->contrast, gamma, ps->bpp);
    gamma_to_sane (ps->gamma_length, gamma, ps->gamma_table_g);

    gamma_n (SANE_UNFIX(ps->gamma_b), ps->bright, ps->contrast, gamma, ps->bpp);
    gamma_to_sane (ps->gamma_length, gamma, ps->gamma_table_b);

    free (gamma);
    return SANE_STATUS_GOOD;
}

/* Max string size */

static size_t max_string_size (SANE_String_Const strings[])
{
    size_t size;
    size_t max_size = 0;
    int i;

    for (i = 0;  strings[i];  ++i)
    {
        size = strlen (strings[i]) + 1;
        if (size > max_size)
            max_size = size;
    }
    return max_size;
}

/* gamma table computation */
static void gamma_n (double gamma, int brightness, int contrast,
                      u_char *buf, int bpp)
{
    int i;
    double i_gamma = 1.0/gamma;
    int length = 1 << bpp;
    int max = length - 1;
    double mid = max / 2.0;

    for (i = 0;  i < length;  i++)
    {
        double val = (i - mid) * (1.0 + contrast / 100.0)
            + (1.0 + brightness / 100.0) * mid;
        val = LIMIT(val, 0, max);
        buf[i] =
            (u_char) LIMIT(255*pow ((double) val/max, i_gamma) + 0.5, 0, 255);
    }
}

static void gamma_from_sane (int length, SANE_Int *in, u_char *out)
{
    int i;
    for (i = 0; i < length; i++)
        out[i] = (u_char) LIMIT(in[i], 0, 255);
}

static void gamma_to_sane (int length, u_char *in, SANE_Int *out)
{
    int i;
    for (i = 0; i < length; i++)
        out[i] = in[i];
}

/* dispersed-dot dither matrices; this is discussed in Foley, Van Dam,
   Feiner and Hughes: Computer Graphics: principles and practice,
   2nd ed. (Addison-Wesley), pp 570-571.

   The function mfDn computes the nth dispersed-dot dither matrix Dn
   given D(n/2) and n; n is presumed to be a power of 2. D8 and D16
   are the matrices of interest to us, since the SnapScan supports
   only 8x8 and 16x16 dither matrices. */

static u_char D2[] ={0, 2, 3, 1};

static u_char D4[16], D8[64], D16[256];

static void mkDn (u_char *Dn, u_char *Dn_half, unsigned n)
{
    unsigned int x, y;
    for (y = 0; y < n; y++) {
        for (x = 0; x < n; x++) {
            /* Dn(x,y) = D2(2*x/n, 2*y/n) +4*Dn_half(x%(n/2), y%(n/2)) */
            Dn[y*n + x] = D2[((int)(2*y/n))*2 + (int)(2*x/n)]
                          + 4*Dn_half[(y%(n/2))*(n/2) + x%(n/2)];
        }
    }
}

static SANE_Bool device_already_in_list (SnapScan_Device *current,
                                         SANE_String_Const name)
{
    for (  ;  NULL != current;  current = current->pnext)
    {
        if (0 == strcmp (name, current->dev.name))
            return SANE_TRUE;
    }
    return SANE_FALSE;
}

static SANE_Char* get_driver_name(SnapScan_Model model_num) {
    SANE_Int i;
    for (i=0; i<known_drivers; i++) {
        if (drivers[i].id == model_num) break;
    }
    if (drivers[i].id != model_num) {
        DBG(0, "Implementation error: Driver name not found\n");
        return ("Unknown");
    }
    return (drivers[i].driver_name);
}

static SANE_Status snapscani_check_device(
    int fd,
    SnapScan_Bus bus_type,
    char* vendor,
    char* model,
    SnapScan_Model* model_num
) {
    static const char me[] = "snapscani_check_device";
    SANE_Status status = SANE_STATUS_GOOD;
    int supported_vendor = 0;
    int i;

    DBG (DL_CALL_TRACE, "%s()\n", me);

    /* check that the device is legitimate */
    if ((status = mini_inquiry (bus_type, fd, vendor, model)) != SANE_STATUS_GOOD)
    {
        DBG (DL_MAJOR_ERROR,
             "%s: mini_inquiry failed with %s.\n",
             me,
             sane_strstatus (status));
        return status;
    }

    DBG (DL_VERBOSE,
         "%s: Is vendor \"%s\" model \"%s\" a supported scanner?\n",
         me,
         vendor,
         model);

    /* check if this is one of our supported vendors */
    for (i = 0;  i < known_vendors;  i++)
    {
        if (0 == strcasecmp (vendor, vendors[i]))
        {
            supported_vendor = 1;
            break;
        }
    }
    if (supported_vendor)
    {
        /* Known vendor.  Check if it is one of our supported models */
        *model_num = snapscani_get_model_id(model, fd, bus_type);
    }
    if (!supported_vendor  ||  UNKNOWN == model_num)
    {
        DBG (DL_MINOR_ERROR,
             "%s: \"%s %s\" is not one of %s\n",
             me,
             vendor,
             model,
             "AGFA SnapScan 300, 310, 600, 1212, 1236, e10, e20, e25, e26, "
             "e40, e42, e50, e52 or e60\n"
             "Acer 300, 310, 610, 610+, "
             "620, 620+, 640, 1240, 3300, 4300 or 5300\n"
             "Guillemot MaxiScan A4 Deluxe");
        status = SANE_STATUS_INVAL;
    } else {
        DBG(DL_VERBOSE, "%s: Autodetected driver: %s\n", me, get_driver_name(*model_num));
    }
    return status;
}

static SANE_Status snapscani_init_device_structure(
    SnapScan_Device **pd,
    const SnapScan_Bus bus_type,
    SANE_String_Const name,
    const char* vendor,
    const char* model,
    const SnapScan_Model model_num
) {
    static const char me[] = "snapscani_init_device_structure";
    SANE_Status status = SANE_STATUS_GOOD;;

    DBG (DL_CALL_TRACE, "%s()\n", me);

    (*pd) = (SnapScan_Device *) malloc (sizeof (SnapScan_Device));
    if (!(*pd))
    {
        DBG (DL_MAJOR_ERROR, "%s: out of memory allocating device.", me);
        return SANE_STATUS_NO_MEM;
    }
    (*pd)->dev.name = strdup (name);
    if (strcmp(vendor, "Color") == 0) {
        (*pd)->dev.vendor = strdup ("Acer");
    } else {
        (*pd)->dev.vendor = strdup (vendor);
    }
    (*pd)->dev.model = strdup (model);
    (*pd)->dev.type = strdup (SNAPSCAN_TYPE);
    (*pd)->bus = bus_type;
    (*pd)->model = model_num;

    if (!(*pd)->dev.name  ||  !(*pd)->dev.vendor  ||  !(*pd)->dev.model  ||  !(*pd)->dev.type)
    {
        DBG (DL_MAJOR_ERROR,
             "%s: out of memory allocating device descriptor strings.\n",
             me);
        free (*pd);
        return SANE_STATUS_NO_MEM;
    }
    (*pd)->x_range.min = x_range_fb.min;
    (*pd)->x_range.quant = x_range_fb.quant;
    (*pd)->x_range.max = x_range_fb.max;
    (*pd)->y_range.min = y_range_fb.min;
    (*pd)->y_range.quant = y_range_fb.quant;
    (*pd)->y_range.max = y_range_fb.max;
    (*pd)->firmware_filename = NULL;

    (*pd)->pnext = first_device;
    first_device = (*pd);
    n_devices++;
    return status;
}

static SANE_Status add_scsi_device (SANE_String_Const full_name)
{
    int fd;
    static const char me[] = "add_scsi_device";
    SANE_Status status = SANE_STATUS_GOOD;
    SnapScan_Device *pd;
    SnapScan_Model model_num = UNKNOWN;
    SnapScan_Bus bus_type = SCSI;
    char vendor[8];
    char model[17];
    SANE_Char *name = NULL;

    DBG (DL_CALL_TRACE, "%s(%s)\n", me, full_name);

    sanei_config_get_string(full_name, &name);
    if (!name)
    {
    	return SANE_STATUS_INVAL;
    }
    /* Avoid adding the same device more then once */
    if (device_already_in_list (first_device, name)) {
        free(name);
        name = 0;
        return SANE_STATUS_GOOD;
    }

    vendor[0] = model[0] = '\0';

    DBG (DL_VERBOSE, "%s: Detected (kind of) a SCSI device\n", me);

    status = sanei_scsi_open (name, &fd, sense_handler, NULL);
    if (status != SANE_STATUS_GOOD)
    {
        DBG (DL_MAJOR_ERROR,
            "%s: error opening device %s: %s\n",
            me,
            name,
            sane_strstatus (status));
    } else {
        status = snapscani_check_device(fd, bus_type, vendor, model, &model_num);
        sanei_scsi_close(fd);
    }
    if (status == SANE_STATUS_GOOD) {
        status = snapscani_init_device_structure(
            &pd,
            bus_type,
            name,
            vendor,
            model,
            model_num
        );
    }
    free(name);
    name = 0;
    return status;
}

static SANE_Status add_usb_device (SANE_String_Const full_name) {
    static const char me[] = "add_usb_device";
    int fd;
    SnapScan_Device *pd;
    SnapScan_Model model_num = UNKNOWN;
    SANE_Word vendor_id, product_id;
    int supported_usb_vendor = 0;
    char vendor[8];
    char model[17];
    SANE_Status status = SANE_STATUS_GOOD;
    SnapScan_Bus bus_type = USB;
    int i;
    SANE_Char *name = NULL;

    DBG (DL_CALL_TRACE, "%s(%s)\n", me, full_name);
    sanei_config_get_string(full_name, &name);
    if (!name)
    {
    	return SANE_STATUS_INVAL;
    }
    /* Avoid adding the same device more then once */
    if (device_already_in_list (first_device, name)) {
        free(name);
        name = 0;
        return SANE_STATUS_GOOD;
    }

    vendor[0] = model[0] = '\0';

    DBG (DL_VERBOSE, "%s: Detected (kind of) an USB device\n", me);
    bus_type = USB;
    status = snapscani_usb_shm_init();
    if (status != SANE_STATUS_GOOD)
    {
        return status; 
    }
    status = snapscani_usb_open (name, &fd, sense_handler, NULL);
    if (status != SANE_STATUS_GOOD)
    {
        DBG (DL_MAJOR_ERROR,
            "%s: error opening device %s: %s\n",
            me,
            name,
            sane_strstatus (status));
    } else {
        if (sanei_usb_get_vendor_product(fd, &vendor_id, &product_id) ==
                SANE_STATUS_GOOD)
        {
            /* check for known USB vendors to avoid hanging scanners by
               inquiry-command.
            */
            DBG(DL_INFO, "%s: Checking if 0x%04x is a supported USB vendor ID\n",
                me, vendor_id);
            for (i = 0; i < known_usb_vendor_ids; i++) {
                if (vendor_id == usb_vendor_ids[i]) {
                    supported_usb_vendor = 1;
                }
            }
            if (!supported_usb_vendor) {
                DBG(DL_MINOR_ERROR,
                    "%s: USB vendor ID 0x%04x is currently NOT supported by the snapscan backend.\n",
                    me, vendor_id);
                status=SANE_STATUS_INVAL;
                snapscani_usb_close(fd);
            }
        }
    }
    if (status == SANE_STATUS_GOOD) {
        status = snapscani_check_device(fd, bus_type, vendor, model, &model_num);
        snapscani_usb_close(fd);
    }
    /* deinit shared memory, will be initialized again in open_scanner */
    snapscani_usb_shm_exit();     
    if (status == SANE_STATUS_GOOD) {
        status = snapscani_init_device_structure(
            &pd,
            bus_type,
            name,
            vendor,
            model,
            model_num
        );
    }
    free(name);
    name = 0;
    return status;
}

/* find_device: find a device in the available list by name

   ARG: the device name

   RET: a pointer to the corresponding device record, or NULL if there
   is no such device */

static SnapScan_Device *find_device (SANE_String_Const name)
{
    static char me[] = "find_device";
    SnapScan_Device *psd;

    DBG (DL_CALL_TRACE, "%s\n", me);

    for (psd = first_device;  psd;  psd = psd->pnext)
    {
        if (strcmp (psd->dev.name, name) == 0)
            return psd;
    }
    return NULL;
}

/*----- functions in the scanner interface -----*/

SANE_Status sane_init (SANE_Int *version_code,
                       SANE_Auth_Callback authorize)
{
    static const char me[] = "sane_snapscan_init";
    char dev_name[PATH_MAX];
    size_t len;
    FILE *fp;
    SANE_Status status;

    DBG_INIT ();

    DBG (DL_CALL_TRACE, "%s\n", me);
    DBG (DL_VERBOSE, "%s: Snapscan backend version %d.%d.%d\n",
        me,
        EXPECTED_MAJOR, MINOR_VERSION, BUILD);

    /* version check */
    if (SANE_CURRENT_MAJOR != EXPECTED_MAJOR)
    {
        DBG (DL_MAJOR_ERROR,
             "%s: this version of the SnapScan backend is intended for use\n"
             "with SANE major version %ld, but the major version of this SANE\n"
             "release is %ld. Sorry, but you need a different version of\n"
             "this backend.\n\n",
             me,
             (long) /*SANE_CURRENT_MAJOR */ V_MAJOR,
             (long) EXPECTED_MAJOR);
        return SANE_STATUS_INVAL;
    }

    if (version_code != NULL)
    {
        *version_code =
            SANE_VERSION_CODE (SANE_CURRENT_MAJOR, MINOR_VERSION, BUILD);
    }

    auth = authorize;
    /* Initialize data structures */
    default_firmware_filename = NULL;
    first_device = NULL;
    n_devices = 0;

    sanei_usb_init();
    sanei_thread_init();
    /* build a device structure */
    fp = sanei_config_open (SNAPSCAN_CONFIG_FILE);
    if (!fp)
    {
        /* default to DEFAULT_DEVICE instead of insisting on config file */
        DBG (DL_INFO,
             "%s: configuration file not found, defaulting to %s.\n",
             me,
             DEFAULT_DEVICE);
        status = add_scsi_device (DEFAULT_DEVICE);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MINOR_ERROR,
                 "%s: failed to add device \"%s\"\n",
                 me,
                 dev_name);
        }
    }
    else
    {
        while (sanei_config_read (dev_name, sizeof (dev_name), fp))
        {
            len = strlen (dev_name);
            if (!len)
                continue;                /* ignore empty lines */
            if (dev_name[0] == '#')        /* ignore line comments */
                continue;
            if (strncasecmp(dev_name, FIRMWARE_KW, strlen(FIRMWARE_KW)) == 0) {
                if (!default_firmware_filename) {
                    sanei_config_get_string(dev_name + strlen(FIRMWARE_KW), &default_firmware_filename);
                    if (default_firmware_filename == NULL) {
                        DBG (0, "%s: Illegal firmware entry %s.\n", me, dev_name);
                    }
                }
            }
            else if (strncasecmp(dev_name, OPTIONS_KW, strlen(OPTIONS_KW)) == 0)
                continue;                   /* ignore options lines */

            else if (strncmp(dev_name, "usb", 3) == 0) {
                sanei_usb_attach_matching_devices (dev_name, add_usb_device);
            }
            else if (strncmp(dev_name, "scsi", 4) == 0) {
                sanei_config_attach_matching_devices (dev_name, add_scsi_device);
            }
            else if (strstr (dev_name, "usb")) {
                add_usb_device(dev_name);
            }
            else {
                add_scsi_device(dev_name);
            }
        }
        fclose (fp);
    }

    /* compute the dither matrices */

    mkDn (D4, D2, 4);
    mkDn (D8, D4, 8);
    mkDn (D16, D8, 16);
    /* scale the D8 matrix from 0..63 to 0..255 */
    {
        u_char i;
        for (i = 0;  i < 64;  i++)
            D8[i] = (u_char) (4 * D8[i] + 2);
    }
    return SANE_STATUS_GOOD;
}

static void free_device_list(SnapScan_Device *psd) {
    if (psd->pnext != NULL) {
        free_device_list(psd->pnext);
    }
    free(psd);
}

void sane_exit (void)
{
    DBG (DL_CALL_TRACE, "sane_snapscan_exit\n");

    if (get_devices_list)
        free (get_devices_list);
    get_devices_list = NULL;

    /* just for safety, reset things to known values */
    auth = NULL;

    if (first_device) {
        free_device_list(first_device);
        first_device = NULL;
    }
    n_devices = 0;
}


SANE_Status sane_get_devices (const SANE_Device ***device_list,
                              SANE_Bool local_only)
{
    static const char *me = "sane_snapscan_get_devices";
    DBG (DL_CALL_TRACE,
         "%s (%p, %ld)\n",
         me,
         (const void *) device_list,
         (long) local_only);

    /* Waste the last list returned from this function */
    if (NULL != get_devices_list)
        free (get_devices_list);

    *device_list =
        (const SANE_Device **) malloc ((n_devices + 1) * sizeof (SANE_Device *));

    if (*device_list)
    {
        int i;
        SnapScan_Device *pdev;
        for (i = 0, pdev = first_device;  pdev;  i++, pdev = pdev->pnext)
            (*device_list)[i] = &(pdev->dev);
        (*device_list)[i] = 0x0000 /*NULL */;
    }
    else
    {
        DBG (DL_MAJOR_ERROR, "%s: out of memory\n", me);
        return SANE_STATUS_NO_MEM;
    }

    get_devices_list = *device_list;

    return SANE_STATUS_GOOD;
}

SANE_Status sane_open (SANE_String_Const name, SANE_Handle * h)
{
    static const char *me = "sane_snapscan_open";
    SnapScan_Device *psd;
    SANE_Status status;

    DBG (DL_CALL_TRACE, "%s (%s, %p)\n", me, name, (void *) h);

    /* possible authorization required */

    /* device exists? */
    psd = find_device (name);
    if (!psd)
    {
        DBG (DL_MINOR_ERROR,
             "%s: device \"%s\" not in current device list.\n",
             me,
             name);
        return SANE_STATUS_INVAL;
    }

    /* create and initialize the scanner structure */

    *h = (SnapScan_Scanner *) calloc (sizeof (SnapScan_Scanner), 1);
    if (!*h)
    {
        DBG (DL_MAJOR_ERROR,
             "%s: out of memory creating scanner structure.\n",
             me);
        return SANE_STATUS_NO_MEM;
    }

    {
        SnapScan_Scanner *pss = *(SnapScan_Scanner **) h;

        {
            pss->devname = strdup (name);
            if (!pss->devname)
            {
                free (*h);
                DBG (DL_MAJOR_ERROR,
                     "%s: out of memory copying device name.\n",
                     me);
                return SANE_STATUS_NO_MEM;
            }
            pss->pdev = psd;
            pss->opens = 0;
            pss->sense_str = NULL;
            pss->as_str = NULL;
            pss->phys_buf_sz = DEFAULT_SCANNER_BUF_SZ;
            if (psd->bus == SCSI) {
                pss->phys_buf_sz = sanei_scsi_max_request_size;
            }
            DBG (DL_DATA_TRACE,
                "%s: Allocating %lu bytes as scanner buffer.\n",
                me, (u_long) pss->phys_buf_sz);
            pss->buf = (u_char *) malloc(pss->phys_buf_sz);
            if (!pss->buf) {
                DBG (DL_MAJOR_ERROR,
                "%s: out of memory creating scanner buffer.\n",
                me);
                return SANE_STATUS_NO_MEM;
            }

            DBG (DL_VERBOSE,
                 "%s: allocated scanner structure at %p\n",
                 me,
                 (void *) pss);
        }
        status = snapscani_usb_shm_init();
        if (status != SANE_STATUS_GOOD)
        {
            return status;
        }
        status = open_scanner (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                 "%s: open_scanner failed, status: %s\n",
                 me,
                 sane_strstatus (status));
            free (pss);
            return SANE_STATUS_ACCESS_DENIED;
        }

        DBG (DL_MINOR_INFO, "%s: waiting for scanner to warm up.\n", me);
        status = wait_scanner_ready (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                "%s: error waiting for scanner to warm up: %s\n",
                me,
                sane_strstatus(status));
            free (pss);
            return status;
        }
        DBG (DL_MINOR_INFO, "%s: performing scanner self test.\n", me);
        status = send_diagnostic (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MINOR_INFO, "%s: send_diagnostic reports %s\n",
                 me, sane_strstatus (status));
            free (pss);
            return status;
        }
        DBG (DL_MINOR_INFO, "%s: self test passed.\n", me);

        /* option initialization depends on getting the hardware configuration
           byte */
        status = inquiry (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                    "%s: error in inquiry command: %s\n",
                me,
                        sane_strstatus (status));
            free (pss);
            return status;
        }

        /* Download Firmware for USB scanners */
        if ( (pss->pdev->bus == USB) && (pss->hwst & 0x02) )
        {
            char model[17];

            status = download_firmware(pss);
            CHECK_STATUS (status, me, "download_firmware");
            /* send inquiry command again, wait for scanner to initialize */
            status = wait_scanner_ready(pss);
            CHECK_STATUS (status, me, "wait_scanner_ready after firmware upload");
            status =  inquiry (pss);
            CHECK_STATUS (status, me, "inquiry after firmware upload");
            /* The model identifier may change after firmware upload */
            memcpy (model, &pss->buf[INQUIRY_PRODUCT], 16);
            model[16] = 0;
            remove_trailing_space(model);
            DBG (DL_INFO,
                "%s (after firmware upload): Checking if \"%s\" is a supported scanner\n",
                me,
                model);
            /* Check if it is one of our supported models */
            pss->pdev->model = snapscani_get_model_id(model, pss->fd, pss->pdev->bus);

            if (pss->pdev->model == UNKNOWN) {
                DBG (DL_MINOR_ERROR,
                    "%s (after firmware upload): \"%s\" is not a supported scanner\n",
                    me,
                    model);
            }
        }
        close_scanner(pss);

        status = alloc_gamma_tables (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                 "%s: error in alloc_gamma_tables: %s\n",
                 me,
                 sane_strstatus (status));
            free (pss);
            return status;
        }

        init_options (pss);
        status = init_gamma (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                 "%s: error in init_gamma: %s\n",
                 me,
                 sane_strstatus (status));
            free (pss);
            return status;
        }

        pss->state = ST_IDLE;
    }
    return SANE_STATUS_GOOD;
}

void sane_close (SANE_Handle h)
{
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;
    DBG (DL_CALL_TRACE, "sane_snapscan_close (%p)\n", (void *) h);
    switch (pss->state)
    {
    case ST_SCAN_INIT:
    case ST_SCANNING:
        release_unit (pss);
        break;
    default:
        break;
    }
    close_scanner (pss);
    snapscani_usb_shm_exit();
    free (pss->gamma_tables);
    free (pss->buf);
    free (pss);
}



SANE_Status sane_get_parameters (SANE_Handle h,
                                 SANE_Parameters *p)
{
    static const char *me = "sane_snapscan_get_parameters";
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;
    SANE_Status status = SANE_STATUS_GOOD;
    SnapScan_Mode mode = actual_mode(pss);

    DBG (DL_CALL_TRACE, "%s (%p, %p)\n", me, (void *) h, (void *) p);

    p->last_frame = SANE_TRUE;        /* we always do only one frame */

    if ((pss->state == ST_SCAN_INIT) || (pss->state == ST_SCANNING))
    {
        /* we are in the middle of a scan, so we can use the data
           that the scanner has reported */
        if (pss->psrc != NULL)
        {
            DBG(DL_DATA_TRACE, "%s: Using source chain data\n", me);
            /* use what the source chain says */
            p->pixels_per_line = pss->psrc->pixelsPerLine(pss->psrc);
            p->bytes_per_line = pss->psrc->bytesPerLine(pss->psrc);
            /* p->lines = pss->psrc->remaining(pss->psrc)/p->bytes_per_line; */
            p->lines = pss->lines;
        }
        else
        {
            DBG(DL_DATA_TRACE, "%s: Using current data\n", me);
            /* estimate based on current data */
            p->pixels_per_line = pss->pixels_per_line;
            p->bytes_per_line = pss->bytes_per_line;
            p->lines = pss->lines;
            if (mode == MD_BILEVELCOLOUR)
                p->bytes_per_line = p->pixels_per_line*3;
        }
    }
    else
    {
        /* no scan in progress. The scanner data may not be up to date.
           we have to calculate an estimate. */
        double width, height;
        int dpi;
        double dots_per_mm;

        DBG(DL_DATA_TRACE, "%s: Using estimated data\n", me);
        width = SANE_UNFIX (pss->brx - pss->tlx);
        height = SANE_UNFIX (pss->bry - pss->tly);
        dpi = pss->res;
        dots_per_mm = dpi / MM_PER_IN;
        p->pixels_per_line = width * dots_per_mm;
        p->lines = height * dots_per_mm;
        switch (mode)
        {
        case MD_COLOUR:
        case MD_BILEVELCOLOUR:
            p->bytes_per_line = 3 * p->pixels_per_line;
            break;
        case MD_LINEART:
            p->bytes_per_line = (p->pixels_per_line + 7) / 8;
            break;
        default:
            /* greyscale */
            p->bytes_per_line = p->pixels_per_line;
            break;
        }
    }
    p->format = (is_colour_mode(mode)) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
    p->depth = (mode == MD_LINEART) ? 1 : 8;

    DBG (DL_DATA_TRACE, "%s: depth = %ld\n", me, (long) p->depth);
    DBG (DL_DATA_TRACE, "%s: lines = %ld\n", me, (long) p->lines);
    DBG (DL_DATA_TRACE,
         "%s: pixels per line = %ld\n",
         me,
         (long) p->pixels_per_line);
    DBG (DL_DATA_TRACE,
         "%s: bytes per line = %ld\n",
         me,
         (long) p->bytes_per_line);

    return status;
}

/* scan data reader routine for child process */

#define READER_WRITE_SIZE 4096

static void reader (SnapScan_Scanner *pss)
{
    static char me[] = "Child reader process";
    SANE_Status status;
    SANE_Byte *wbuf = NULL;


    DBG (DL_CALL_TRACE, "%s\n", me);

    wbuf = (SANE_Byte*) malloc(READER_WRITE_SIZE);
    if (wbuf == NULL)
    {
        DBG (DL_MAJOR_ERROR, "%s: failed to allocate write buffer.\n", me);
        return;
    }

    while ((pss->preadersrc->remaining(pss->preadersrc) > 0) && !cancelRead)
    {
        SANE_Int ndata = READER_WRITE_SIZE;
        status = pss->preadersrc->get(pss->preadersrc, wbuf, &ndata);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR,
                 "%s: %s on read.\n",
                 me,
                 sane_strstatus (status));
            return;
        }
        {
            SANE_Byte *buf = wbuf;
            DBG (DL_DATA_TRACE, "READ %d BYTES (%d)\n", ndata, cancelRead);
            while (ndata > 0)
            {
                int written = write (pss->rpipe[1], buf, ndata);
                DBG (DL_DATA_TRACE, "WROTE %d BYTES\n", written);
                if (written == -1)
                {
                    DBG (DL_MAJOR_ERROR,
                         "%s: error writing scan data on parent pipe.\n",
                         me);
                    perror ("pipe error: ");
                }
                else
                {
                    ndata -= written;
                    buf += written;
                }
            }
        }
    }
}

/** signal handler to kill the child process
 */
static RETSIGTYPE usb_reader_process_sigterm_handler( int signo )
{
    DBG( DL_INFO, "(SIG) reader_process: terminated by signal %d\n", signo );
    cancelRead = SANE_TRUE;
}

static RETSIGTYPE sigalarm_handler( int signo UNUSEDARG)
{
    DBG( DL_INFO, "ALARM!!!\n" );
}

/** executed as a child process
 * read the data from the driver and send them to the parent process
 */
static int reader_process( void *args )
{
    SANE_Status      status;
    struct SIGACTION act;
    sigset_t         ignore_set;
    SnapScan_Scanner *pss = (SnapScan_Scanner *) args;

    if( sanei_thread_is_forked()) {
        DBG( DL_MINOR_INFO, "reader_process started (forked)\n" );
        /* child process - close read side, make stdout the write side of the pipe */
        close( pss->rpipe[0] );            
        pss->rpipe[0] = -1;
    } else {
        DBG( DL_MINOR_INFO, "reader_process started (as thread)\n" );
    }

    sigfillset ( &ignore_set );
    sigdelset  ( &ignore_set, SIGUSR1 );
    sigprocmask( SIG_SETMASK, &ignore_set, 0 );

    memset   ( &act, 0, sizeof (act));
    sigaction( SIGTERM, &act, 0 );

    cancelRead = SANE_FALSE;

    /* install the signal handler */
    sigemptyset(&(act.sa_mask));
    act.sa_flags = 0;

    act.sa_handler = usb_reader_process_sigterm_handler;
    sigaction( SIGUSR1, &act, 0 );

    status = create_base_source (pss, SCSI_SRC, &(pss->preadersrc));
    if (status == SANE_STATUS_GOOD)
    {
        reader (pss);
    }
    else
    {
        DBG (DL_MAJOR_ERROR,
                "Reader process: failed to create SCSISource.\n");
    }
    pss->preadersrc->done(pss->preadersrc);
    free(pss->preadersrc);
    pss->preadersrc = 0;
    close( pss->rpipe[1] );            
    pss->rpipe[1] = -1;        
    DBG( DL_MINOR_INFO, "reader_process: finished reading data\n" );
    return SANE_STATUS_GOOD;
}


static SANE_Status start_reader (SnapScan_Scanner *pss)
{
    SANE_Status status = SANE_STATUS_GOOD;
    static char me[] = "start_reader";

    DBG (DL_CALL_TRACE, "%s\n", me);

    pss->nonblocking = SANE_FALSE;
    pss->rpipe[0] = pss->rpipe[1] = -1;
    pss->child = -1;

    if (pipe (pss->rpipe) != -1)
    {
        pss->orig_rpipe_flags = fcntl (pss->rpipe[0], F_GETFL, 0);
        pss->child =  sanei_thread_begin(reader_process, (void *) pss);
        
        cancelRead = SANE_FALSE;
        
        if (pss->child < 0)
        {
            /* we'll have to read in blocking mode */
            DBG (DL_MAJOR_ERROR,
                 "%s: Error while calling sanei_thread_begin; must read in blocking mode.\n",
                 me);
            close (pss->rpipe[0]);
            close (pss->rpipe[1]);
            status = SANE_STATUS_UNSUPPORTED;
        }
        if (sanei_thread_is_forked())
        {
            /* parent; close write side */
            close (pss->rpipe[1]);
            pss->rpipe[1] = -1;
        }
        pss->nonblocking = SANE_TRUE;
    }
    return status;
}

static SANE_Status send_gamma_table (SnapScan_Scanner *pss, u_char dtc, u_char dtcq)
{
    static char me[] = "send_gamma_table";
    SANE_Status status = SANE_STATUS_GOOD;
    status = send (pss, dtc, dtcq);
    CHECK_STATUS (status, me, "send");
    switch (pss->pdev->model)
    {
        case PERFECTION1270:
        case PERFECTION1670:
        case PERFECTION2480:
            /* Some epson scanners need the gamma table twice */
            status = send (pss, dtc, dtcq);
            CHECK_STATUS (status, me, "2nd send");
            break;
        default:
            break;
    }
    return status;
}

static SANE_Status download_gamma_tables (SnapScan_Scanner *pss)
{
    static char me[] = "download_gamma_tables";
    SANE_Status status = SANE_STATUS_GOOD;
    double gamma_gs = SANE_UNFIX (pss->gamma_gs);
    double gamma_r = SANE_UNFIX (pss->gamma_r);
    double gamma_g = SANE_UNFIX (pss->gamma_g);
    double gamma_b = SANE_UNFIX (pss->gamma_b);
    SnapScan_Mode mode = actual_mode (pss);
    int dtcq_gamma_gray;
    int dtcq_gamma_red;
    int dtcq_gamma_green;
    int dtcq_gamma_blue;

    DBG (DL_CALL_TRACE, "%s\n", me);
    switch (mode)
    {
    case MD_COLOUR:
        break;
    case MD_BILEVELCOLOUR:
        if (!pss->halftone)
        {
            gamma_r =
            gamma_g =
            gamma_b = 1.0;
        }
        break;
    case MD_LINEART:
        if (!pss->halftone)
            gamma_gs = 1.0;
        break;
    default:
        /* no further action for greyscale */
        break;
    }

    DBG (DL_DATA_TRACE, "%s: Sending gamma table for %d bpp\n", me, pss->bpp);
    switch (pss->bpp)
    {
    case 10:
        dtcq_gamma_gray = DTCQ_GAMMA_GRAY10;
        dtcq_gamma_red = DTCQ_GAMMA_RED10;
        dtcq_gamma_green = DTCQ_GAMMA_GREEN10;
        dtcq_gamma_blue = DTCQ_GAMMA_BLUE10;
        break;
    case 14:
        dtcq_gamma_gray = DTCQ_GAMMA_GRAY14;
        dtcq_gamma_red = DTCQ_GAMMA_RED14;
        dtcq_gamma_green = DTCQ_GAMMA_GREEN14;
        dtcq_gamma_blue = DTCQ_GAMMA_BLUE14;
        break;
    default:
        dtcq_gamma_gray = DTCQ_GAMMA_GRAY8;
        dtcq_gamma_red = DTCQ_GAMMA_RED8;
        dtcq_gamma_green = DTCQ_GAMMA_GREEN8;
        dtcq_gamma_blue = DTCQ_GAMMA_BLUE8;
        break;
    }

    if (is_colour_mode(mode))
    {
        if (pss->val[OPT_CUSTOM_GAMMA].b)
        {
            if (pss->val[OPT_GAMMA_BIND].b)
            {
                /* Use greyscale gamma for all rgb channels */
                gamma_from_sane (pss->gamma_length, pss->gamma_table_gs,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_red);
                CHECK_STATUS (status, me, "send");

                gamma_from_sane (pss->gamma_length, pss->gamma_table_gs,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_green);
                CHECK_STATUS (status, me, "send");

                gamma_from_sane (pss->gamma_length, pss->gamma_table_gs,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_blue);
                CHECK_STATUS (status, me, "send");
            }
            else
            {
                gamma_from_sane (pss->gamma_length, pss->gamma_table_r,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_red);
                CHECK_STATUS (status, me, "send");

                gamma_from_sane (pss->gamma_length, pss->gamma_table_g,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_green);
                CHECK_STATUS (status, me, "send");

                gamma_from_sane (pss->gamma_length, pss->gamma_table_b,
                                 pss->buf + SEND_LENGTH);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_blue);
                CHECK_STATUS (status, me, "send");
            }
        }
        else
        {
            if (pss->val[OPT_GAMMA_BIND].b)
            {
                /* Use greyscale gamma for all rgb channels */
                gamma_n (gamma_gs, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_red);
                CHECK_STATUS (status, me, "send");

                gamma_n (gamma_gs, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_green);
                CHECK_STATUS (status, me, "send");

                gamma_n (gamma_gs, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_blue);
                CHECK_STATUS (status, me, "send");
            }
            else
            {
                gamma_n (gamma_r, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_red);
                CHECK_STATUS (status, me, "send");

                gamma_n (gamma_g, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_green);
                CHECK_STATUS (status, me, "send");

                gamma_n (gamma_b, pss->bright, pss->contrast,
                         pss->buf + SEND_LENGTH, pss->bpp);
                status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_blue);
                CHECK_STATUS (status, me, "send");
            }
        }
    }
    else
    {
        if(pss->val[OPT_CUSTOM_GAMMA].b)
        {
            gamma_from_sane (pss->gamma_length, pss->gamma_table_gs,
                             pss->buf + SEND_LENGTH);
            status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_gray);
            CHECK_STATUS (status, me, "send");
        }
        else
        {
            gamma_n (gamma_gs, pss->bright, pss->contrast,
                     pss->buf + SEND_LENGTH, pss->bpp);
            status = send_gamma_table(pss, DTC_GAMMA, dtcq_gamma_gray);
            CHECK_STATUS (status, me, "send");
        }
    }
    return status;
}

static SANE_Status download_halftone_matrices (SnapScan_Scanner *pss)
{
    static char me[] = "download_halftone_matrices";
    SANE_Status status = SANE_STATUS_GOOD;
    if ((pss->halftone) &&
        ((actual_mode(pss) == MD_LINEART) || (actual_mode(pss) == MD_BILEVELCOLOUR)))
    {
        u_char *matrix;
        size_t matrix_sz;
        u_char dtcq;

        if (pss->dither_matrix == dm_dd8x8)
        {
            matrix = D8;
            matrix_sz = sizeof (D8);
        }
        else
        {
            matrix = D16;
            matrix_sz = sizeof (D16);
        }

        memcpy (pss->buf + SEND_LENGTH, matrix, matrix_sz);

        if (is_colour_mode(actual_mode(pss)))
        {
            if (matrix_sz == sizeof (D8))
                dtcq = DTCQ_HALFTONE_COLOR8;
            else
                dtcq = DTCQ_HALFTONE_COLOR16;

            /* need copies for green and blue bands */
            memcpy (pss->buf + SEND_LENGTH + matrix_sz,
                    matrix,
                    matrix_sz);
            memcpy (pss->buf + SEND_LENGTH + 2 * matrix_sz,
                    matrix,
                    matrix_sz);
        }
        else
        {
            if (matrix_sz == sizeof (D8))
                dtcq = DTCQ_HALFTONE_BW8;
            else
                dtcq = DTCQ_HALFTONE_BW16;
        }

        status = send (pss, DTC_HALFTONE, dtcq);
        CHECK_STATUS (status, me, "send");
    }
    return status;
}

static SANE_Status measure_transfer_rate (SnapScan_Scanner *pss)
{
    static char me[] = "measure_transfer_rate";
    SANE_Status status = SANE_STATUS_GOOD;

    if (pss->hconfig & HCFG_RB)
    {
        /* We have a ring buffer. We simulate one round of a read-store
           cycle on the size of buffer we will be using. For this read only,
           the buffer size must be rounded to a 128-byte boundary. */

        DBG (DL_VERBOSE, "%s: have ring buffer\n", me);
        pss->expected_read_bytes =
            (pss->buf_sz%128)  ?  (pss->buf_sz/128 + 1)*128  :  pss->buf_sz;

        status = scsi_read (pss, READ_TRANSTIME);
        CHECK_STATUS (status, me, "scsi_read");
        pss->expected_read_bytes = 0;
        status = scsi_read (pss, READ_TRANSTIME);
        CHECK_STATUS (status, me, "scsi_read");
    }
    else
    {
        /* we don't have a ring buffer. The test requires transferring one
           scan line of data (rounded up to next 128 byte boundary). */

        DBG (DL_VERBOSE, "%s: we don't have a ring buffer.\n", me);
        pss->expected_read_bytes = pss->bytes_per_line;

        if (pss->expected_read_bytes%128)
        {
            pss->expected_read_bytes =
                (pss->expected_read_bytes/128 + 1)*128;
        }
        status = scsi_read (pss, READ_TRANSTIME);
        CHECK_STATUS (status, me, "scsi_read");
        DBG (DL_VERBOSE, "%s: read %ld bytes.\n", me, (long) pss->read_bytes);
    }

    pss->expected_read_bytes = 0;
    status = scsi_read (pss, READ_TRANSTIME);
    if (status != SANE_STATUS_GOOD)
    {
        DBG (DL_MAJOR_ERROR, "%s: test read failed.\n", me);
        return status;
    }

    DBG (DL_VERBOSE, "%s: successfully calibrated transfer rate.\n", me);
    return status;
}


SANE_Status sane_start (SANE_Handle h)
{
    static const char *me = "sane_snapscan_start";
    SANE_Status status;
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;

    DBG (DL_CALL_TRACE, "%s (%p)\n", me, (void *) h);

    /* possible authorization required */

    status = open_scanner (pss);
    CHECK_STATUS (status, me, "open_scanner");

    status = wait_scanner_ready (pss);
    CHECK_STATUS (status, me, "wait_scanner_ready");

    /* start scanning; reserve the unit first, because a release_unit is
       necessary to abort a scan in progress */

    pss->state = ST_SCAN_INIT;

    reserve_unit(pss);

    /* set up the window and fetch the resulting scanner parameters */
    status = set_window(pss);
    CHECK_STATUS (status, me, "set_window");

    status = inquiry(pss);
    CHECK_STATUS (status, me, "inquiry");

    /* download the gamma and halftone tables */

    status = download_gamma_tables(pss);
    CHECK_STATUS (status, me, "download_gamma_tables");
    
    status = download_halftone_matrices(pss);
    CHECK_STATUS (status, me, "download_halftone_matrices");

    if (pss->pdev->model == PERFECTION2480 && pss->val[OPT_QUALITY_CAL].b)
    {
        status = calibrate(pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR, "%s: calibration failed.\n", me);
            release_unit (pss);
            return status;
        }
    }

    /* we must measure the data transfer rate between the host and the
       scanner, and the method varies depending on whether there is a
       ring buffer or not. */

    status = measure_transfer_rate(pss);
    CHECK_STATUS (status, me, "measure_transfer_rate");

    /* now perform an inquiry again to retrieve the scan speed */
    status = inquiry(pss);
    CHECK_STATUS (status, me, "inquiry");

    DBG (DL_DATA_TRACE,
         "%s: after measuring speed:\n\t%lu bytes per scan line\n"
         "\t%f milliseconds per scan line.\n\t==>%f bytes per millisecond\n",
         me,
         (u_long) pss->bytes_per_line,
         pss->ms_per_line,
         pss->bytes_per_line/pss->ms_per_line);


    if (pss->pdev->model != PERFECTION2480 && pss->val[OPT_QUALITY_CAL].b)
    {
        status = calibrate(pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR, "%s: calibration failed.\n", me);
            release_unit (pss);
            return status;
        }
    }

    status = scan(pss);
    if (status != SANE_STATUS_GOOD)
    {
        DBG (DL_MAJOR_ERROR, "%s: scan command failed: %s.\n", me, sane_strstatus(status));
        release_unit (pss);
        return status;
    }

    if (pss->source == SRC_ADF)
    {
        /* Wait for scanner ready again (e.g. until paper is loaded from an ADF) */
        /* Maybe replace with get_data_buffer_status()? */
        status = wait_scanner_ready (pss);
        if (status != SANE_STATUS_GOOD)
        {
            DBG (DL_MAJOR_ERROR, "%s: scan command failed while waiting for scanner: %s.\n", me, sane_strstatus(status));
            release_unit (pss);
            return status;
        }
    }

    DBG (DL_MINOR_INFO, "%s: starting the reader process.\n", me);
    status = start_reader(pss);
    {
        BaseSourceType st = FD_SRC;
        if (status != SANE_STATUS_GOOD)
            st = SCSI_SRC;
        status = create_source_chain (pss, st, &(pss->psrc));
    }

    return status;
}


SANE_Status sane_read (SANE_Handle h,
                       SANE_Byte *buf,
                       SANE_Int maxlen,
                       SANE_Int *plen)
{
    static const char *me = "sane_snapscan_read";
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;
    SANE_Status status = SANE_STATUS_GOOD;

    DBG (DL_CALL_TRACE,
        "%s (%p, %p, %ld, %p)\n",
        me,
        (void *) h,
        (void *) buf,
        (long) maxlen,
        (void *) plen);

    *plen = 0;

    if (pss->state == ST_CANCEL_INIT) {
        pss->state = ST_IDLE;
        return SANE_STATUS_CANCELLED;
    }

    if (pss->psrc == NULL  ||  pss->psrc->remaining(pss->psrc) == 0)
    {
        if (pss->child > 0)
        {
            sanei_thread_waitpid (pss->child, 0);        /* ensure no zombies */
            pss->child = -1;
        }
        release_unit (pss);
        close_scanner (pss);
        if (pss->psrc != NULL)
        {
            pss->psrc->done(pss->psrc);
            free(pss->psrc);
            pss->psrc = NULL;
        }
        pss->state = ST_IDLE;
        return SANE_STATUS_EOF;
    }

    *plen = maxlen;
    status = pss->psrc->get(pss->psrc, buf, plen);

    switch (pss->state)
    {
    case ST_IDLE:
        DBG (DL_MAJOR_ERROR,
            "%s: weird error: scanner state should not be idle on call to "
            "sane_read.\n",
            me);
        break;
    case ST_SCAN_INIT:
        /* we've read some data */
        pss->state = ST_SCANNING;
        break;
    case ST_CANCEL_INIT:
        /* stop scanning */
        status = SANE_STATUS_CANCELLED;
        break;
    default:
        break;
    }

    return status;
}

void sane_cancel (SANE_Handle h)
{
    char *me = "sane_snapscan_cancel";
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;
    struct SIGACTION act;
    pid_t            res;

    DBG (DL_CALL_TRACE, "%s\n", me);
    switch (pss->state)
    {
    case ST_IDLE:
        break;
    case ST_SCAN_INIT:
    case ST_SCANNING:
        /* signal a cancellation has occurred */
        pss->state = ST_CANCEL_INIT;
        /* signal the reader, if any */
        if (pss->child > 0)
        {
            DBG( DL_INFO, ">>>>>>>> killing reader_process <<<<<<<<\n" );

            sigemptyset(&(act.sa_mask));
            act.sa_flags = 0;

            act.sa_handler = sigalarm_handler;
            sigaction( SIGALRM, &act, 0 );

            if (sanei_thread_is_forked())
            {
                /* use SIGUSR1 to set cancelRead in child process */
                sanei_thread_sendsig( pss->child, SIGUSR1 );
            }
            else
            {
                cancelRead = SANE_TRUE;
            }
               
            /* give'em 10 seconds 'til done...*/
            alarm(10);
            res = sanei_thread_waitpid( pss->child, 0 );
            alarm(0);

            if( res != pss->child ) {
                DBG( DL_MINOR_ERROR,"sanei_thread_waitpid() failed !\n");

                /* do it the hard way...*/
#ifdef USE_PTHREAD
                sanei_thread_kill( pss->child );
#else
                sanei_thread_sendsig( pss->child, SIGKILL );
#endif
            }
            pss->child = -1;
            DBG( DL_INFO,"reader_process killed\n");
        }
        release_unit (pss);
        close_scanner (pss);
        break;
    case ST_CANCEL_INIT:
        DBG (DL_INFO, "%s: cancellation already initiated.\n", me);
        break;
    default:
        DBG (DL_MAJOR_ERROR,
             "%s: weird error: invalid scanner state (%ld).\n",
             me,
             (long) pss->state);
        break;
    }
}

SANE_Status sane_set_io_mode (SANE_Handle h, SANE_Bool m)
{
    static char me[] = "sane_snapscan_set_io_mode";
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;
    char *op;

    DBG (DL_CALL_TRACE, "%s\n", me);

    if (pss->state != ST_SCAN_INIT)
        return SANE_STATUS_INVAL;

    if (m)
    {
        if (pss->child == -1)
        {
            DBG (DL_MINOR_INFO,
                 "%s: no reader child; must use blocking mode.\n",
                 me);
            return SANE_STATUS_UNSUPPORTED;
        }
        op = "ON";
        fcntl (pss->rpipe[0], F_SETFL, O_NONBLOCK | pss->orig_rpipe_flags);
    }
    else
    {
        op = "OFF";
        fcntl (pss->rpipe[0], F_SETFL, pss->orig_rpipe_flags);
    }
    DBG (DL_MINOR_INFO, "%s: turning nonblocking mode %s.\n", me, op);
    pss->nonblocking = m;
    return SANE_STATUS_GOOD;
}

SANE_Status sane_get_select_fd (SANE_Handle h, SANE_Int * fd)
{
    static char me[] = "sane_snapscan_get_select_fd";
    SnapScan_Scanner *pss = (SnapScan_Scanner *) h;

    DBG (DL_CALL_TRACE, "%s\n", me);

    if (pss->state != ST_SCAN_INIT)
        return SANE_STATUS_INVAL;

    if (pss->child == -1)
    {
        DBG (DL_MINOR_INFO,
             "%s: no reader child; cannot provide select file descriptor.\n",
             me);
        return SANE_STATUS_UNSUPPORTED;
    }
    *fd = pss->rpipe[0];
    return SANE_STATUS_GOOD;
}

/*
 * $Log: snapscan.c,v $
 * Revision 1.48  2004/12/09 23:21:48  oliver-guest
 * Added quality calibration for Epson 2480 (by Simon Munton)
 *
 * Revision 1.47  2004/12/01 22:49:14  oliver-guest
 * Fix for allocation of gamma tables by Simon Munton
 *
 * Revision 1.46  2004/12/01 22:12:03  oliver-guest
 * Added support for Epson 1270
 *
 * Revision 1.45  2004/10/03 17:34:36  hmg-guest
 * 64 bit platform fixes (bug #300799).
 *
 * Revision 1.44  2004/09/02 20:59:12  oliver-guest
 * Added support for Epson 2480
 *
 * Revision 1.43  2004/06/16 19:52:26  oliver-guest
 * Don't enforce even number of URB packages on 1212u_2. Fixes bug #300753.
 *
 * Revision 1.42  2004/06/15 12:17:37  hmg-guest
 * Only use __attribute__ if gcc is used for compilation. Some other compilers
 * don't know __attribute__ and therefore can't compile sane-backends without this
 * fix. See bug #300803.
 *
 * Revision 1.41  2004/05/26 22:37:01  oliver-guest
 * Use shared memory for urb counters in snapscan backend
 *
 * Revision 1.40  2004/04/09 11:59:02  oliver-guest
 * Fixes for pthread implementation
 *
 * Revision 1.39  2004/04/08 21:53:10  oliver-guest
 * Use sanei_thread in snapscan backend
 *
 * Revision 1.37  2003/11/27 23:11:32  oliver-guest
 * Send gamma table twice for Epson Perfection 1670
 *
 * Revision 1.36  2003/11/09 21:43:45  oliver-guest
 * Disabled quality calibration for Epson Perfection 1670
 *
 * Revision 1.35  2003/11/07 23:26:49  oliver-guest
 * Final bugfixes for bascic support of Epson 1670
 *
 * Revision 1.34  2003/10/21 20:43:25  oliver-guest
 * Bugfixes for SnapScan backend
 *
 * Revision 1.33  2003/10/07 18:29:20  oliver-guest
 * Initial support for Epson 1670, minor bugfix
 *
 * Revision 1.32  2003/09/24 18:05:39  oliver-guest
 * Bug #300198: Check second argument of sanei_config_get_string
 *
 * Revision 1.31  2003/09/12 16:10:33  hmg-guest
 * Moved union Option_Value from backend header files to sanei_backend.h. No need
 * to copy it over and over again. Changed header inclusion order in backend
 * files to include backend.h after sanei_backend.h. Based on a patch from stef
 * <stef-listes@wanadoo.fr>.
 *
 * Revision 1.30  2003/08/19 21:05:08  oliverschwartz
 * Scanner ID cleanup
 *
 * Revision 1.29  2003/04/30 20:49:40  oliverschwartz
 * SnapScan backend 1.4.26
 *
 * Revision 1.58  2003/04/30 20:43:07  oliverschwartz
 * Set backend version number to 1.4.26
 *
 * Revision 1.57  2003/04/02 21:17:14  oliverschwartz
 * Fix for 1200 DPI with Acer 5000
 *
 * Revision 1.56  2003/02/08 10:45:09  oliverschwartz
 * Use 600 DPI as optical resolution for Benq 5000
 *
 * Revision 1.55  2003/01/08 21:16:17  oliverschwartz
 * Added support for Acer / Benq 310U
 *
 * Revision 1.54  2002/12/10 20:14:12  oliverschwartz
 * Enable color offset correction for SnapScan300
 *
 * Revision 1.53  2002/10/31 19:29:41  oliverschwartz
 * Set version to 1.4.17
 *
 * Revision 1.52  2002/10/12 10:40:48  oliverschwartz
 * Added support for Snapscan e10
 *
 * Revision 1.51  2002/09/26 19:27:44  oliverschwartz
 * Version 1.4.16
 *
 * Revision 1.50  2002/09/24 16:07:44  oliverschwartz
 * Added support for Benq 5000
 *
 * Revision 1.49  2002/07/12 22:53:54  oliverschwartz
 * Version 1.4.15
 *
 * Revision 1.48  2002/07/12 22:53:16  oliverschwartz
 * call sanei_usb_init() before sanei_usb_attach_matching_devices()
 *
 * Revision 1.47  2002/06/06 21:16:23  oliverschwartz
 * Set backend version to 1.4.14
 *
 * Revision 1.46  2002/06/06 20:40:01  oliverschwartz
 * Changed default scan area for transparancy unit of SnapScan e50
 *
 * Revision 1.45  2002/05/02 18:29:34  oliverschwartz
 * - Added ADF support
 * - Fixed status handling after cancel
 *
 * Revision 1.44  2002/04/27 14:42:30  oliverschwartz
 * Cleanup of debug logging
 *
 * Revision 1.43  2002/04/23 22:40:33  oliverschwartz
 * Improve config file reading
 *
 * Revision 1.42  2002/04/10 21:00:09  oliverschwartz
 * Check for NULL pointer before deleting device list
 *
 * Revision 1.41  2002/03/24 12:12:36  oliverschwartz
 * - Moved option functions to snapscan-options.c
 * - Autodetect USB scanners
 * - Cleanup
 *
 * Revision 1.40  2002/02/09 14:55:23  oliverschwartz
 * Added language translation support (SANE_I18N)
 *
 * Revision 1.39  2002/01/23 20:40:54  oliverschwartz
 * Don't use quantization for scan area parameter
 * Improve recognition of Acer 320U
 * Version 1.4.7
 *
 * Revision 1.38  2002/01/14 21:11:56  oliverschwartz
 * Add workaround for bug semctl() call in libc for PPC
 *
 * Revision 1.37  2002/01/10 21:33:12  oliverschwartz
 * Set version number to 1.4.4
 *
 * Revision 1.36  2002/01/06 18:34:02  oliverschwartz
 * Added support for Snapscan e42 thanks to Yari Adán Petralanda
 *
 * Revision 1.35  2001/12/20 23:18:01  oliverschwartz
 * Remove tmpfname
 *
 * Revision 1.34  2001/12/18 18:28:35  oliverschwartz
 * Removed temporary file
 *
 * Revision 1.33  2001/12/12 19:43:30  oliverschwartz
 * - Set version number to 1.4.3
 * - Clean up CVS Log
 *
 * Revision 1.32  2001/12/09 23:06:45  oliverschwartz
 * - use sense handler for USB if scanner reports CHECK_CONDITION
 *
 * Revision 1.31  2001/12/08 11:50:34  oliverschwartz
 * Fix dither matrix computation
 *
 * Revision 1.30  2001/11/29 22:50:14  oliverschwartz
 * Add support for SnapScan e52
 *
 * Revision 1.29  2001/11/27 23:16:17  oliverschwartz
 * - Fix color alignment for SnapScan 600
 * - Added documentation in snapscan-sources.c
 * - Guard against TL_X < BR_X and TL_Y < BR_Y
 *
 * Revision 1.28  2001/11/25 18:51:41  oliverschwartz
 * added support for SnapScan e52 thanks to Rui Lopes
 *
 * Revision 1.27  2001/11/16 20:28:35  oliverschwartz
 * add support for Snapscan e26
 *
 * Revision 1.26  2001/11/16 20:23:16  oliverschwartz
 * Merge with sane-1.0.6
 *   - Check USB vendor IDs to avoid hanging scanners
 *   - fix bug in dither matrix computation
 *
 * Revision 1.25  2001/10/25 11:06:22  oliverschwartz
 * Change snapscan backend version number to 1.4.0
 *
 * Revision 1.24  2001/10/11 14:02:10  oliverschwartz
 * Distinguish between e20/e25 and e40/e50
 *
 * Revision 1.23  2001/10/09 22:34:23  oliverschwartz
 * fix compiler warnings
 *
 * Revision 1.22  2001/10/08 19:26:01  oliverschwartz
 * - Disable quality calibration for scanners that do not support it
 *
 * Revision 1.21  2001/10/08 18:22:02  oliverschwartz
 * - Disable quality calibration for Acer Vuego 310F
 * - Use sanei_scsi_max_request_size as scanner buffer size
 *   for SCSI devices
 * - Added new devices to snapscan.desc
 *
 * Revision 1.20  2001/09/18 15:01:07  oliverschwartz
 * - Read scanner id string again after firmware upload
 *   to indentify correct model
 * - Make firmware upload work for AGFA scanners
 * - Change copyright notice
 *
 * Revision 1.19  2001/09/17 10:01:08  sable
 * Added model AGFA 1236U
 *
 * Revision 1.18  2001/09/10 10:16:32  oliverschwartz
 * better USB / SCSI recognition, correct max scan area for 1236+TPO
 *
 * Revision 1.17  2001/09/09 18:06:32  oliverschwartz
 * add changes from Acer (new models; automatic firmware upload for USB scanners); fix distorted colour scans after greyscale scans (call set_window only in sane_start); code cleanup
 *
 * Revision 1.16  2001/09/07 09:42:13  oliverschwartz
 * Sync with Sane-1.0.5
 *
 * Revision 1.15  2001/05/15 20:51:14  oliverschwartz
 * check for pss->devname instead of name in sane_open()
 *
 * Revision 1.14  2001/04/10 13:33:06  sable
 * Transparency adapter bug and xsane crash corrections thanks to Oliver Schwartz
 *
 * Revision 1.13  2001/04/10 13:00:31  sable
 * Moving sanei_usb_* to snapscani_usb*
 *
 * Revision 1.12  2001/04/10 11:04:31  sable
 * Adding support for snapscan e40 an e50 thanks to Giuseppe Tanzilli
 *
 * Revision 1.11  2001/03/17 22:53:21  sable
 * Applying Mikael Magnusson patch concerning Gamma correction
 * Support for 1212U_2
 *
 * Revision 1.4  2001/03/04 16:50:53  mikael
 * Added Scan Mode, Geometry, Enhancement and Advanced groups. Implemented brightness and contrast controls with gamma tables. Added Quality Calibration, Analog Gamma Bind, Custom Gamma and Gamma Vector GS,R,G,B options.
 *
 * Revision 1.3  2001/02/16 18:32:28  mikael
 * impl calibration, signed position, increased buffer size
 *
 * Revision 1.2  2001/02/10 18:18:29  mikael
 * Extended x and y ranges
 *
 * Revision 1.1.1.1  2001/02/10 17:09:29  mikael
 * Imported from snapscan-11282000.tar.gz
 *
 * Revision 1.10  2000/11/10 01:01:59  sable
 * USB (kind of) autodetection
 *
 * Revision 1.9  2000/11/01 01:26:43  sable
 * Support for 1212U
 *
 * Revision 1.8  2000/10/30 22:31:13  sable
 * Auto preview mode
 *
 * Revision 1.7  2000/10/28 14:16:10  sable
 * Bug correction for SnapScan310
 *
 * Revision 1.6  2000/10/28 14:06:35  sable
 * Add support for Acer300f
 *
 * Revision 1.5  2000/10/15 19:52:06  cbagwell
 * Changed USB support to a 1 line modification instead of multi-file
 * changes.
 *
 * Revision 1.4  2000/10/13 03:50:27  cbagwell
 * Updating to source from SANE 1.0.3.  Calling this versin 1.1
 *
 * Revision 1.3  2000/08/12 15:09:35  pere
 * Merge devel (v1.0.3) into head branch.
 *
 * Revision 1.1.1.1.2.5  2000/07/29 16:04:33  hmg
 * 2000-07-29  Henning Meier-Geinitz <hmg@gmx.de>
 *
 *         * backend/GUIDE: Added some comments about portability and
 *           documentation.
 *         * backend/abaton.c backend/agfafocus.c backend/apple.c
 *           backend/canon.c backend/coolscan.c backend/dc210.c backend/dc25.c
 *           backend/dll.c backend/dmc.c backend/microtek.c backend/microtek2.c
 *            backend/microtek2.c backend/mustek_pp.c backend/net.c backend/pint.c
 *           backend/pnm.c backend/qcam.c backend/ricoh.c backend/s9036.c
 *           backend/sane_strstatus.c backend/sharp.c backend/snapscan.c
 *           backend/st400.c backend/stubs.c backend/tamarack.c backend/v4l.c:
 *           Changed include statements from #include <sane/...> to
 *           #include "sane...".
 *         * backend/avision.c backend/dc25.c: Use DBG(0, ...) instead of
 *            fprintf (stderr, ...)
 *         * backend/avision.c backend/canon-sane.c backend/coolscan.c
 *           backend/dc25.c backend/microtek.c backend/microtek2.c
 *            backend/st400.c: Use sanei_config_read() instead of fgets().
 *         * backend/coolscan.desc backend/microtek.desc backend/microtek2.desc
 *           backend/st400.desc: Added :interface and :manpage entries.
 *         * backend/nec.desc: Status is beta now (was: new). Fixed typo.
 *         * doc/canon.README: Removed, because the information is included in
 *            the manpage now.
 *         * doc/Makefile.in: Added sane-coolscan to list of mapages to install.
 *         * README: Added Link to coolscan manpage.
 *         * backend/mustek.*: Update to Mustek backend 1.0-94. Fixed the
 *           #include <sane/...> bug.
 *
 * Revision 1.1.1.1.2.4  2000/07/25 21:47:43  hmg
 * 2000-07-25  Henning Meier-Geinitz <hmg@gmx.de>
 *
 *         * backend/snapscan.c: Use DBG(0, ...) instead of fprintf (stderr, ...).
 *         * backend/abaton.c backend/agfafocus.c backend/apple.c backend/dc210.c
 *            backend/dll.c backend/dmc.c backend/microtek2.c backend/pint.c
 *           backend/qcam.c backend/ricoh.c backend/s9036.c backend/snapscan.c
 *           backend/tamarack.c: Use sanei_config_read instead of fgets.
 *         * backend/dc210.c backend/microtek.c backend/pnm.c: Added
 *           #include <sane/config.h>.
 *         * backend/dc25.c backend/m3096.c  backend/sp15.c
 *            backend/st400.c: Moved #include <sane/config.h> to the beginning.
 *         * AUTHORS: Changed agfa to agfafocus.
 *
 * Revision 1.1.1.1.2.3  2000/07/17 21:37:28  hmg
 * 2000-07-17  Henning Meier-Geinitz <hmg@gmx.de>
 *
 *         * backend/snapscan.c backend/snapscan-scsi.c: Replace C++ comment
 *           with C comment.
 *
 * Revision 1.1.1.1.2.2  2000/07/13 04:47:46  pere
 * New snapscan backend version dated 20000514 from Steve Underwood.
 *
 * Revision 1.2  2000/05/14 13:30:20  coppice
 * R, G and B images now merge correctly. Still some outstanding issues,
 * but a lot more useful than before.
 *
 * Revision 1.2  2000/03/05 13:55:20  pere
 * Merged main branch with current DEVEL_1_9.
 *
 * Revision 1.1.1.1.2.1  1999/09/15 18:20:44  charter
 * Early version 1.0 snapscan.c
 *
 * Revision 2.2  1999/09/09 18:22:45  charter
 * Checkpoint. Now using Sources for scanner data, and have removed
 * references to the old snapscan-310.c stuff. This stuff must still
 * be incorporated into the RGBRouter to get trilinear CCD SnapScan
 * models working.
 *
 * Revision 2.1  1999/09/08 03:07:05  charter
 * Start of branch 2; same as 1.47.
 *
 * Revision 1.47  1999/09/08 03:03:53  charter
 * The actions for the scanner command options now use fprintf for
 * printing, rather than DGB. I want the output to come out no matter
 * what the value of the snapscan debug level.
 *
 * Revision 1.46  1999/09/07 20:53:41  charter
 * Changed expected_data_len to bytes_remaining.
 *
 * Revision 1.45  1999/09/06 23:32:37  charter
 * Split up sane_start() into sub-functions to improve readability (again).
 * Introduced actual_mode() and is_colour_mode() (again).
 * Fixed problems with cancellation. Works fine with my system now.
 *
 * Revision 1.44  1999/09/02 05:28:01  charter
 * Added Gary Plewa's name to the list of copyrighted contributors.
 *
 * Revision 1.43  1999/09/02 05:23:54  charter
 * Added Gary Plewa's patch for the Acer PRISA 620s.
 *
 * Revision 1.42  1999/09/02 02:05:34  charter
 * Check-in of revision 1.42 (release 0.7 of the backend).
 * This is part of the recovery from the great disk crash of Sept 1, 1999.
 *
 * Revision 1.42  1999/07/09 22:37:55  charter
 * Potential bugfix for problems with sane_get_parameters() and
 * the new generic scsi driver (suggested by Francois Desarmeni,
 * Douglas Gilbert, Abel Deuring).
 *
 * Revision 1.41  1999/07/09 20:58:07  charter
 * Changes to support SnapScan 1236s (Petter Reinholdsten).
 *
 * Revision 1.40  1998/12/16 18:43:06  charter
 * Fixed major version problem precipitated by release of SANE-1.00.
 *
 * Revision 1.39  1998/09/07  06:09:26  charter
 * Formatting (whitespace) changes.
 *
 * Revision 1.38  1998/09/07  06:06:01  charter
 * Merged in Wolfgang Goeller's changes (Vuego 310S, bugfixes).
 *
 * Revision 1.37  1998/08/06  06:16:39  charter
 * Now using sane_config_attach_matching_devices() in sane_snapscan_init().
 * Change contributed by David Mosberger-Tang.
 *
 * Revision 1.36  1998/05/11  17:02:53  charter
 * Added Mikko's threshold stuff.
 *
 * Revision 1.35  1998/03/10 23:43:23  eblot
 * Bug correction
 *
 * Revision 0.72  1998/03/10 23:40:42  eblot
 * More support for 310/600 models: color preview, large window
 *
 * Revision 1.35  1998/03/10  21:32:07  eblot
 * Debugging
 *
 * Revision 1.34  1998/02/15  21:55:53  charter
 * From Emmanuel Blot:
 * First routines to support SnapScan 310 scanned data.
 *
 * Revision 1.33  1998/02/06  02:30:28  charter
 * Now using a mode enum (instead of the static string pointers directly).
 * Now check for the SnapScan 310 and 600 explicitly (start of support
 * for these models).
 *
 * Revision 1.32  1998/02/01  21:56:48  charter
 * Patches to fix compilation problems on Solaris supplied by
 * Jim McBeath.
 *
 * Revision 1.31  1998/02/01  03:36:40  charter
 * Now check for BRX < TLX and BRY < TLY and whether the area of the
 * scanning window is approaching zero in set_window. I'm setting a
 * minimum window size of 75x75 hardware pixels (0.25 inches a side).
 * If the area falls to zero, the scanner seems to hang in the middle
 * of the set_window command.
 *
 * Revision 1.30  1998/02/01  00:00:33  charter
 * TLX, TLY, BRX and BRY are now lengths expressed in mm. The frontends
 * can now allow changes in the units, and units that are more user-
 * friendly.
 *
 * Revision 1.29  1998/01/31  21:09:19  charter
 * Fixed another problem with add_device(): if mini_inquiry ends
 * up indirectly invoking the sense handler, there'll be a segfault
 * because the sense_handler isn't set. Had to fix sense_handler so
 * it can handle a NULL pss pointer and then use the sanei_scsi stuff
 * everywhere. This error is most likely to occur if the scanner is
 * turned off.
 *
 * Revision 1.28  1998/01/31  18:45:22  charter
 * Last fix botched, produced a compile error. Thought I'd already
 * compiled successfully.
 *
 * Revision 1.27  1998/01/31  18:32:42  charter
 * Fixed stupid bug in add_device that causes segfault when no snapscan
 * found: closing a scsi fd opened with open() using sanei_scsi_close().
 *
 * Revision 1.26  1998/01/30  21:19:02  charter
 * sane_snapscan_init() handles failure of add_device() in the same
 * way when there is no snapscan.conf file available as when there is
 * one.
 *
 * Revision 1.25  1998/01/30  19:41:11  charter
 * Waiting for child process termination at regular end of scan (not
 * just on cancellation); before I was getting zombies.
 *
 * Revision 1.24  1998/01/30  19:19:27  charter
 * Changed from strncmp() to strncasecmp() to do vendor and model
 * comparisons in sane_snapscan_init. There are some snapcsan models
 * that use lower case.
 * Now have debug level defines instead of raw numbers, and better debug
 * information categories.
 * Don't complain at debug level 0 when a snapscan isn't found on a
 * requested device.
 * Changed CHECK_STATUS to take caller parameter instead of always
 * assuming an available string "me".
 *
 * Revision 1.23  1998/01/30  11:03:04  charter
 * Fixed * vs [] operator precedence screwup in sane_snapscan_get_devices()
 * that caused a segfault in scanimage -h.
 * Fixed problem with not closing the scsi fd between certain commands
 * that caused scanimage to hang; now using open_scanner() and close_scanner().
 *
 * Revision 1.22  1998/01/28  09:02:55  charter
 * Fixed bug: zero allocation length in request sense command buffer
 * was preventing sense information from being received. The
 * backend now correctly waits for the scanner to warm up.
 * Now using the hardware configuration byte to check whether
 * both 8x8 and 16x16 halftoning should be made available.
 *
 * Revision 1.21  1998/01/25  09:57:57  charter
 * Added more SCSI command buttons (and a group for them).
 * Made the output of the Inquiry command a bit nicer.
 *
 * Revision 1.20  1998/01/25  08:53:14  charter
 * Have added bi-level colour mode, with halftones too.
 * Can now select preview mode (but it's an advanced option, since
 * you usually don't want to do it).
 *
 * Revision 1.19  1998/01/25  02:25:02  charter
 * Fixed bug: preview mode gives blank image at initial startup.
 * Fixed bug: lineart mode goes weird after a preview or gs image.
 * More changes to option relationships;
 * now using test_unit_ready and send_diagnostic in sane_snapscan_open().
 * Added negative option.
 *
 * Revision 1.18  1998/01/24  05:15:32  charter
 * Now have RGB gamma correction and dispersed-dot dither halftoning
 * for BW images. Cleaned up some spots in the code and have set up
 * option interactions a bit better (e.g. halftoning and GS gamma
 * correction made inactive in colour mode, etc). TL_[XY] and BR_[XY]
 * now change in ten-pixel increments (I had problems with screwed-up
 * scan lines when the dimensions were weird at low res... could be the
 * problem).
 *
 * Revision 1.17  1998/01/23  13:03:17  charter
 * Several changes, all aimed at getting scanning performance working
 * correctly, and the progress/cancel window functioning. Cleaned up
 * a few nasty things as well.
 *
 * Revision 1.16  1998/01/23  07:40:23  charter
 * Reindented using GNU convention at David Mosberger-Tang's request.
 * Also applied David's patch fixing problems on 64-bit architectures.
 * Now using scanner's reported speed to guage amount of data to request
 * in a read on the scsi fd---nonblocking mode operates better now.
 * Fixed stupid bug I introduced in preview mode data transfer.
 *
 * Revision 1.15  1998/01/22  06:18:57  charter
 * Raised the priority of a couple of DBG messages in reserve_unit()
 * and release_unit(), and got rid of some unecessary ones.
 *
 * Revision 1.14  1998/01/22  05:15:35  charter
 * Have replaced the bit depth option with a mode option; various
 * changes associated with that.
 * Also, I again close the STDERR_FILENO in the reader child and
 * dup the STDOUT file descriptor onto it. This prevents an "X io"
 * error when the child exits, while still allowing the use of
 * DBG.
 *
 * Revision 1.13  1998/01/21  20:41:22  charter
 * Added copyright info.
 * Also now seem to have cancellation working. This requires using a
 * new scanner state variable and checking in all the right places
 * in the reader child and the sane_snapscan_read function. I've
 * tested it using both blocking and nonblocking I/O and it seems
 * to work both ways.
 * I've also switched to GTK+-0.99.2 and sane-0.69, and the
 * mysterious problems with the preview window have disappeared.
 * Problems with scanimage doing weird things to options have also
 * gone away and the frontends seem more stable.
 *
 * Revision 1.12  1998/01/21  11:05:53  charter
 * Inoperative code largely #defined out; I had the preview window
 * working correctly by having the window coordinates properly
 * constrained, but now the preview window bombs with a floating-
 * point error each time... I'm not sure yet what happened.
 * I've also figured out that we need to use reserve_unit and
 * release_unit in order to cancel scans in progress. This works
 * under scanimage, but I can't seem to find a way to fit cancellation
 * into xscanimage properly.
 *
 * Revision 1.11  1998/01/20  22:42:08  charter
 * Applied Franck's patch from Dec 17; preview mode is now grayscale.
 *
 * Revision 1.10  1997/12/10  23:33:12  charter
 * Slight change to some floating-point computations in the brightness
 * and contrast stuff. The controls don't seem to do anything to the
 * scanner though (I think these aren't actually supported in the
 * SnapScan).
 *
 * Revision 1.9  1997/11/26  15:40:50  charter
 * Brightness and contrast added by Michel.
 *
 * Revision 1.8  1997/11/12  12:55:40  charter
 * No longer exec after forking to do nonblocking scanning; found how
 * to fix the problems with SIGPIPEs from before.
 * Now support a config file like the other scanner drivers, and
 * can check whether a given device is an AGFA SnapScan (mini_inquiry()).
 *
 * Revision 1.7  1997/11/10  05:52:08  charter
 * Now have the child reader process and pipe stuff working, and
 * nonblocking mode. For large scans the nonblocking mode actually
 * seems to cut down on cpu hogging (though there is still a hit).
 *
 * Revision 1.6  1997/11/03  07:45:54  charter
 * Added the predef_window stuff. I've tried it with 6x4, and it seems
 * to work; I think something gets inconsistent if a preview is
 * performed though.
 *
 * Revision 1.5  1997/11/03  03:15:27  charter
 * Global static variables have now become part of the scanner structure;
 * the inquiry command automatically retrieves window parameters into
 * scanner structure members. Things are a bit cleaned up.
 *
 * Revision 1.4  1997/11/02  23:35:28  charter
 * After much grief.... I can finally scan reliably. Now it's a matter
 * of getting the band arrangement sorted out.
 *
 * Revision 1.3  1997/10/30  07:36:37  charter
 * Fixed a stupid bug in the #defines for the inquiry command, pointed out
 * by Franck.
 *
 * Revision 1.2  1997/10/14  06:00:11  charter
 * Option manipulation and some basic SCSI commands done; the basics
 * for scanning are written but there are bugs. A full scan always hangs
 * the SCSI driver, and preview mode scans complete but it isn't clear
 * whether any meaningful data is received.
 *
 * Revision 1.1  1997/10/13  02:25:54  charter
 * Initial revision
 * */
