/* sane - Scanner Access Now Easy.

   Copyright (C) 2002 Frank Zago (sane at zago dot net)
   Copyright (C) 2002 Other SANE contributors
   
   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. 
*/

/* 
   $Id: sceptre.c,v 1.12 2005/07/07 11:55:42 fzago-guest Exp $
   Sceptre S1200 SCSI scanner (sometimes also called S120)
*/

/*--------------------------------------------------------------------------*/

#define BUILD 10		/* 2002-03-21 */
#define BACKEND_NAME sceptre
#define SCEPTRE_CONFIG_FILE "sceptre.conf"

/*--------------------------------------------------------------------------*/


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

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/lassert.h"

#include "sceptre.h"

/*--------------------------------------------------------------------------*/

static const SANE_String scan_mode_list[] = { LINEART_STR, HALFTONE_STR,
  GRAY_STR, COLOR_STR, NULL
};

static const SANE_Range gamma_range = {
  0,				/* minimum */
  255,				/* maximum */
  0				/* quantization */
};

static const SANE_Range threshold_range = {
  0,				/* minimum */
  255,				/* maximum */
  0				/* quantization */
};

static const SANE_Range halftone_range = {
  1,				/* minimum */
  4,				/* maximum */
  0				/* quantization */
};

/*--------------------------------------------------------------------------*/

#define NUM_OF_RES 15
/* Table of supported resolution and number of lines of color shifting. */
static const SANE_Word resolutions_list[NUM_OF_RES + 1] = {
  NUM_OF_RES, 10, 25, 30, 45, 75, 90, 150, 300, 450, 600, 750, 900, 1050,
  1125, 1200
};

static const SANE_Word color_shift_list[NUM_OF_RES + 1] = {
  NUM_OF_RES, 0, 0, 0, 0, 1, 1, 2, 4, 6, 8, 10, 12, 14, 15, 16
};

/*--------------------------------------------------------------------------*/

/* Define the supported scanners and their characteristics. */
static const struct scanners_supported scanners[] = {
  /*      { 6, "KINPO   ", "Vividscan S600  ", "KINPO",   "S600" }, */
  {6, "KINPO   ", "Vividscan S120  ", "Sceptre", "S1200"}
};

/*--------------------------------------------------------------------------*/

/* List of scanner attached. */
static Sceptre_Scanner *first_dev = NULL;
static int num_devices = 0;
static const SANE_Device **devlist = NULL;


/* Local functions. */

/* Display a buffer in the log. */
static void
hexdump (int level, const char *comment, unsigned char *p, int l)
{
  int i;
  char line[128];
  char *ptr;

  DBG (level, "%s\n", comment);
  ptr = line;
  for (i = 0; i < l; i++, p++)
    {
      if ((i % 16) == 0)
	{
	  if (ptr != line)
	    {
	      *ptr = '\0';
	      DBG (level, "%s\n", line);
	      ptr = line;
	    }
	  sprintf (ptr, "%3.3d:", i);
	  ptr += 4;
	}
      sprintf (ptr, " %2.2x", *p);
      ptr += 3;
    }
  *ptr = '\0';
  DBG (level, "%s\n", line);
}

/* Initialize a scanner entry. Return an allocated scanner with some
 * preset values. */
static Sceptre_Scanner *
sceptre_init (void)
{
  Sceptre_Scanner *dev;

  DBG (DBG_proc, "sceptre_init: enter\n");

  /* Allocate a new scanner entry. */
  dev = malloc (sizeof (Sceptre_Scanner));
  if (dev == NULL)
    {
      return NULL;
    }

  memset (dev, 0, sizeof (Sceptre_Scanner));

  /* Allocate the buffer used to transfer the SCSI data. */
  dev->buffer_size = 64 * 1024;
  dev->buffer = malloc (dev->buffer_size);
  if (dev->buffer == NULL)
    {
      free (dev);
      return NULL;
    }

  dev->sfd = -1;

  DBG (DBG_proc, "sceptre_init: exit\n");

  return (dev);
}

/* Closes an open scanner. */
static void
sceptre_close (Sceptre_Scanner * dev)
{
  DBG (DBG_proc, "sceptre_close: enter\n");

  if (dev->sfd != -1)
    {
      sanei_scsi_close (dev->sfd);
      dev->sfd = -1;
    }

  DBG (DBG_proc, "sceptre_close: exit\n");
}

/* Frees the memory used by a scanner. */
static void
sceptre_free (Sceptre_Scanner * dev)
{
  int i;

  DBG (DBG_proc, "sceptre_free: enter\n");

  if (dev == NULL)
    return;

  sceptre_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
  if (dev->image)
    {
      free (dev->image);
    }
  for (i = 1; i < OPT_NUM_OPTIONS; i++)
    {
      if (dev->opt[i].type == SANE_TYPE_STRING && dev->val[i].s)
	{
	  free (dev->val[i].s);
	}
    }

  free (dev);

  DBG (DBG_proc, "sceptre_free: exit\n");
}

/* Inquiry a device and returns TRUE if is supported. */
static int
sceptre_identify_scanner (Sceptre_Scanner * dev)
{
  CDB cdb;
  SANE_Status status;
  size_t size;
  int i;

  DBG (DBG_proc, "sceptre_identify_scanner: enter\n");

  size = 36;
  MKSCSI_INQUIRY (cdb, size);
  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			    NULL, 0, dev->buffer, &size);

  if (status)
    {
      DBG (DBG_error,
	   "sceptre_identify_scanner: inquiry failed with status %s\n",
	   sane_strstatus (status));
      return (SANE_FALSE);
    }

  if (size < 36)
    {
      DBG (DBG_error,
	   "sceptre_identify_scanner: not enough data to identify device\n");
      return (SANE_FALSE);
    }

  dev->scsi_type = dev->buffer[0] & 0x1f;
  memcpy (dev->scsi_vendor, dev->buffer + 0x08, 0x08);
  dev->scsi_vendor[0x08] = 0;
  memcpy (dev->scsi_product, dev->buffer + 0x10, 0x010);
  dev->scsi_product[0x10] = 0;
  memcpy (dev->scsi_version, dev->buffer + 0x20, 0x04);
  dev->scsi_version[0x04] = 0;

  DBG (DBG_info, "device is \"%s\" \"%s\" \"%s\"\n",
       dev->scsi_vendor, dev->scsi_product, dev->scsi_version);

  /* Lookup through the supported scanners table to find if this
   * backend supports that one. */
  for (i = 0; i < NELEMS (scanners); i++)
    {
      if (dev->scsi_type == scanners[i].scsi_type &&
	  strcmp (dev->scsi_vendor, scanners[i].scsi_vendor) == 0 &&
	  strcmp (dev->scsi_product, scanners[i].scsi_product) == 0)
	{

	  DBG (DBG_error, "sceptre_identify_scanner: scanner supported\n");

	  dev->scnum = i;

	  return (SANE_TRUE);
	}
    }

  DBG (DBG_proc, "sceptre_identify_scanner: exit\n");

  return (SANE_FALSE);
}

/* Return the number of bytes left to read. */
static SANE_Status
sceptre_get_status (Sceptre_Scanner * dev, size_t * data_left)
{
  size_t size;
  CDB cdb;
  SANE_Status status;

  DBG (DBG_proc, "sceptre_get_status: enter\n");

  /* Get status. */
  size = 0x10;
  MKSCSI_GET_DATA_BUFFER_STATUS (cdb, 1, size);
  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			    NULL, 0, dev->buffer, &size);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sceptre_get_status: cannot get buffer status\n");
      *data_left = 0;
      return (SANE_STATUS_IO_ERROR);
    }

  if (size != 16)
    {
      DBG (DBG_error,
	   "sceptre_get_status: invalid data size returned (%ld)\n",
	   (long) size);
      return (SANE_STATUS_IO_ERROR);
    }

  hexdump (DBG_info2, "GET BUFFER STATUS result", dev->buffer, 16);

  /* Read the size left. The scanner returns the rest of the
   * bytes to read, not just what's in its buffers. */
  *data_left = B32TOI (&dev->buffer[8]);

  if (dev->raster_real == 0)
    {
      /* First call. Set the correct parameters. */
      dev->raster_real = B16TOI (&dev->buffer[12]) * 3;
      dev->params.lines = B16TOI (&dev->buffer[12]);
      dev->params.pixels_per_line = B16TOI (&dev->buffer[14]);
    }

  DBG (DBG_proc, "sceptre_get_status: exit, data_left=%ld\n",
       (long) *data_left);

  return (SANE_STATUS_GOOD);
}

/* 
 * Adjust the rasters. This function is used during a color scan,
 * because the scanner does not present a format sane can interpret
 * directly.
 *
 * The scanner sends the colors by rasters (R then G then B), whereas
 * sane is waiting for a group of 3 bytes per color. To make things
 * funnier, the rasters are shifted. This shift factor depends on the
 * resolution used. The format of those raster is: 
 *   R...R RG...RG RGB...RGB BG...GB B...B
 * 
 * So this function reorders all that mess. It gets the input from
 * dev->buffer and write the output in dev->image. size_in the the
 * length of the valid data in dev->buffer.
 */
static void
sceptre_adjust_raster (Sceptre_Scanner * dev, size_t size_in)
{
  int nb_rasters;		/* number of rasters in dev->buffer */

  int raster;			/* current raster number in buffer */
  int line;			/* line number for that raster */
  int colour;			/* colour for that raster */
  size_t offset;

  DBG (DBG_proc, "sceptre_adjust_raster: enter\n");

  assert (dev->scan_mode == SCEPTRE_COLOR);
  assert ((size_in % dev->params.bytes_per_line) == 0);

  if (size_in == 0)
    {
      return;
    }

  /* 
   * The color coding is one line for each color (in the RGB order).
   * Recombine that stuff to create a RGB value for each pixel.
   */

  nb_rasters = size_in / dev->raster_size;

  for (raster = 0; raster < nb_rasters; raster++)
    {

      /* 
       * Find the color to which this raster belongs to.
       *   0 = red
       *   1 = green
       *   2 = blue
       *
       * When blue comes, it always finishes the current line;
       */
      line = 0;
      if (dev->raster_num < dev->color_shift)
	{
	  colour = 0;		/* Red */
	  line = dev->raster_num;
	}
      else if (dev->raster_num < (3 * dev->color_shift))
	{
	  /* even = red, odd = green */
	  colour = (dev->raster_num - dev->color_shift) % 2;
	  if (colour)
	    {
	      /* Green */
	      line = (dev->raster_num - dev->color_shift) / 2;
	    }
	  else
	    {
	      /* Red */
	      line = (dev->raster_num + dev->color_shift) / 2;
	    }
	}
      else if (dev->raster_num >= dev->raster_real - dev->color_shift)
	{
	  /* Blue */
	  colour = 2;
	  line = dev->line;
	}
      else if (dev->raster_num >= dev->raster_real - 3 * dev->color_shift)
	{
	  /* Green or Blue */
	  colour =
	    (dev->raster_real - dev->raster_num - dev->color_shift) % 2 + 1;
	  if (colour == 1)
	    {
	      /* Green */
	      line = dev->line + dev->color_shift;
	    }
	  else
	    {
	      /* Blue */
	      line = dev->line;
	    }
	}
      else
	{
	  colour = (dev->raster_num - 3 * dev->color_shift) % 3;
	  switch (colour)
	    {
	    case 0:
	      /* Red */
	      line = (dev->raster_num + 3 * dev->color_shift) / 3;
	      break;
	    case 1:
	      /* Green */
	      line = dev->raster_num / 3;
	      break;
	    case 2:
	      /* Blue */
	      line = (dev->raster_num - 3 * dev->color_shift) / 3;
	      break;
	    }
	}

      /* Adjust the line number relative to the image. */
      line -= dev->line;

      offset = dev->image_end + line * dev->params.bytes_per_line;

      assert (offset <= (dev->image_size - dev->raster_size));

      /* Copy the raster to the temporary image. */
      {
	int i;
	unsigned char *src = dev->buffer + raster * dev->raster_size;
	unsigned char *dest = dev->image + offset + colour;

	for (i = 0; i < dev->raster_size; i++)
	  {
	    *dest = *src;
	    src++;
	    dest += 3;
	  }
      }

      if (colour == 2)
	{
	  /* This blue raster completes a new line */
	  dev->line++;
	  dev->image_end += dev->params.bytes_per_line;
	}

      dev->raster_num++;
    }

  DBG (DBG_proc, "sceptre_adjust_raster: exit\n");
}

/* SCSI sense handler. Callback for SANE. 
 *
 * Since this scanner does not have REQUEST SENSE, it is always an
 * error if this function is called.*/
static SANE_Status
sceptre_sense_handler (int scsi_fd, unsigned char __unused__ *result, void __unused__ *arg)
{
  DBG (DBG_proc, "sceptre_sense_handler (scsi_fd = %d)\n", scsi_fd);

  return SANE_STATUS_IO_ERROR;
}

/* Attach a scanner to this backend. */
static SANE_Status
attach_scanner (const char *devicename, Sceptre_Scanner ** devp)
{
  Sceptre_Scanner *dev;
  int sfd;

  DBG (DBG_sane_proc, "attach_scanner: %s\n", devicename);

  if (devp)
    *devp = NULL;

  /* Check if we know this device name. */
  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devicename) == 0)
	{
	  if (devp)
	    {
	      *devp = dev;
	    }
	  DBG (DBG_info, "device is already known\n");
	  return SANE_STATUS_GOOD;
	}
    }

  /* Allocate a new scanner entry. */
  dev = sceptre_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

  DBG (DBG_info, "attach_scanner: opening %s\n", devicename);

  if (sanei_scsi_open (devicename, &sfd, sceptre_sense_handler, dev) != 0)
    {
      DBG (DBG_error, "ERROR: attach_scanner: open failed\n");
      sceptre_free (dev);
      return SANE_STATUS_INVAL;
    }

  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->sfd = sfd;

  /* Now, check that it is a scanner we support. */
  if (sceptre_identify_scanner (dev) == SANE_FALSE)
    {
      DBG (DBG_error,
	   "ERROR: attach_scanner: scanner-identification failed\n");
      sceptre_free (dev);
      return SANE_STATUS_INVAL;
    }

  sceptre_close (dev);

  /* Set the default options for that scanner. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = scanners[dev->scnum].real_vendor;
  dev->sane.model = scanners[dev->scnum].real_product;
  dev->sane.type = SANE_I18N ("flatbed scanner");

  dev->resolution_range.min = SANE_FIX (50);
  dev->resolution_range.max = SANE_FIX (1200);
  dev->resolution_range.quant = SANE_FIX (1);

  /* 
   * The S1200 has an area of 8.5 inches / 11.7 inches. (A4 like)
   * That's roughly 215*297 mm
   * The values are coded by
   *    size in inch * 600 dpi.
   * The maximums are:
   *   X:  8.5 inches * 600 = 5100 dots
   *   Y: 11.7 inches * 600 = 7020 
   *                (although the windows driver stops at 7019)
   *
   * The values are stored in mm. Inches sucks anyway.
   *   X: 5078 dots (22 dots lost)
   *   Y: 7015 dots (5 dots lost)
   *
   * There seems to be a minimum area, but yet to be determined.
   */
  dev->x_range.min = SANE_FIX (0);
  dev->x_range.max = SANE_FIX (215.90);	/* in mm */
  dev->x_range.quant = 0;

  dev->y_range.min = SANE_FIX (0);
  dev->y_range.max = SANE_FIX (297.14);	/* in mm */
  dev->y_range.quant = SANE_FIX (0);

  /* Link the scanner with the others. */
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    {
      *devp = dev;
    }

  num_devices++;

  DBG (DBG_proc, "attach_scanner: exit\n");

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one (const char *dev)
{
  attach_scanner (dev, NULL);
  return SANE_STATUS_GOOD;
}

/* Reset the options for that scanner. */
static void
sceptre_init_options (Sceptre_Scanner * dev)
{
  int i;

  DBG (DBG_proc, "sceptre_init_options: enter\n");

  /* Pre-initialize the options. */
  memset (dev->opt, 0, sizeof (dev->opt));
  memset (dev->val, 0, sizeof (dev->val));

  for (i = 0; i < OPT_NUM_OPTIONS; ++i)
    {
      dev->opt[i].size = sizeof (SANE_Word);
      dev->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }

  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  dev->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  dev->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  dev->val[OPT_NUM_OPTS].w = OPT_NUM_OPTIONS;

  /* Mode group */
  dev->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  dev->opt[OPT_MODE_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_MODE_GROUP].cap = 0;
  dev->opt[OPT_MODE_GROUP].size = 0;
  dev->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Scanner supported modes */
  dev->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  dev->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  dev->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  dev->opt[OPT_MODE].type = SANE_TYPE_STRING;
  dev->opt[OPT_MODE].size = 30;	/* should define yet another max_string_size() */
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list =
    (SANE_String_Const *) scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup (scan_mode_list[0]);

  /* Common resolution */
  dev->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  dev->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  dev->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  dev->opt[OPT_RESOLUTION].constraint.word_list = resolutions_list;
  dev->val[OPT_RESOLUTION].w = 150;

  /* Geometry group */
  dev->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
  dev->opt[OPT_GEOMETRY_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
  dev->opt[OPT_GEOMETRY_GROUP].size = 0;
  dev->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Upper left X */
  dev->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  dev->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  dev->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  dev->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
  dev->opt[OPT_TL_X].unit = SANE_UNIT_MM;
  dev->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_TL_X].constraint.range = &(dev->x_range);
  dev->val[OPT_TL_X].w = dev->x_range.min;

  /* Upper left Y */
  dev->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  dev->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
  dev->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
  dev->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_TL_Y].constraint.range = &(dev->y_range);
  dev->val[OPT_TL_Y].w = dev->y_range.min;

  /* bottom-right x */
  dev->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
  dev->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  dev->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
  dev->opt[OPT_BR_X].type = SANE_TYPE_FIXED;
  dev->opt[OPT_BR_X].unit = SANE_UNIT_MM;
  dev->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BR_X].constraint.range = &(dev->x_range);
  dev->val[OPT_BR_X].w = dev->x_range.max;

  /* bottom-right y */
  dev->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
  dev->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
  dev->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
  dev->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BR_Y].constraint.range = &(dev->y_range);
  dev->val[OPT_BR_Y].w = dev->y_range.max;

  /* Enhancement group */
  dev->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  dev->opt[OPT_ENHANCEMENT_GROUP].desc = "";	/* not valid for a group */
  dev->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  dev->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* custom-gamma table */
  dev->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA;
  dev->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL;
  dev->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
  dev->val[OPT_CUSTOM_GAMMA].w = SANE_FALSE;

  /* red gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
  dev->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_R].size = GAMMA_LENGTH * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_R].constraint.range = &gamma_range;
  dev->val[OPT_GAMMA_VECTOR_R].wa = dev->gamma_R;

  /* green gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
  dev->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_G].size = GAMMA_LENGTH * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_G].constraint.range = &gamma_range;
  dev->val[OPT_GAMMA_VECTOR_G].wa = dev->gamma_G;

  /* blue gamma vector */
  dev->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
  dev->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
  dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_GAMMA_VECTOR_B].size = GAMMA_LENGTH * sizeof (SANE_Word);
  dev->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_GAMMA_VECTOR_B].constraint.range = &gamma_range;
  dev->val[OPT_GAMMA_VECTOR_B].wa = dev->gamma_B;

  /* Threshold */
  dev->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
  dev->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  dev->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
  dev->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
  dev->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
  dev->opt[OPT_THRESHOLD].size = sizeof (SANE_Int);
  dev->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_THRESHOLD].constraint.range = &threshold_range;
  dev->val[OPT_THRESHOLD].w = 128;

  /* Halftone pattern */
  dev->opt[OPT_HALFTONE_PATTERN].name = SANE_NAME_HALFTONE_PATTERN;
  dev->opt[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN;
  dev->opt[OPT_HALFTONE_PATTERN].desc = SANE_DESC_HALFTONE_PATTERN;
  dev->opt[OPT_HALFTONE_PATTERN].type = SANE_TYPE_INT;
  dev->opt[OPT_HALFTONE_PATTERN].size = sizeof (SANE_Int);
  dev->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_HALFTONE_PATTERN].constraint.range = &halftone_range;
  dev->val[OPT_HALFTONE_PATTERN].w = 1;

  /* preview */
  dev->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
  dev->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  dev->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
  dev->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
  dev->opt[OPT_PREVIEW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  dev->val[OPT_PREVIEW].w = SANE_FALSE;

  /* Lastly, set the default mode. This might change some values
   * previously set here. */
  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String *) COLOR_STR, NULL);

  DBG (DBG_proc, "sceptre_init_options: leave\n");
}

/* Wait until the scanner is ready.
 *
 * The only reason I know the scanner is not ready is because it is
 * moving the CCD. 
 */
static SANE_Status
sceptre_wait_scanner (Sceptre_Scanner * dev)
{
  SANE_Status status;
  int timeout;
  CDB cdb;
  size_t size;

  DBG (DBG_proc, "sceptre_wait_scanner: enter\n");

  MKSCSI_TEST_UNIT_READY (cdb);
  cdb.data[4] = 1;		/* returns one byte. Non standard SCSI. */

  /* Set the timeout to 120 seconds. */
  timeout = 120;

  while (timeout > 0)
    {

      /* test unit ready */
      size = 1;			/* read one info byte */
      status =
	sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			 NULL, 0, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD || size != 1)
	{
	  DBG (DBG_error, "sceptre_wait_scanner: TUR failed\n");
	  return (SANE_STATUS_IO_ERROR);
	}

      /* Apparently the scanner returns only 2 values: 
       *   0x00 - ready
       *   0xff - not ready
       */
      if (dev->buffer[0] != 0x00)
	{
	  sleep (1);		/* wait 1 seconds */
	  timeout--;
	}
      else
	{
	  return (SANE_STATUS_GOOD);
	}
    };

  DBG (DBG_proc, "sceptre_wait_scanner: scanner not ready\n");
  return (SANE_STATUS_IO_ERROR);
}

/* Diagnostic the scanner. */
static SANE_Status
sceptre_do_diag (Sceptre_Scanner * dev)
{
  SANE_Status status;
  CDB cdb;
  size_t size;

  DBG (DBG_proc, "sceptre_receive_diag enter\n");

  /* SEND DIAGNOSTIC. */
  MKSCSI_SEND_DIAG (cdb, 0);

  /* The windows driver sets that field. This is non standard. */
  cdb.data[2] = 0x80;

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len, NULL, 0, NULL, NULL);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sceptre_do_diag: exit, status=%d\n", status);
      return (status);
    }

  /* RECEIVE DIAGNOSTIC */

  /* The windows driver ask for 3 byte. This is non standard
   * SCSI. The page returned should be at least 4 bytes. */
  size = 3;
  MKSCSI_RECEIVE_DIAG (cdb, 0, size);

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			    NULL, 0, dev->buffer, &size);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sceptre_do_diag: exit, status=%d\n", status);
      return (status);
    }

  DBG (DBG_proc, "sceptre_receive_diag exit\n");

  return (status);
}

/* I'm not sure if the command sent is really set mode. The SCSI
 * command used is MODE SELECT, but no data is sent. Again, this is
 * not standard. */
static SANE_Status
sceptre_set_mode (Sceptre_Scanner * dev)
{
  SANE_Status status;
  CDB cdb;
  size_t size;

  DBG (DBG_proc, "sceptre_set_mode: enter\n");

  size = 0x18;
  MKSCSI_MODE_SELECT (cdb, 1, 0, size);

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len, NULL, 0, NULL, NULL);

  DBG (DBG_proc, "sceptre_set_mode: exit, status=%d\n", status);

  return (status);
}

/* Start a scan. */
static SANE_Status
sceptre_scan (Sceptre_Scanner * dev)
{
  CDB cdb;
  SANE_Status status;

  DBG (DBG_proc, "sceptre_scan: enter\n");

  MKSCSI_SCAN (cdb);

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len, NULL, 0, NULL, NULL);

  DBG (DBG_proc, "sceptre_scan: exit, status=%d\n", status);

  return status;
}

/* Set a window. */
static SANE_Status
sceptre_set_window (Sceptre_Scanner * dev)
{
  size_t size;
  CDB cdb;
  unsigned char window[82];
  SANE_Status status;

  DBG (DBG_proc, "sceptre_set_window: enter\n");

  size = sizeof (window);
  MKSCSI_SET_WINDOW (cdb, size);

  memset (window, 0, size);

  /* size of the parameters (74 = 0x4a bytes) */
  window[7] = sizeof (window) - 8;

  /* X and Y resolution */
  Ito16 (dev->resolution, &window[10]);
  Ito16 (dev->resolution, &window[12]);

  /* Upper Left (X,Y) */
  Ito32 (dev->x_tl, &window[14]);
  Ito32 (dev->y_tl, &window[18]);

  /* Width and length */
  Ito32 (dev->width, &window[22]);
  Ito32 (dev->length, &window[26]);

  /* Image Composition, Halftone and Depth */
  switch (dev->scan_mode)
    {
    case SCEPTRE_LINEART:
      window[31] = dev->val[OPT_THRESHOLD].w;
      window[33] = 0;
      window[34] = 1;
      window[36] = 0;
      break;
    case SCEPTRE_HALFTONE:
      window[31] = 0x80;
      window[33] = 0;
      window[34] = 1;
      window[36] = dev->val[OPT_HALFTONE_PATTERN].w;
      break;
    case SCEPTRE_GRAYSCALE:
      window[31] = 0x80;
      window[33] = 2;
      window[34] = 8;
      window[36] = 0;
      break;
    case SCEPTRE_COLOR:
      window[31] = 0x80;
      window[33] = 5;
      window[34] = 24;
      window[36] = 0;
      break;
    }

  /* Unknown parameters. They look constant in the windows driver. */
  window[30] = 0x04;
  window[32] = 0x04;
  window[37] = 0x80;		/* RIF, although it looks unused. */

  hexdump (DBG_info2, "windows", window, sizeof (window));

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			    window, sizeof (window), NULL, NULL);

  DBG (DBG_proc, "sceptre_set_window: exit, status=%d\n", status);

  return status;
}

/* Read the image from the scanner and fill the temporary buffer with it. */
static SANE_Status
sceptre_fill_image (Sceptre_Scanner * dev)
{
  SANE_Status status;
  size_t size;
  CDB cdb;
  size_t data_left;

  DBG (DBG_proc, "sceptre_fill_image: enter\n");

  assert (dev->image_begin == dev->image_end);
  assert (dev->real_bytes_left > 0);

  /* Copy the complete lines, plus the imcompletes
   * ones. We don't keep the real end of data used
   * in image, so we copy the biggest possible. 
   *
   * This is a no-op for non color images.
   */
  memmove (dev->image, dev->image + dev->image_begin, dev->raster_ahead);
  dev->image_begin = 0;
  dev->image_end = 0;

  while (dev->real_bytes_left)
    {

      if ((status = sceptre_get_status (dev, &data_left)) != SANE_STATUS_GOOD)
	{
	  return (status);
	}

      /* 
       * Try to read the maximum number of bytes.
       */
      size = data_left;
      if (size > dev->real_bytes_left)
	{
	  size = dev->real_bytes_left;
	}
      if (size > dev->image_size - dev->raster_ahead - dev->image_end)
	{
	  size = dev->image_size - dev->raster_ahead - dev->image_end;
	}
      if (size > dev->buffer_size)
	{
	  size = dev->buffer_size;
	}

      /* Round down to a multiple of line size. */
      size = size - (size % dev->params.bytes_per_line);

      if (size == 0)
	{
	  /* Probably reached the end of the buffer. 
	   * Check, just in case. */
	  assert (dev->image_end != 0);
	  return (SANE_STATUS_GOOD);
	}

      DBG (DBG_info, "sceptre_fill_image: to read   = %ld bytes (bpl=%d)\n",
	   (long) size, dev->params.bytes_per_line);

      MKSCSI_READ_10 (cdb, 0, 0, size);

      hexdump (DBG_info2, "sceptre_fill_image: READ_10 CDB", cdb.data, 10);

      status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
				NULL, 0, dev->buffer, &size);

      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "sceptre_fill_image: cannot read from the scanner\n");
	  return status;
	}

      DBG (DBG_info, "sceptre_fill_image: real bytes left = %ld\n",
	   (long)dev->real_bytes_left);

      switch (dev->scan_mode)
	{
	case SCEPTRE_COLOR:
	  sceptre_adjust_raster (dev, size);
	  break;
	case SCEPTRE_LINEART:
	case SCEPTRE_HALFTONE:
	  {
	    /* Invert black and white. */
	    unsigned char *src = dev->buffer;
	    unsigned char *dest = dev->image + dev->image_end;
	    size_t i;
	    for (i = 0; i < size; i++)
	      {
		*dest = *src ^ 0xff;
		dest++;
		src++;
	      }
	    dev->image_end += size;
	  }
	  break;
	default:
	  memcpy (dev->image + dev->image_end, dev->buffer, size);
	  dev->image_end += size;
	}

      dev->real_bytes_left -= size;
    }

  return (SANE_STATUS_GOOD);	/* unreachable */
}

/* Copy from the raw buffer to the buffer given by the backend. 
 *
 * len in input is the maximum length available in buf, and, in
 * output, is the length written into buf.
 */
static void
sceptre_copy_raw_to_frontend (Sceptre_Scanner * dev, SANE_Byte * buf,
			      size_t * len)
{
  size_t size;

  size = dev->image_end - dev->image_begin;
  if (size > *len)
    {
      size = *len;
    }
  *len = size;

  memcpy (buf, dev->image + dev->image_begin, size);

  dev->image_begin += size;
}

/* Stop a scan. */
static SANE_Status
do_cancel (Sceptre_Scanner * dev)
{
  DBG (DBG_sane_proc, "do_cancel enter\n");

  if (dev->scanning == SANE_TRUE)
    {

      /* Reposition the CCD. */
      dev->x_tl = 0;
      dev->x_tl = 0;
      dev->width = 0;
      dev->length = 0;
      sceptre_set_window (dev);
      sceptre_scan (dev);

      sceptre_close (dev);
    }

  dev->scanning = SANE_FALSE;

  DBG (DBG_sane_proc, "do_cancel exit\n");

  return SANE_STATUS_CANCELLED;
}

/* Start a scan. */
static const SANE_Word gamma_init[GAMMA_LENGTH] = {
  0x00, 0x06, 0x0A, 0x0D, 0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F,
  0x21, 0x23, 0x25, 0x27,
  0x28, 0x2A, 0x2C, 0x2D, 0x2F, 0x30, 0x32, 0x33, 0x35, 0x36, 0x38, 0x39,
  0x3A, 0x3C, 0x3D, 0x3F,
  0x40, 0x41, 0x43, 0x44, 0x45, 0x46, 0x48, 0x49, 0x4A, 0x4B, 0x4D, 0x4E,
  0x4F, 0x50, 0x51, 0x53,
  0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60,
  0x61, 0x62, 0x63, 0x64,
  0x65, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
  0x72, 0x73, 0x74, 0x75,
  0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7D, 0x7E, 0x7F, 0x80,
  0x81, 0x82, 0x83, 0x84,
  0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
  0x90, 0x91, 0x92, 0x92,
  0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
  0x9E, 0x9F, 0x9F, 0xA0,
  0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xA9, 0xAA,
  0xAB, 0xAC, 0xAD, 0xAD,
  0xAE, 0xAF, 0xB0, 0xB1, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7,
  0xB8, 0xB9, 0xB9, 0xBA,
  0xBB, 0xBC, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC0, 0xC1, 0xC2, 0xC3, 0xC3,
  0xC4, 0xC5, 0xC6, 0xC6,
  0xC7, 0xC8, 0xC9, 0xC9, 0xCA, 0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCF, 0xCF,
  0xD0, 0xD1, 0xD2, 0xD2,
  0xD3, 0xD4, 0xD5, 0xD5, 0xD6, 0xD7, 0xD7, 0xD8, 0xD9, 0xDA, 0xDA, 0xDB,
  0xDC, 0xDC, 0xDD, 0xDE,
  0xDF, 0xDF, 0xE0, 0xE1, 0xE1, 0xE2, 0xE3, 0xE4, 0xE4, 0xE5, 0xE6, 0xE6,
  0xE7, 0xE8, 0xE8, 0xE9,
  0xEA, 0xEB, 0xEB, 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xEF, 0xF0, 0xF1, 0xF1,
  0xF2, 0xF3, 0xF4, 0xF4,
  0xF5, 0xF6, 0xF6, 0xF7, 0xF8, 0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFC, 0xFC,
  0xFD, 0xFE, 0xFE, 0xFF
};

static SANE_Status
sceptre_send_gamma (Sceptre_Scanner * dev)
{
  CDB cdb;
  int i;
  struct
  {
    unsigned char gamma_R[GAMMA_LENGTH];
    unsigned char gamma_G[GAMMA_LENGTH];
    unsigned char gamma_B[GAMMA_LENGTH];
  }
  param;
  size_t size;
  SANE_Status status;

  DBG (DBG_proc, "sceptre_send_gamma: enter\n");

  size = sizeof (param);

  assert (size == 0x300);

  MKSCSI_SEND_10 (cdb, 0x03, 0x02, size);

  if (dev->val[OPT_CUSTOM_GAMMA].w)
    {
      /* Use the custom gamma. */
      for (i = 0; i < GAMMA_LENGTH; i++)
	{
	  param.gamma_R[i] = dev->gamma_R[i];
	  param.gamma_G[i] = dev->gamma_G[i];
	  param.gamma_B[i] = dev->gamma_B[i];
	}
    }
  else
    {
      for (i = 0; i < GAMMA_LENGTH; i++)
	{
	  param.gamma_R[i] = gamma_init[i];
	  param.gamma_G[i] = gamma_init[i];
	  param.gamma_B[i] = gamma_init[i];
	}
    }

  hexdump (DBG_info2, "gamma", param.gamma_R, 3 * GAMMA_LENGTH);

  status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
			    &param, sizeof (param), NULL, NULL);

  DBG (DBG_proc, "sceptre_send_gamma: exit, status=%d\n", status);

  return (status);
}

/*--------------------------------------------------------------------------*/

/* Entry points */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback __unused__ authorize)
{
  FILE *fp;
  char dev_name[PATH_MAX];
  size_t len;

  DBG_INIT ();

  DBG (DBG_proc, "sane_init: enter\n");

  DBG (DBG_error, "This is sane-sceptre version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2002 by Frank Zago\n");

  if (version_code)
    {
      *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, BUILD);
    }

  fp = sanei_config_open (SCEPTRE_CONFIG_FILE);
  if (!fp)
    {
      /* default to /dev/scanner instead of insisting on config file */
      attach_scanner ("/dev/scanner", 0);
      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (dev_name, sizeof (dev_name), fp))
    {
      if (dev_name[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (dev_name);

      if (!len)
	continue;		/* ignore empty lines */

      sanei_config_attach_matching_devices (dev_name, attach_one);
    }

  fclose (fp);

  DBG (DBG_proc, "sane_init: leave\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool __unused__ local_only)
{
  Sceptre_Scanner *dev;
  int i;

  DBG (DBG_proc, "sane_get_devices: enter\n");

  if (devlist)
    free (devlist);

  devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
  if (!devlist)
    return SANE_STATUS_NO_MEM;

  i = 0;
  for (dev = first_dev; i < num_devices; dev = dev->next)
    devlist[i++] = &dev->sane;
  devlist[i++] = 0;

  *device_list = devlist;

  DBG (DBG_proc, "sane_get_devices: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Sceptre_Scanner *dev;
  SANE_Status status;

  DBG (DBG_proc, "sane_open: enter\n");

  /* search for devicename */
  if (devicename[0])
    {
      DBG (DBG_info, "sane_open: devicename=%s\n", devicename);

      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devicename) == 0)
	    {
	      break;
	    }
	}

      if (!dev)
	{
	  status = attach_scanner (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return status;
	    }
	}
    }
  else
    {
      DBG (DBG_sane_info, "sane_open: no devicename, opening first device\n");
      dev = first_dev;		/* empty devicename -> use first device */
    }

  if (!dev)
    {
      DBG (DBG_error, "No scanner found\n");

      return SANE_STATUS_INVAL;
    }

  sceptre_init_options (dev);

  /* Initialize the gamma table. */
  memcpy (dev->gamma_R, gamma_init, dev->opt[OPT_GAMMA_VECTOR_R].size);
  memcpy (dev->gamma_G, gamma_init, dev->opt[OPT_GAMMA_VECTOR_G].size);
  memcpy (dev->gamma_B, gamma_init, dev->opt[OPT_GAMMA_VECTOR_B].size);

  *handle = dev;

  DBG (DBG_proc, "sane_open: exit\n");

  return SANE_STATUS_GOOD;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Sceptre_Scanner *dev = handle;

  DBG (DBG_proc, "sane_get_option_descriptor: enter, option %d\n", option);

  if ((unsigned) option >= OPT_NUM_OPTIONS)
    {
      return NULL;
    }

  DBG (DBG_proc, "sane_get_option_descriptor: exit\n");

  return dev->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  Sceptre_Scanner *dev = handle;
  SANE_Status status;
  SANE_Word cap;

  DBG (DBG_proc, "sane_control_option: enter, option %d, action %d\n",
       option, action);

  if (info)
    {
      *info = 0;
    }

  if (dev->scanning)
    {
      return SANE_STATUS_DEVICE_BUSY;
    }

  if (option < 0 || option >= OPT_NUM_OPTIONS)
    {
      return SANE_STATUS_INVAL;
    }

  cap = dev->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {

      switch (option)
	{
	  /* word options */
	case OPT_NUM_OPTS:
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_THRESHOLD:
	case OPT_CUSTOM_GAMMA:
	case OPT_HALFTONE_PATTERN:
	case OPT_PREVIEW:

	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;

	  /* string options */
	case OPT_MODE:
	  strcpy (val, dev->val[option].s);
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (val, dev->val[option].wa, dev->opt[option].size);
	  return SANE_STATUS_GOOD;

	default:
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {

      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (DBG_error, "could not set option, not settable\n");
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (dev->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "could not set option, invalid value\n");
	  return status;
	}

      switch (option)
	{

	  /* Side-effect options */
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_RESOLUTION:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* Side-effect free options */
	case OPT_THRESHOLD:
	case OPT_HALFTONE_PATTERN:
	case OPT_PREVIEW:
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  free (dev->val[OPT_MODE].s);
	  dev->val[OPT_MODE].s = (SANE_Char *) strdup (val);

	  /* Set default options for the scan modes. */
	  dev->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;

	  if (strcmp (dev->val[OPT_MODE].s, LINEART_STR) == 0)
	    {
	      dev->scan_mode = SCEPTRE_LINEART;
	      dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, HALFTONE_STR) == 0)
	    {
	      dev->scan_mode = SCEPTRE_HALFTONE;
	      dev->opt[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, GRAY_STR) == 0)
	    {
	      dev->scan_mode = SCEPTRE_GRAYSCALE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, COLOR_STR) == 0)
	    {
	      dev->scan_mode = SCEPTRE_COLOR;
	      dev->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
	      if (dev->val[OPT_CUSTOM_GAMMA].w)
		{
		  dev->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
		  dev->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
		}
	    }

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (dev->val[option].wa, val, dev->opt[option].size);
	  return SANE_STATUS_GOOD;

	case OPT_CUSTOM_GAMMA:
	  dev->val[OPT_CUSTOM_GAMMA].w = *(SANE_Word *) val;
	  if (dev->val[OPT_CUSTOM_GAMMA].w)
	    {
	      /* use custom_gamma_table */
	      dev->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      dev->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	      dev->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	    }
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS;
	    }
	  return SANE_STATUS_GOOD;

	default:
	  return SANE_STATUS_INVAL;
	}
    }

  DBG (DBG_proc, "sane_control_option: exit, bad\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Sceptre_Scanner *dev = handle;
  int x_dpi;			/* X-Resolution */

  DBG (DBG_proc, "sane_get_parameters: enter\n");

  if (!(dev->scanning))
    {
      /* Prepare the parameters for the caller. */
      memset (&dev->params, 0, sizeof (SANE_Parameters));

      if (dev->val[OPT_PREVIEW].w == SANE_TRUE)
	{
	  dev->resolution = 30;	/* Windows TWAIN does 32 */
	  dev->x_tl = 0;
	  dev->y_tl = 0;
	  dev->x_br = mmToIlu (SANE_UNFIX (dev->x_range.max));
	  dev->y_br = mmToIlu (SANE_UNFIX (dev->y_range.max));
	}
      else
	{
	  /* Setup the parameters for the scan. These values will be re-used
	   * in the SET WINDOWS command. */
	  dev->resolution = dev->val[OPT_RESOLUTION].w;

	  dev->x_tl = mmToIlu (SANE_UNFIX (dev->val[OPT_TL_X].w));
	  dev->y_tl = mmToIlu (SANE_UNFIX (dev->val[OPT_TL_Y].w));
	  dev->x_br = mmToIlu (SANE_UNFIX (dev->val[OPT_BR_X].w));
	  dev->y_br = mmToIlu (SANE_UNFIX (dev->val[OPT_BR_Y].w));
	}

      /* Check the corners are OK. */
      if (dev->x_tl > dev->x_br)
	{
	  int s;
	  s = dev->x_tl;
	  dev->x_tl = dev->x_br;
	  dev->x_br = s;
	}
      if (dev->y_tl > dev->y_br)
	{
	  int s;
	  s = dev->y_tl;
	  dev->y_tl = dev->y_br;
	  dev->y_br = s;
	}

      dev->width = dev->x_br - dev->x_tl;
      dev->length = dev->y_br - dev->y_tl;

      /* 
       * Adjust the "X Resolution".  The sceptre S1200 ignores the
       * Y-Resolution parameter in the windows block. X-Resolution
       * is used instead. However the limits are not the same for X
       * (600 dpi) and Y (1200 dpi).  
       */
      x_dpi = dev->resolution;
      if (x_dpi > 600)
	{
	  x_dpi = 600;
	}

      /* Set depth */
      switch (dev->scan_mode)
	{
	case SCEPTRE_LINEART:
	  dev->params.format = SANE_FRAME_GRAY;
	  dev->depth = 1;
	  break;
	case SCEPTRE_HALFTONE:
	  dev->params.format = SANE_FRAME_GRAY;
	  dev->depth = 1;
	  break;
	case SCEPTRE_GRAYSCALE:
	  dev->params.format = SANE_FRAME_GRAY;
	  dev->depth = 8;
	  break;
	case SCEPTRE_COLOR:
	  dev->params.format = SANE_FRAME_RGB;
	  dev->depth = 8;
	  break;
	}

      /* this scanner does only one pass */
      dev->params.last_frame = SANE_TRUE;
      dev->params.depth = dev->depth;

      /* Compute the number of pixels, bytes per lines and lines. */
      switch (dev->scan_mode)
	{
	case SCEPTRE_LINEART:
	case SCEPTRE_HALFTONE:
	  dev->params.pixels_per_line = (dev->width * x_dpi) / 600;
	  dev->params.pixels_per_line &= ~0x7;	/* round down to 8 */

	  dev->params.bytes_per_line = (dev->params.pixels_per_line) / 8;

	  dev->params.lines = ((dev->length * dev->resolution) / 600);
	  if ((dev->params.lines) * 600 != (dev->length * dev->resolution))
	    {
	      /* Round up lines to 2. */
	      dev->params.lines &= ~1;
	      dev->params.lines += 2;
	    }

	  break;

	case SCEPTRE_GRAYSCALE:
	case SCEPTRE_COLOR:
	  /* pixels_per_line rounding rules:
	   *  2n + [0.0 .. 1.0]  -> round to 2n
	   *  2n + ]1.0 .. 2.0]  -> round to 2n + 2
	   */
	  dev->params.pixels_per_line = (dev->width * x_dpi) / 600;
	  if (dev->params.pixels_per_line & 1)
	    {
	      if ((dev->params.pixels_per_line * 600) == (dev->width * x_dpi))
		{
		  /* 2n */
		  dev->params.pixels_per_line--;
		}
	      else
		{
		  /* 2n+2 */
		  dev->params.pixels_per_line++;
		}
	    }

	  dev->params.bytes_per_line = dev->params.pixels_per_line;
	  if (dev->scan_mode == SCEPTRE_COLOR)
	    dev->params.bytes_per_line *= 3;

	  /* lines number rounding rules:
	   *   2n + [0.0 .. 2.0[  -> round to 2n
	   *
	   * Note: the rounding is often incorrect at high
	   * resolution (ag more than 300dpi) 
	   */
	  dev->params.lines = (dev->length * dev->resolution) / 600;
	  dev->params.lines &= ~1;

	  break;
	}

      /* Find the proper color shifting parameter. */
      if (dev->scan_mode == SCEPTRE_COLOR)
	{
	  int i = 1;
	  while (resolutions_list[i] != dev->resolution)
	    {
	      i++;
	    }
	  dev->color_shift = color_shift_list[i];
	}
      else
	{
	  dev->color_shift = 0;
	}

      DBG (DBG_proc, "color_shift = %d\n", dev->color_shift);

      dev->bytes_left = dev->params.lines * dev->params.bytes_per_line;
    }

  /* Return the current values. */
  if (params)
    {
      *params = (dev->params);
    }

  DBG (DBG_proc, "sane_get_parameters: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Sceptre_Scanner *dev = handle;
  SANE_Status status;

  DBG (DBG_proc, "sane_start: enter\n");

  if (!(dev->scanning))
    {

      sane_get_parameters (dev, NULL);

      if (dev->image)
	{
	  free (dev->image);
	}
      /* Compute the length necessary in image. The first part will store
       * the complete lines, and the rest is used to stored ahead
       * rasters.
       */
      dev->raster_ahead =
	(2 * dev->color_shift + 1) * dev->params.bytes_per_line;
      dev->image_size = dev->buffer_size + dev->raster_ahead;
      dev->image = malloc (dev->image_size);
      if (dev->image == NULL)
	{
	  return SANE_STATUS_NO_MEM;
	}
      dev->image_begin = 0;
      dev->image_end = 0;

      dev->raster_size = dev->params.bytes_per_line / 3;
      dev->raster_num = 0;
      dev->raster_real = 0;
      dev->line = 0;

      /* Open again the scanner. */
      if (sanei_scsi_open
	  (dev->devicename, &(dev->sfd), sceptre_sense_handler, dev) != 0)
	{
	  DBG (DBG_error, "ERROR: sane_start: open failed\n");
	  return SANE_STATUS_INVAL;
	}

      /* The scanner must be ready. */
      status = sceptre_wait_scanner (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_do_diag (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_set_mode (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_set_window (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_send_gamma (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_scan (dev);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

      status = sceptre_get_status (dev, &dev->real_bytes_left);
      if (status)
	{
	  sceptre_close (dev);
	  return status;
	}

    }

  dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;

  dev->scanning = SANE_TRUE;

  DBG (DBG_proc, "sane_start: exit\n");

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SANE_Status status;
  Sceptre_Scanner *dev = handle;
  size_t size;
  int buf_offset;		/* offset into buf */

  DBG (DBG_proc, "sane_read: enter\n");

  *len = 0;

  if (!(dev->scanning))
    {
      /* OOPS, not scanning */
      return do_cancel (dev);
    }

  if (dev->bytes_left <= 0)
    {
      return (SANE_STATUS_EOF);
    }

  buf_offset = 0;

  do
    {
      if (dev->image_begin == dev->image_end)
	{
	  /* Fill image */
	  status = sceptre_fill_image (dev);
	  if (status != SANE_STATUS_GOOD)
	    {
	      return (status);
	    }
	}

      /* Something must have been read */
      if (dev->image_begin == dev->image_end)
	{
	  DBG (DBG_info, "sane_read: nothing read\n");
	  return SANE_STATUS_IO_ERROR;
	}

      /* Copy the data to the frontend buffer. */
      size = max_len - buf_offset;
      if (size > dev->bytes_left)
	{
	  size = dev->bytes_left;
	}
      sceptre_copy_raw_to_frontend (dev, buf + buf_offset, &size);

      buf_offset += size;

      dev->bytes_left -= size;
      *len += size;

    }
  while ((buf_offset != max_len) && dev->bytes_left);

  DBG (DBG_info, "sane_read: leave, bytes_left=%ld\n", (long)dev->bytes_left);

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_set_io_mode (SANE_Handle __unused__ handle, SANE_Bool __unused__ non_blocking)
{
  SANE_Status status;
  Sceptre_Scanner *dev = handle;

  DBG (DBG_proc, "sane_set_io_mode: enter\n");

  if (dev->scanning == SANE_FALSE)
    {
      return (SANE_STATUS_INVAL);
    }

  if (non_blocking == SANE_FALSE)
    {
      status = SANE_STATUS_GOOD;
    }
  else
    {
      status = SANE_STATUS_UNSUPPORTED;
    }

  DBG (DBG_proc, "sane_set_io_mode: exit\n");

  return status;
}

SANE_Status
sane_get_select_fd (SANE_Handle __unused__ handle, SANE_Int __unused__ * fd)
{
  DBG (DBG_proc, "sane_get_select_fd: enter\n");

  DBG (DBG_proc, "sane_get_select_fd: exit\n");

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Sceptre_Scanner *dev = handle;

  DBG (DBG_proc, "sane_cancel: enter\n");

  do_cancel (dev);

  DBG (DBG_proc, "sane_cancel: exit\n");
}

void
sane_close (SANE_Handle handle)
{
  Sceptre_Scanner *dev = handle;
  Sceptre_Scanner *dev_tmp;

  DBG (DBG_proc, "sane_close: enter\n");

  do_cancel (dev);
  sceptre_close (dev);

  /* Unlink dev. */
  if (first_dev == dev)
    {
      first_dev = dev->next;
    }
  else
    {
      dev_tmp = first_dev;
      while (dev_tmp->next && dev_tmp->next != dev)
	{
	  dev_tmp = dev_tmp->next;
	}
      if (dev_tmp->next != NULL)
	{
	  dev_tmp->next = dev_tmp->next->next;
	}
    }

  sceptre_free (dev);
  num_devices--;

  DBG (DBG_proc, "sane_close: exit\n");
}

void
sane_exit (void)
{
  DBG (DBG_proc, "sane_exit: enter\n");

  while (first_dev)
    {
      sane_close (first_dev);
    }

  if (devlist)
    {
      free (devlist);
      devlist = NULL;
    }

  DBG (DBG_proc, "sane_exit: exit\n");
}
