/* sane - Scanner Access Now Easy.

   Copyright (C) 2004 Gerard Klaver  <gerard at gkall dot hobby dot nl>
   The teco2 and gl646 backend (Frank Zago) are used as a template for 
   this backend
   
   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: logitech1.c,v 1.01 2004/08/06 17:37:22 gekl-guest Exp $

   Logitech/Storm scanner backend Gerard Klaver
   start 06-08-2004
*/

/*SANE FLOW DIAGRAM

   - sane_init() : initialize backend, attach scanners
   . - sane_get_devices() : query list of scanner devices
   . - sane_open() : open a particular scanner device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get scanner fd
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . . (sane_read called multiple times; after sane_read returns EOF, 
   . . loop may continue with sane_start which may return a 2nd page
   . . when doing duplex scans, or load the next page from the ADF)
   . .
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened scanner device
   - sane_exit() : terminate use of backend
*/
/*--------------------------------------------------------------------------*/

#define BUILD 1			/* 2004/08/06  update 06-08-2004 */
#define BACKEND_NAME logitech1
#define LOGITECH1_CONFIG_FILE "logitech1.conf"

/* --------------------- SANE INTERNATIONALISATION ------------------------ */

/* must be first include */
#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_usb.h"
#include "../include/sane/sanei_debug.h"
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"
#include "../include/lassert.h"


#include "logitech1.h"

#define TIMEOUT 1000

/*--------------------------------------------------------------------------*/
/* Lists of possible scan modes. */
static SANE_String_Const scan_mode_list[] = {
  SANE_VALUE_SCAN_MODE_LINEART,
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,

  NULL
};

/* List of color dropout. */
static SANE_String_Const filter_color_list[] = {
  "Red",
  "Green",
  "Blue",
  NULL
};
static const int filter_color_val[] = {
  0,
  1,
  2
};

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

/* List of dithering options. */
static SANE_String_Const dither_list[] = {
  "Line art",
  "2x2",
  "3x3",
  "4x4 bayer",
  "4x4 smooth",
  "8x8 bayer",
  "8x8 smooth",
  "8x8 horizontal",
  "8x8 vertical",
  NULL
};
static const int dither_val[] = {
  0x00,
  0x01,
  0x02,
  0x03,
  0x04,
  0x05,
  0x06,
  0x07,
  0x08
};

/*-----------------------------------------minium, maximum, quantization----*/
static const SANE_Range brightness_range = { 0, 255, 1 };

static const SANE_Range contrast_range = { 0, 255, 1 };

static const SANE_Range threshold_range = { 0, 255, 0 };

static const SANE_Range black_level_range = { 0, 255, 1 };

static const SANE_Range red_level_range = { 0, 64, 1 };

static const SANE_Range green_level_range = { 0, 64, 1 };

static const SANE_Range blue_level_range = { 0, 64, 1 };

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

static const struct dpi_color_adjust logitech_dpi_color_adjust[] = {

  /*dpi, y, x, color sequence R G or B, 0 (-) or 1 (+) color skewing, lines skewing */
  {100, 100, 1, 0, 2, 1, 2},
  {150, 150, 1, 0, 2, 1, 2},
  {200, 200, 1, 0, 2, 1, 2},
  {300, 300, 1, 0, 2, 1, 2},
  /* must be the last entry */
  {0, 0, 0, 0, 0, 0, 0}
};

/* For all scanners. apear in the c and logitech_dpi_color_adjust list. */
#define DEF_RESOLUTION 100

static const struct scanner_hardware scanners[] = {

  {0x046d, 0x040f, USB_CLASS_VENDOR_SPEC,
   "LOGITECH", "PageScan USB",
   {1, 300, 1},			/* resolution */
   300, 300,			/* max x and Y resolution */
   2550, 12, 3, 0,		/* calibration */
   {SANE_FIX (0), SANE_FIX (8.5 * MM_PER_INCH), 0},
   {SANE_FIX (0), SANE_FIX (11.7 * MM_PER_INCH), 0},
   logitech_dpi_color_adjust}
};

/* List of scanners attached. */
static Logitech_Scanner *first_dev = NULL;
static int num_devices = 0;
/* used by sane_get_devices */
static const SANE_Device **devlist = NULL;

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

/* Local functions. */

/* Display a buffer in the log. Display by lines of 16 bytes. */
static void
hexdump (int level, const char *comment, unsigned char *buf, const int length)
{
  int i;
  char line[128];
  char *ptr;
  char asc_buf[17];
  char *asc_ptr;

  DBG (level, "  %s\n", comment);

  i = 0;
  goto start;

  do
    {
      if (i < length)
	{
	  ptr += sprintf (ptr, " %2.2x", *buf);

	  if (*buf >= 32 && *buf <= 127)
	    {
	      asc_ptr += sprintf (asc_ptr, "%c", *buf);
	    }
	  else
	    {
	      asc_ptr += sprintf (asc_ptr, ".");
	    }
	}
      else
	{
	  /* After the length; do nothing. */
	  ptr += sprintf (ptr, "   ");
	}

      i++;
      buf++;

      if ((i % 16) == 0)
	{
	  /* It's a new line */
	  DBG (level, "  %s    %s\n", line, asc_buf);

	start:
	  ptr = line;
	  *ptr = '\0';
	  asc_ptr = asc_buf;
	  *asc_ptr = '\0';

	  ptr += sprintf (ptr, "  %3.3d:", i);
	}

    }
  while (i < ((length + 15) & ~15));
}

/* Returns the length of the longest string, including the terminating
 * character. */
static size_t
max_string_size (SANE_String_Const strings[])
{
  size_t size, 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;
}

/* Lookup a string list from one array and return its index. */
static int
get_string_list_index (SANE_String_Const list[], SANE_String_Const name)
{
  int index;

  index = 0;
  while (list[index] != NULL)
    {
      if (strcmp (list[index], name) == 0)
	{
	  return (index);
	}
      index++;
    }

  DBG (DBG_error, "name %s not found in list\n", name);

  assert (0);			/* bug in backend, core dump */

  return (-1);
}

/* Initialize a scanner entry. Return an allocated scanner with some
 *  */
static Logitech_Scanner *
logitech_init (void)
{
  Logitech_Scanner *dev;

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

  /* Allocate a new scanner entry. */
  dev = calloc (1, sizeof (Logitech_Scanner));
  if (dev == NULL)
    {
      return NULL;
    }
  memset (dev, 0, sizeof (Logitech_Scanner));
  /* Allocate the buffer used to transfer the USB data */
  dev->buffer_size = 256 * 1024;
  dev->buffer = malloc (dev->buffer_size);
  if (dev->buffer == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windoww buffer*/
  dev->windoww_size = 0x40;
  dev->windoww = malloc (dev->windoww_size);
  if (dev->windoww == NULL)
    {
      free (dev);
      return NULL;
    }

/* Allocate the windowr buffer*/
  dev->windowr_size = 0x100;
  dev->windowr = malloc (dev->windowr_size);
  if (dev->windowr == NULL)
    {
      free (dev);
      return NULL;
    }
/* Allocate the  buffer11 */
  dev->size11 = 0x02;
  dev->buffer11 = malloc (dev->size11);
  if (dev->buffer11 == NULL)
    {
      free (dev);
      return NULL;
    }
/* Allocate the buffer15 */
  dev->size15 = 0x02;
  dev->buffer15 = malloc (dev->size15);
  if (dev->buffer15 == NULL)
    {
      free (dev);
      return NULL;
    }

  dev->fd = -1;

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

  return (dev);
}

/* Closes an open scanners. */
static void
logitech_close (Logitech_Scanner * dev)
{
  DBG (DBG_proc, "logitech_close: enter \n");

  if (dev->fd != -1)
    {
      sanei_usb_close (dev->fd);
      dev->fd = -1;
    }

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

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

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

  if (dev == NULL)
    return;

  logitech_close (dev);
  if (dev->devicename)
    {
      free (dev->devicename);
    }
  if (dev->buffer)
    {
      free (dev->buffer);
    }
  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);
	}
    }
  if (dev->resolutions_list)
    free (dev->resolutions_list);

  free (dev);

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

/* Set a window. */
static SANE_Status
logitech_set_window (Logitech_Scanner * dev)
{
  size_t size;			/* significant size of window */
  unsigned char window[56];
  SANE_Status status;
  int i;

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

  /* size of the whole windows block */
  size = 0;			/* keep gcc quiet */
  size = 56;

  /* Check that window is big enough */
  assert (size <= sizeof (window));

  memset (window, 0, size);

  /* size of the windows descriptor block */
  window[7] = size - 8;

  /* X and Y resolution */
  Ito16 (dev->x_resolution, &window[10]);
  Ito16 (dev->y_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 */
  switch (dev->scan_mode)
    {
    case LOGITECH_LINEART:
      window[31] = dev->val[OPT_THRESHOLD].w;
      window[33] = 0x00;
      i = get_string_list_index (dither_list, dev->val[OPT_DITHER].s);
      window[36] = dither_val[i];
      break;
    case LOGITECH_GRAY:
      window[33] = 0x02;
      break;
    case LOGITECH_COLOR:
      window[33] = 0x05;
      break;
    }

  /* Depth */
  window[34] = dev->depth;

  /* Lamp to use */
  i = get_string_list_index (filter_color_list, dev->val[OPT_FILTER_COLOR].s);
  window[48] = filter_color_val[i];

  /* Unknown - invariants */
  window[31] = 0x80;
  window[37] = 0x80;

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

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

  return status;
}

/* Reset scanner */
static SANE_Status
logitech_reset_window (Logitech_Scanner * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "logitech_reset_window: enter\n");
  status = SANE_STATUS_GOOD;
  DBG (DBG_proc, "logitech_reset_window: leave, status=%d\n", status);

  return status;
}

/* Read the size of the scan, for scanner ? */
static SANE_Status
logitech_get_scan_size (Logitech_Scanner * dev)
{
  SANE_Status status;

  DBG (DBG_proc, "logitech_get_scan_size: enter\n");
  /* hexdump (DBG_info2, "logitech_get_scan_size return", dev->buffer, size); */

  dev->params.lines = 150 * 11;
  dev->bytes_per_raster = 150 * 11 * 1;
  switch (dev->scan_mode)
    {
    case LOGITECH_LINEART:
      dev->params.bytes_per_line = (150 * 8.5) / 8;
      dev->params.pixels_per_line = 150 * 8.5;
      break;
    case LOGITECH_GRAY:
      dev->params.pixels_per_line = 75 * 17 * 1;
      dev->params.bytes_per_line = dev->params.bytes_per_line;
      break;
    case LOGITECH_COLOR:
      dev->params.pixels_per_line = 75 * 17 * 1;
      dev->params.bytes_per_line = dev->params.pixels_per_line * 3;
      break;
    }

  DBG (DBG_proc, "logitech_get_scan_size: , param.bytes_per_line =0x%x\n",
       dev->params.bytes_per_line);

  status = SANE_STATUS_GOOD;	/* no correct test yet */

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

  return (status);
}

/* Wait until the scanner is ready to send the data. This is the
   almost the same code as logitech_get_scan_size(). This function has to
   be called once after the scan has started. */
static SANE_Status
logitech_wait_for_data (Logitech_Scanner * dev)
{
  SANE_Status status;
  int i;

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

  for (i = 0; i < 60; i++)
    {
      status = SANE_STATUS_GOOD;

      if (i = 59)
	{
	  return (SANE_STATUS_GOOD);
	}

      sleep (1);
    }

  return SANE_STATUS_GOOD;
  DBG (DBG_proc,
       "logitech_wait_for_data: scanner not ready to send data (%d)\n",
       status);

  return SANE_STATUS_DEVICE_BUSY;
}

/* Start a scan. */
static SANE_Status
logitech_scan (Logitech_Scanner * dev)
{
  SANE_Status status;

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

/* no command yet placed   */

  status = SANE_STATUS_GOOD;

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

  return status;
}

/* Do the calibration stuff. Get 12 lines of data. Each pixel is coded
 * in 3 bytes. To do the calibration, allocates an array
 * big enough for one line, read the 12 lines of calibration, and do a
 * simple average. The input line is 1 raster for each color. However
 * the output line is interlaced (ie RBG for the first pixel, then RGB
 * for the second, and so on...). The output line are the value to use
 * to compensate for the white point. 
 * There is two algorithms:
 *
 *   The range goes from 0 to 0xfff, and the average is 0x800. So if
 *   the average input is 0x700, the output value for that dot must be
 *   0x1000-0x700=0x900.
 * 
 * and
 *
 *   the calibration needs to be a multiplication factor, to
 *   compensate for the too strong or too weak pixel in the sensor.
 *
 * See more info in doc/teco/teco2.txt
 *
 **/
static SANE_Status
logitech_do_calibration (Logitech_Scanner * dev)
{
  SANE_Status status;
  CDB cdb;
  size_t size;
  int i;
  int j;
  int colsub0_0, colsub0_1, colsub0_2;
  int colsub1_0, colsub1_1, colsub1_2;
  int *tmp_buf, *tmp_min_buf, *tmp_max_buf;	/* hold the temporary calibration */
  size_t tmp_buf_size, tmp_min_buf_size, tmp_max_buf_size;
  const char *calibration_algo;
  int cal_algo;

  colsub0_0 = 0;
  colsub0_1 = 0;
  colsub0_2 = 0;
  colsub1_0 = 0;
  colsub1_1 = 0;
  colsub1_2 = 0;

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

  /* Get default calibration algorithm. */
  cal_algo = dev->hw->cal_algo;
  if ((calibration_algo = getenv ("SANE_LOGITECH1_CAL_ALGO")) != NULL)
    {
      cal_algo = atoi (calibration_algo);
    }
  if (cal_algo != 0 && cal_algo != 1)
    {
      DBG (DBG_error, "Invalid calibration algorithm (%d)\n", cal_algo);
      cal_algo = 0;
    }

  /* white level red, green and blue calibration correction */
  /* 0x110 or 272 is middle value */
  colsub0_1 = 240 + (dev->val[OPT_WHITE_LEVEL_R].w);
  colsub0_2 = 240 + (dev->val[OPT_WHITE_LEVEL_G].w);
  colsub0_0 = 240 + (dev->val[OPT_WHITE_LEVEL_B].w);
  /* 14000 is middle value */
  colsub1_1 = 12720 + (40 * dev->val[OPT_WHITE_LEVEL_R].w);
  colsub1_2 = 12720 + (40 * dev->val[OPT_WHITE_LEVEL_G].w);
  colsub1_0 = 12720 + (40 * dev->val[OPT_WHITE_LEVEL_B].w);

  tmp_buf_size = dev->hw->cal_length * 3 * sizeof (int);
  tmp_min_buf_size = dev->hw->cal_length * 3 * sizeof (int);
  tmp_max_buf_size = dev->hw->cal_length * 3 * sizeof (int);
  tmp_buf = malloc (tmp_buf_size);
  tmp_min_buf = malloc (tmp_min_buf_size);
  tmp_max_buf = malloc (tmp_max_buf_size);
  memset (tmp_buf, 0, tmp_buf_size);
  memset (tmp_min_buf, 0xff, tmp_min_buf_size);
  memset (tmp_max_buf, 0x00, tmp_max_buf_size);

  if ((tmp_buf == NULL) || (tmp_min_buf == NULL) || (tmp_max_buf == NULL))
    {
      DBG (DBG_proc,
	   "logitech_do_calibration: not enough memory (%d bytes)\n",
	   tmp_buf_size);
      return (SANE_STATUS_NO_MEM);
    }

  for (i = 0; i < dev->hw->cal_lines; i++)
    {

      /*     MKSCSI_VENDOR_SPEC (cdb, SCSI_VENDOR_09, 6);   */

      /* No idea what that is. */
      switch (dev->scan_mode)
	{
	case LOGITECH_LINEART:
	  /* cdb.data[2] = 0x02; */
	  break;
	case LOGITECH_GRAY:
	  /* cdb.data[2] = 0x01; */
	  break;
	case LOGITECH_COLOR:
	  /* cdb.data[2] = 0x00; */
	  break;
	}

      /* Length of the scanner * number of bytes per color */
      size = dev->hw->cal_length * dev->hw->cal_col_len;
      cdb.data[3] = (size >> 8) & 0xff;
      cdb.data[4] = (size >> 0) & 0xff;

      hexdump (DBG_info2, "CDB:", cdb.data, cdb.len);
      /*
       * status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
       NULL, 0, dev->buffer, &size);
       */
      status = SANE_STATUS_IO_ERROR;
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "logitech_do_calibration: cannot read from the scanner\n");
	  free (tmp_buf);
	  return status;
	}
      /*hexdump (DBG_info2, "got calibration line:", dev->buffer, size); */

      for (j = 0; j < dev->hw->cal_length; j++)
	{
	  tmp_buf[3 * j + 0] += dev->buffer[3 * j + 0];
	  /* get lowest value */
	  if (tmp_min_buf[3 * j + 0] >> dev->buffer[3 * j + 0])
	    {
	      tmp_min_buf[3 * j + 0] = dev->buffer[3 * j + 0];
	    }
	  /* get highest value */
	  if (tmp_max_buf[3 * j + 0] < dev->buffer[3 * j + 0])
	    {
	      tmp_max_buf[3 * j + 0] = dev->buffer[3 * j + 0];
	    }
	  tmp_buf[3 * j + 1] += dev->buffer[3 * j + 1];
	  /* get lowest value */
	  if (tmp_min_buf[3 * j + 1] >> dev->buffer[3 * j + 1])
	    {
	      tmp_min_buf[3 * j + 1] = dev->buffer[3 * j + 1];
	    }
	  /* get hightest value */
	  if (tmp_max_buf[3 * j + 1] < dev->buffer[3 * j + 1])
	    {
	      tmp_max_buf[3 * j + 1] = dev->buffer[3 * j + 1];
	    }
	  tmp_buf[3 * j + 2] += dev->buffer[3 * j + 2];
	  /* get lowest value */
	  if (tmp_min_buf[3 * j + 2] >> dev->buffer[3 * j + 2])
	    {
	      tmp_min_buf[3 * j + 2] = dev->buffer[3 * j + 2];
	    }
	  /* get highest value */
	  if (tmp_max_buf[3 * j + 2] < dev->buffer[3 * j + 2])
	    {
	      tmp_max_buf[3 * j + 2] = dev->buffer[3 * j + 2];
	    }
	}
    }

  /* hexdump (DBG_info2, "calibration before average:", tmp_buf, tmp_buf_size); */
  /* hexdump (DBG_info2, "calibration before average min value:", tmp_min_buf, tmp_min_buf_size); */
  /* hexdump (DBG_info2, "calibration before average max value:", tmp_max_buf, tmp_max_buf_size); */

  /* Do the average. Since we got 12 lines, subtract lowest and highest
   * value and divide by 10  and create the final calibration value that
   * compensates for the for the white values read */
  for (j = 0; j < dev->hw->cal_length; j++)
    {
      /* subtract lowest and highest value */
      tmp_buf[j] = tmp_buf[j] - (tmp_min_buf[j] + tmp_max_buf[j]);
      tmp_buf[j + dev->hw->cal_length] = tmp_buf[j + dev->hw->cal_length]
	- (tmp_min_buf[j + dev->hw->cal_length]
	   + tmp_max_buf[j + dev->hw->cal_length]);
      tmp_buf[j + 2 * dev->hw->cal_length] =
	tmp_buf[j + 2 * dev->hw->cal_length] -
	(tmp_min_buf[j + 2 * dev->hw->cal_length] +
	 tmp_max_buf[j + 2 * dev->hw->cal_length]);
      /* sequence colors first color row one then two and last three   */
      if (cal_algo == 1)
	{
	  tmp_buf[j] = (colsub1_0 * (dev->hw->cal_lines - 2)) / tmp_buf[j];
	  tmp_buf[j + dev->hw->cal_length] =
	    (colsub1_1 * (dev->hw->cal_lines - 2)) / tmp_buf[j +
							     dev->hw->
							     cal_length];
	  tmp_buf[j + 2 * dev->hw->cal_length] =
	    (colsub1_2 * (dev->hw->cal_lines - 2)) / tmp_buf[j +
							     2 *
							     dev->hw->
							     cal_length];
	}
      else
	{
	  tmp_buf[j] = colsub0_0 - (tmp_buf[j] / (dev->hw->cal_lines - 2));
	  tmp_buf[j + dev->hw->cal_length] =
	    colsub0_1 -
	    (tmp_buf[j + dev->hw->cal_length] / (dev->hw->cal_lines - 2));
	  tmp_buf[j + 2 * dev->hw->cal_length] =
	    colsub0_2 -
	    (tmp_buf[j + 2 * dev->hw->cal_length] / (dev->hw->cal_lines - 2));
	}
    }

  /*hexdump (DBG_info2, "calibration after average:", tmp_buf, tmp_buf_size); */

  /* Build the calibration line to send. */
  for (j = 0; j < dev->hw->cal_length; j++)
    {
      dev->buffer[3 * j + 0] = (tmp_buf[3 * j + 0] >> 0) & 0xff;

      dev->buffer[3 * j + 1] = (tmp_buf[3 * j + 1] >> 0) & 0xff;

      dev->buffer[3 * j + 2] = (tmp_buf[3 * j + 2] >> 0) & 0xff;
    }

  free (tmp_buf);
  tmp_buf = NULL;

  /* Send the calibration line. The CDB is the same as the previous
   * one, except for the command. */

  cdb.data[0] = 0x0E;
  size = dev->hw->cal_length * dev->hw->cal_col_len;

  hexdump (DBG_info2, "CDB:", cdb.data, cdb.len);
  /*hexdump (DBG_info2, "calibration line sent:", dev->buffer, size); */
  /* status = sanei_scsi_cmd2 (dev->sfd, cdb.data, cdb.len,
     dev->buffer, size, NULL, NULL);
   */
  status = SANE_STATUS_IO_ERROR;
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error,
	   "logitech_do_calibration: calibration line was not sent correctly\n");
      return status;
    }

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

  return SANE_STATUS_GOOD;
}

/* Inquiry a device and returns TRUE if is supported. */
static int
logitech_identify_scanner (Logitech_Scanner * dev)
{
  SANE_Status status;
  SANE_Word vendor;
  SANE_Word product;
  int i;

  DBG (DBG_info, "logitech_identify_scanner: open\n");
  status = sanei_usb_get_vendor_product (dev->fd, &vendor, &product);

  /* Loop through our list to make sure this scanner is supported. */
  for (i = 0; i < NELEMS (scanners); i++)
    {
      if (scanners[i].vendor == vendor && scanners[i].product == product)
	{
	  unsigned char val;

	  DBG (DBG_info,
	       "logitech_identify_scanner: scanner %x:%x is in list\n",
	       vendor, product);
	  status = 1;
	  if (status == 0)
	    {

	      DBG (DBG_error,
		   "logitech_identify_scanner: this is not a LOGITECH (idVendor = %d, bProduct = %d) writing register failed with %s\n",
		   vendor, product, sane_strstatus (status));
	      return SANE_FALSE;
	    }
/*			status = logitech_read_reg (dev, 0x0c, &val); */
	  status = 1;
	  if (status == 0)
	    {
	      DBG (DBG_error,
		   "logitech_identify_scanner: this is not a LOGITECH (idVendor = %d, bProduct = %d), read register failed %s\n",
		   vendor, product, sane_strstatus (status));

	      return SANE_FALSE;
	    }
	  val = 0xf8;
	  if (val != 0xf8)
	    {
	      DBG (DBG_error,
		   " this is not a LOGITECH ( reg 0x0c. != val 0xf8 )\n");
	      return SANE_FALSE;
	    }

	  DBG (DBG_info, "logitech_identify_scanner: scanner supported\n");
	  dev->hw = &(scanners[i]);
	  return SANE_TRUE;

	}
    }
  DBG (DBG_error,
       "logitech_identify_scanner: this is not a LOGITECH ( reg0x0c. != val 0xf8 ) exit\n");
  return SANE_FALSE;
}

static SANE_Status
logitech_scanner_init (Logitech_Scanner * dev)
{
  SANE_Status status;
  SANE_Byte val;
  SANE_Byte *call_mem;
  SANE_Byte call_value;
  size_t call_size;
  int icall_max;
  int i, j;
  SANE_Byte urb26val;
  int delay;
  int command;
  size_t sizew;
  size_t sizer;
  size_t size11;
  size_t size15;

  DBG (DBG_proc, "logitech_scanner_init: open\n");

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;
  size11  = dev->size11;
  size15  = dev->size15;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);
  memset (dev->buffer11, 0, size11);
  memset (dev->buffer15, 0, size15);

  command = 0;

  /* urb 5, first write (40 | 0c | ab 00 | 00 00, xp 04 00 | 01 00 | 00 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xab, 0x0004, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb 6, first read (c0 0c 40 00 f4 f9 01 00 val=00 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0xf9f4, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb 7, write (40 0c 40 00 1c fa  01 00 00 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xfa1c, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb 8, write (40 0c ac 00 64 a8 01 00 00 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xac, 0xa864, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb 9,  (c0 0c 40 00 d4 ed 01 00 val=00) */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0xedd4, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb 10, write (40 0c 40 00 98 ff 01 00 80 */
  val = 0x80;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff98, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  DBG (DBG_proc,
       "To get a scan started, place paper in middle position scanner\n");

  /* urb 11, ep 83  data send = 00 00 received  =31 08 after urb 13 linux 31 02 */
  size11 = 0x02;
  dev->buffer11[0] = 0x00;
  dev->buffer11[1] = 0x00;
  status = sanei_usb_read_int (dev->fd, dev->buffer11, &size11);
  if (status != SANE_STATUS_GOOD)
    goto done;
  hexdump (DBG_info2, "urb 11 read init should be 31 08", dev->buffer11, size11);
  
  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* urb 12,  (c0 0c 42 00 d4 ed 01 00 val=00) */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x42, 0xedd4, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* urb 13, write (40 0c 42 00 8c ff 01 00 04 */
  val = 0x04;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x42, 0xff8c, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* int urb 11 return */

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  /* urb14, write 40 0c 21 00 f0 2e 01 00 00 data not 04 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x21, 0x2ef0, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 3000;
  usleep (delay);

  DBG (DBG_proc, " start urb 15 read int, detect paper\n");

  /* urb15, ep 83  data send 31 00 data received after urb 117 = 10 10 */
  dev->size15 = 0x02;
  dev->buffer15[0] = 0x31;
  dev->buffer15[1] = 0x00;
  status = sanei_usb_read_int (dev->fd, dev->buffer15, &dev->size15);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  hexdump (DBG_info2,
	   "urb 15 read init, should be 10 08 after bulk read 10 10 ",
	   dev->buffer15, dev->size15);

  /* some time delay or check needed  281-304 */
  delay = 14000;
  usleep (delay);

  /* urb16, write 40 0c 22 00 f0 2e 01 00 urb 16=90 */
  val = 0x90;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x22, 0x2ef0, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed 304-310 */
  delay = 6000;
  usleep (delay);

  /* urb17, write 40 0c ad 00 f0 2e 01 00 00 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xad, 0x2ef0, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

  /* urb18, write 40 0c ae 00 f0 2e 01 00 00 */
  val = 0x00;
  if (status != SANE_STATUS_GOOD)
    goto done;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xae, 0x2ef0, 0x01, &val);
  command++;

  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

  /* urb19, ep 01 write bulk, 0000 - 0fff
   * 0000 - 000b 00
   * 000c - 0a29 between 15 and 20
   * 0a2a - 0fff 00 
   * dummy program, place 0x15 from ... to ...) */
  call_value = 0x15;
  i = 0;

  DBG (DBG_proc,
       "logitech_scanner_init, memset start, i=%d, usb command=%d, value=0x%x\n",
       i, command, call_value);
  /* memset (call, call_value, sizeof (*call) );  */
  call_size = 0xfff;
  call_mem = malloc (call_size);
  memset (call_mem, 0x00, call_size);
  for (j = 0x0c; j < 0xa2a; j++)
    {
      call_mem[j] = call_value;
    }
  DBG (DBG_proc,
       "logitech_scanner_init, memset done, i=%d, usb command=%d, value=0x%x\n",
       i, command, call_value);

/*	status = sanei_usb_write_bulk(dev->fd, call_mem, sizeof (*call_mem) ); doesn't work yet reason?  */
  status = sanei_usb_write_bulk (dev->fd, call_mem, &call_size);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  urb26val = 0xb0;
  icall_max = 8;
  /* dummy calibration download data 
   * loop 3 * for low value, 4 * for high value  */
  for (i = 1; i < icall_max; i++)
    {
      if (i == 4)
	urb26val = 0x80;
      else
	urb26val = urb26val;

      /* urb 20, write (40 0c ac 00 ff ff 01 00 00 */
      val = 0x00;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xac, 0xffff, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb 21,  (c0 0c 40 00 d4 ed 01 00 val=80) */
      status =
	sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0xedd4, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb 22, write (40 0c 40 00 98 ff 01 00 80 */
      val = 0x80;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff98, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed for between urb 42-43 */
      delay = 3000;
      usleep (delay);

      /* urb 23,  (c0 0c 42 00 d4 ed 01 00 val=04) */
      status =
	sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x42, 0xedd4, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb24, write 40 0c 42 00 8c ff 01 00 04 */
      val = 0x04;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x42, 0xff8c, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb25, write 40 0c 21 00 f0 2e 01 00 00 */
      val = 0x00;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x21, 0x2ef0, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb26, write 40 0c 22 00 f0 2e 01 00 urb urb26=b0, urb35=d0, urb46=f0
       * urb56=80, urb66=a0, urb76=c0, urb86=e0*/
      val = urb26val;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x22, 0x2ef0, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;
      urb26val = urb26val + 0x20;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb27, write 40 0c ad 00 f0 2e 01 00 00 */
      val = 0x00;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xad, 0x2ef0, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* some time delay or check needed */
      delay = 3000;
      usleep (delay);

      /* urb28, write 40 0c ae 00 f0 2e 01 00 00 */
      val = 0x00;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xae, 0x2ef0, 0x01, &val);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      /* urb29, ep 01 write bulk, 0000 - 0fff
       * 0000 - 000b 00
       * 000c - 0a29 between 15 and 20
       * 0a2a - 0fff 00 
       * dummy program, place 0x15 from ... to ...) */
      if (i < 4)
	{
	  call_value = 0x15 + i;
	}
      else if ((icall_max / 2) <= i && i < icall_max)
	{
	  call_value = 0xa5 + i;
	}

      DBG (DBG_proc,
	   "logitech_scanner_init, memset start, i=%d, usb command=%d, value=0x%x\n",
	   i, command, call_value);
      /* memset (call, call_value, sizeof (*call) );  */
      call_size = 0xfff;
      call_mem = malloc (call_size);
      memset (call_mem, 0x00, call_size);
      for (j = 0x0c; j < 0xa2a; j++)
	{
	  call_mem[j] = call_value;
	}
      DBG (DBG_proc,
	   "logitech_scanner_init, memset done, i=%d, usb command=%d, value=0x%x\n",
	   i, command, call_value);

/*	status = sanei_usb_write_bulk(dev->fd, call_mem, sizeof (*call_mem) ); doesn't work yet reason?  */
      status = sanei_usb_write_bulk (dev->fd, call_mem, &call_size);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;
    }

  /*  30-39, 40-49 data is also 0x15
   * urb 50-59, 60-69, 70-79, 80-89  data is also 0xa5  */

  /* Urb 90 read c0 0c 40 00 00 00 01 00  val=80 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

done:
  if (status)
    {
      DBG (DBG_error, "logitech_scanner_init failed for command %d: %s\n",
	   command, sane_strstatus (status));
    }
  DBG (DBG_proc, "logitech_scanner_init: exit\n");
  return status;
}

/* Attach a scanner to this backend. */
static SANE_Status
attach_scanner (SANE_String_Const devicename, Logitech_Scanner ** devp)
{
  Logitech_Scanner *dev;
  int fd;

  DBG (DBG_info, "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 = logitech_init ();
  if (dev == NULL)
    {
      DBG (DBG_error, "ERROR: not enough memory\n");
      return SANE_STATUS_NO_MEM;
    }

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

  if (sanei_usb_open (devicename, &fd) != 0)
    {
      DBG (DBG_error, "ERROR: attach_scanner: open failed\n");
      logitech_free (dev);
      return SANE_STATUS_INVAL;
    }
  /* Fill some scanner specific values. */
  dev->devicename = strdup (devicename);
  dev->fd = fd;

  /* Now, check that it is a scanner we support. */

  if (logitech_identify_scanner (dev) == SANE_FALSE)
    {
      DBG (DBG_error,
	   "ERROR: attach_scanner: scanner-identification failed\n");
      logitech_free (dev);
      return SANE_STATUS_INVAL;
    }

  logitech_close (dev);

  /* Build list of scanner supported resolutions. */
  DBG (DBG_proc, "attach_scanner: build resolution list\n");

  if (dev->hw->color_adjust[0].resolution_x != 0)
    {
      int num_entries;
      int i;
      num_entries = 0;

      while (dev->hw->color_adjust[num_entries].resolution_x != 0)
	num_entries++;

      dev->resolutions_list = malloc (sizeof (SANE_Word) * (num_entries + 1));

      if (dev->resolutions_list == NULL)
	{
	  DBG (DBG_error,
	       "ERROR: attach_scanner: scanner resolution list failed\n");
	  logitech_free (dev);
	  return SANE_STATUS_NO_MEM;
	}

      dev->resolutions_list[0] = num_entries;
      DBG (DBG_proc, "attach_scanner: make color resolution table \n");
      for (i = 0; i < num_entries; i++)
	{
	  dev->resolutions_list[i + 1] =
	    dev->hw->color_adjust[i].resolution_x;
	}
    }
  else
    {
      dev->resolutions_list = NULL;
    }

  /* Set the default options for that scanner. */
  dev->sane.name = dev->devicename;
  dev->sane.vendor = dev->hw->vendor_name;
  dev->sane.model = dev->hw->product_name;
  dev->sane.type = SANE_I18N ("scanner");

  /* 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)
{
  DBG (DBG_proc, "attach_one: open \n");
  attach_scanner (dev, NULL);
  DBG (DBG_proc, "attach_one: exit \n");
  return SANE_STATUS_GOOD;
}

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

  DBG (DBG_proc, "logitech_init_options: open\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;
    }
  DBG (DBG_proc,
       "logitech_init_options: done loop opt_num_options=%d, i=%d \n",
       OPT_NUM_OPTIONS, i);
  /* Number of options. */
  dev->opt[OPT_NUM_OPTS].name = "";
  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 = max_string_size (scan_mode_list);
  dev->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_MODE].constraint.string_list = scan_mode_list;
  dev->val[OPT_MODE].s = (SANE_Char *) strdup ("");	/* will be set later */

  /* X and Y 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_RANGE;
  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;

/* 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 = 0;
  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->hw->x_range;
  dev->val[OPT_TL_X].w = dev->hw->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->hw->y_range;
  dev->val[OPT_TL_Y].w = dev->hw->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->hw->x_range;
  dev->val[OPT_BR_X].w = dev->hw->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->hw->y_range;
  dev->val[OPT_BR_Y].w = dev->hw->y_range.max;

  /* brightness   */
  dev->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  dev->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  dev->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  dev->val[OPT_BRIGHTNESS].w = 80;	/* to get middle value */

  /* contrast     */
  dev->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  dev->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  dev->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  dev->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
  dev->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  dev->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_CONTRAST].constraint.range = &contrast_range;
  dev->val[OPT_CONTRAST].w = 80;	/* to get middle value */

  /* 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 = SANE_CAP_ADVANCED;
  dev->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  dev->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* 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_DITHER].name = "dither";
  dev->opt[OPT_DITHER].title = SANE_I18N ("Dither");
  dev->opt[OPT_DITHER].desc = SANE_I18N ("Dither");
  dev->opt[OPT_DITHER].type = SANE_TYPE_STRING;
  dev->opt[OPT_DITHER].size = max_string_size (dither_list);
  dev->opt[OPT_DITHER].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_DITHER].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_DITHER].constraint.string_list = dither_list;
  dev->val[OPT_DITHER].s = strdup (dither_list[0]);

  /* Color filter */
  dev->opt[OPT_FILTER_COLOR].name = "color-filter";
  dev->opt[OPT_FILTER_COLOR].title = "Color dropout";
  dev->opt[OPT_FILTER_COLOR].desc = "Color dropout";
  dev->opt[OPT_FILTER_COLOR].type = SANE_TYPE_STRING;
  dev->opt[OPT_FILTER_COLOR].size = max_string_size (filter_color_list);
  dev->opt[OPT_FILTER_COLOR].cap |= SANE_CAP_INACTIVE;
  dev->opt[OPT_FILTER_COLOR].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  dev->opt[OPT_FILTER_COLOR].constraint.string_list = filter_color_list;
  dev->val[OPT_FILTER_COLOR].s = (SANE_Char *) strdup (filter_color_list[0]);

  /* black level correction */
  dev->opt[OPT_BLACK_LEVEL].name = SANE_NAME_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].title = SANE_TITLE_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].desc = SANE_DESC_BLACK_LEVEL;
  dev->opt[OPT_BLACK_LEVEL].type = SANE_TYPE_INT;
  dev->opt[OPT_BLACK_LEVEL].unit = SANE_UNIT_NONE;
  dev->opt[OPT_BLACK_LEVEL].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_BLACK_LEVEL].constraint.range = &black_level_range;
  dev->val[OPT_BLACK_LEVEL].w = 02;	/* to get middle value */

  /* red level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_R].name = SANE_NAME_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].title = SANE_TITLE_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].desc = SANE_DESC_WHITE_LEVEL_R;
  dev->opt[OPT_WHITE_LEVEL_R].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_R].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_R].constraint.range = &red_level_range;
  dev->val[OPT_WHITE_LEVEL_R].w = 02;	/* to get middle value */

  /* green level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_G].name = SANE_NAME_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].title = SANE_TITLE_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].desc = SANE_DESC_WHITE_LEVEL_G;
  dev->opt[OPT_WHITE_LEVEL_G].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_G].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_G].constraint.range = &green_level_range;
  dev->val[OPT_WHITE_LEVEL_G].w = 02;	/* to get middle value */

  /* blue level calibration manual correction */
  dev->opt[OPT_WHITE_LEVEL_B].name = SANE_NAME_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].title = SANE_TITLE_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].desc = SANE_DESC_WHITE_LEVEL_B;
  dev->opt[OPT_WHITE_LEVEL_B].type = SANE_TYPE_INT;
  dev->opt[OPT_WHITE_LEVEL_B].unit = SANE_UNIT_NONE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint_type = SANE_CONSTRAINT_RANGE;
  dev->opt[OPT_WHITE_LEVEL_B].constraint.range = &blue_level_range;
  dev->val[OPT_WHITE_LEVEL_B].w = 02;	/* to get middle value */

  /* 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 scan mode. This might change some
   * values previously set here. */

  sane_control_option (dev, OPT_MODE, SANE_ACTION_SET_VALUE,
		       (SANE_String_Const *) scan_mode_list[0], NULL);
  DBG (DBG_proc, "logitech_init_options: exit\n");
}

/* 
 * Wait until the scanner is ready.
 */
static SANE_Status
logitech_wait_scanner (Logitech_Scanner * dev)
{
  SANE_Status status;
  int timeout;

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

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

  while (timeout > 0)
    {

      /* test unit ready */
/*      status = sanei_scsi_cmd (dev->fd, cdb.data, cdb.len, NULL, NULL);*/
      status = SANE_STATUS_GOOD;
      if (status == SANE_STATUS_GOOD)
	{
	  DBG (DBG_proc, "logitech_wait_scanner: scanner status good\n");
	  return SANE_STATUS_GOOD;
	}

      sleep (1);
    };

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


/* 
 * Adjust the rasters. This function is used during a color scan,
 * because the scanner does not present a format sane can interpret
 * directly.
 */

#define COLOR_0 (color_adjust->z3_color_0)
#define COLOR_1 (color_adjust->z3_color_1)
#define COLOR_2 (color_adjust->z3_color_2)

static void
logitech_adjust_raster (Logitech_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 color;			/* color for that raster */
  size_t offset;
  const struct dpi_color_adjust *color_adjust = dev->color_adjust;

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

  color = 0;			/* keep gcc quiet */

  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 and the line which this raster belongs to.
       */
      line = 0;
      if (dev->raster_num < color_adjust->color_shift)
	{
	  /* Top of the picture. */
	  if (color_adjust->factor_x)
	    {
	      color = COLOR_2;
	    }
	  else
	    {
	      color = COLOR_1;
	    }
	  line = dev->raster_num;
	}
      else if (dev->raster_num < (3 * color_adjust->color_shift))
	{
	  /* Top of the picture. */
	  if ((dev->raster_num - color_adjust->color_shift) % 2)
	    {
	      color = COLOR_0;
	      line = (dev->raster_num - color_adjust->color_shift) / 2;
	    }
	  else
	    {
	      if (color_adjust->factor_x)
		{
		  color = COLOR_2;
		}
	      else
		{
		  color = COLOR_1;
		}
	      line = (dev->raster_num + color_adjust->color_shift) / 2;
	    }
	}
      else if (dev->raster_num >=
	       dev->raster_real - color_adjust->color_shift)
	{
	  /* Bottom of the picture. */
	  if (color_adjust->factor_x)
	    {
	      color = COLOR_1;
	    }
	  else
	    {
	      color = COLOR_2;
	    }
	  line = dev->line;
	}
      else if (dev->raster_num >=
	       dev->raster_real - 3 * color_adjust->color_shift)
	{
	  /* Bottom of the picture. */
	  if ((dev->raster_real - dev->raster_num -
	       color_adjust->color_shift) % 2)
	    {
	      if (color_adjust->factor_x)
		{
		  color = COLOR_1;
		  line = dev->line;
		}
	      else
		{
		  color = COLOR_0;
		  line = dev->line + color_adjust->color_shift - 1;
		}
	    }
	  else
	    {
	      if (color_adjust->factor_x)
		{
		  color = COLOR_0;
		  line = dev->line + color_adjust->color_shift;
		}
	      else
		{
		  color = COLOR_2;
		  line = dev->line;
		}
	    }
	}
      else
	{
	  /* Middle of the picture. */
	  switch ((dev->raster_num) % 3)
	    {
	    case 0:
	      color = COLOR_2;
	      if (color_adjust->factor_x)
		line = dev->raster_num / 3 + color_adjust->color_shift;
	      else
		line = dev->raster_num / 3 - color_adjust->color_shift;
	      break;
	    case 1:
	      color = COLOR_0;
	      line = dev->raster_num / 3;
	      break;
	    case 2:
	      color = COLOR_1;
	      if (color_adjust->factor_x)
		line = dev->raster_num / 3 - color_adjust->color_shift;
	      else
		line = dev->raster_num / 3 + color_adjust->color_shift;
	      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->params.bytes_per_line));

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

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

      DBG (DBG_info, "raster=%d, line=%d, color=%d\n", dev->raster_num,
	   dev->line + line, color);

      if ((color_adjust->factor_x == 0 && color == COLOR_2) ||
	  (color_adjust->factor_x == 1 && color == COLOR_1))
	{
	  /* This blue raster completes a new line */
	  dev->line++;
	  dev->image_end += dev->params.bytes_per_line;
	}

      dev->raster_num++;
    }

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


/* Read the image from the scanner and fill the temporary buffer with it. */
static SANE_Status
logitech_fill_image (Logitech_Scanner * dev)
{
  SANE_Status status;
  size_t size;
  SANE_Byte val;
  int command = 100;
  size_t size_ri;
  SANE_Byte buffer_ri[2];
  int delay;
  size_t sizew;
  size_t sizer;
  size_t i;

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

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

  sizew = dev->windoww_size;
  sizer = dev->windowr_size;

  memset (dev->windoww, 0, sizew);
  memset (dev->windowr, 0, sizer);

  /* Copy the complete lines, plus the incompletes
   * 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;

  DBG (DBG_proc, "logitech_fill_image: real bytes left=0x%x \n",
       dev->real_bytes_left);

  /* Fill image, some setting for the scanner first */

  /* Urb 91 write 40 0c 40 00 98 ff 01 00,  */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff98, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 92 read c0 04 4f 00 00 00 02 00  val=01 01  linux 00 00 */
  sizer = 0x02;
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x04, 0x4f, 0x0000, sizer,
			   dev->windowr);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  hexdump (DBG_info2, "urb 92 val = 01 01", dev->windowr, sizer);

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 93 (read c0 0c 52 00 00 00 01 00 val =08  linux 07 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x52, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 94 (write 40 0c 0e 00 1b ff 01 00 val =24 */
  val = 0x24;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x0e, 0xff1b, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 95 (write 40 0c 0f 00 1b ff 01 00 val = 00 */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x0f, 0xff1b, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 96 (write 40 0c 10 00 1b ff 01 00 val = 29 */
  val = 0x29;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x10, 0xff1b, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 97 (write 40 0c 11 00 1b ff 01 00 val = 0a */
  val = 0x0a;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x11, 0xff1b, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 98 (read c0 0c 0e 00 00 00 01 00 val =24 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x0e, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 99 (read c0 0c 0f 00 00 00 01 00 val =00 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x0f, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 100 (read c0 0c 10 00 00 00 01 00 val =29 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x10, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 101 (read c0 0c 11 00 00 00 01 00 val =0a */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x11, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 102 (write 40 04 oc 00 b8 fe 17 00 val = 
   * f1 ff 24 00 29 0a 48 49 2c a8 2b 2b 7a 8e 5f 31 40 eb 0c 30 04 00 00 
   * linux value problem*/
  /*status = logitech_write_reg (dev, 0x04, 0x0c, 0x17, logitech_init_str1); */
  sizew = 0x17;
  for (i = 0; i < sizew; i++)
    dev->windoww[i] = logitech_init_str1[i];
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x04, 0x0c, 0xfeb8, sizew,
			   dev->windoww);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 103 (write 40 04 30 00 b8 fe 0a 00 val =
   * 10 12 70 72 50 52 70 73 ff ff , linux value problem */
  /*status = logitech_write_reg (dev, 0x04, 0x30, 0x0a, logitech_init_str2); */
  sizew = 0x0a;
  for (i = 0; i < sizew; i++)
    dev->windoww[i] = logitech_init_str2[i];
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x04, 0x30, 0xfeb8, sizew,
			   dev->windoww);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 104 (write 40 04 40 00 b8 fe 03 00 val = 36 2b 00  linux value problem */
  sizew = 0x03;
  for (i = 0; i < sizew; i++)
    dev->windoww[i] = logitech_init_str3[i];
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x04, 0x40, 0xfeb8, sizew,
			   dev->windoww);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 105 (C) write 40 0c aa 00 03 00 01 00  */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xaa, 0x0003, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 106 (C) write: 40 0c ad 00 03 00 01 00  */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xad, 0x0003, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 107 (C) write: 40 0c ae 00 03 00 01 00  */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xae, 0x0003, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 108 (C) write: 40 0c ac 00 03 00 01 00  */
  val = 0x00;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0xac, 0x0003, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 109 (C) read: c0 0c 40 00 28 ff 01 00  linux read e1 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0xff28, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);
  
      /* Urb 110 (write 40 0c 40 00 40 ff 01 00 val = 36*, maybe write val from urb 109 */
      sizew = 0x01;
	    dev->windoww[0] = 0x36;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff40, sizew, dev->windoww);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      hexdump (DBG_info2, "urb 110", dev->windoww, sizew);
      
      /* some time delay or check needed */
      delay = 2000;
      usleep (delay);

      /* Urb 111 (write 40 0c 40 00 40 ff 01 00 val = 76 */
      sizew= 0x01;
      dev->windoww[0] = 0x76;
      status =
	sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff40, sizew, dev->windoww);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      hexdump (DBG_info2, "urb 111", dev->windoww, sizew);

    do
    { 
      /*  Try to read the maximum number of bytes.  */
      DBG (DBG_proc,
	   "logitech_fill_image: real dev bytes left, start while loop=0x%x \n",
	   dev->real_bytes_left);

      size = dev->real_bytes_left;
      command++;
      DBG (DBG_proc, "command while loop=%d\n", command);
      /*            if (size > dev->image_size - dev->raster_ahead - dev->image_end)
         {
         size = dev->image_size - dev->raster_ahead - dev->image_end;
         DBG (DBG_proc, "logitech_fill_image: size > dev->image_size - dev->raster_ahead - dev->image_end, size = 0x%x \n", size);
         }  */
      if (size > dev->buffer_size)
	{
	  size = dev->buffer_size;
	  DBG (DBG_proc,
	       "logitech_fill_image: size > dev->buffer_size, size =0x%x \n",
	       size);
	}
      if (size > 0x8000)
	{
	  size = 0x8000;	/* it seems package from printer < 8000/urb */

	  DBG (DBG_proc, "logitech_fill_image: size > 0x20000, size =0x%x \n",
	       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);
	}
delay = 25000;
  usleep (delay);

      /* Urb 112 first bulk read, ep 82 scanning, urb 113 2e bulk read for image */

      DBG (DBG_proc,
	   "logitech_fill_image: start read bulk to read size=0x%x, dev->real_bytes_left 0x%x\n",
	   size, dev->real_bytes_left);

      status = sanei_usb_read_bulk (dev->fd, dev->buffer, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       "logitech_fill_image: cannot read from the scanner\n");
	  return status;
	}
      DBG (DBG_info,
	   "logitech_fill_image: data read size=0x%lx bytes (bpl=0x%x)\n",
	   (long) size, dev->params.bytes_per_line);

      if (dev->scan_mode == LOGITECH_COLOR)
	{
	  logitech_adjust_raster (dev, size);
	}
      else
	{
	  memcpy (dev->image + dev->image_end, dev->buffer, size);
	  dev->image_end += size;
	  DBG (DBG_info,
	       "logitech_fill_image: dev->image_end=0x%x bytes\n",
	       dev->image_end);
	}
      dev->real_bytes_left -= size;

      /* urb 15 (return from), ep 83  val=10 10 */
     /* dev->size15 = 0x02;
      dev->buffer15[0] = 0x00;
      dev->buffer15[1] = 0x00;
      status = sanei_usb_read_int (dev->fd, dev->buffer15, &dev->size15);
      if (status != SANE_STATUS_GOOD)
	goto done;
      command++;

      hexdump (DBG_info2, "after urb 117, urb 15 return should be 10 10",
	       dev->buffer15, dev->size15);
*/
      /* some time delay or check needed */
      if (dev->real_bytes_left < size)
      {
	dev->real_bytes_left = 0;	/* do only if size is small */
	  size = 0;
	  dev->bytes_left = 0;
   dev->buffer15[0] = 0x10;   /* to keep save if switch doesn't work ok */
   dev->buffer15[1] = 0x10;


	  DBG (DBG_proc,
	       "logitech_fill_image, detect end of paper, scan can stop?, size =0\n");
      } 
	    DBG (DBG_proc, "logitech_fill_image, next loop for scan lines\n");
    }      
  while ((dev->buffer15[0] != 0x10) || (dev->buffer15[1] != 0x10));

  /* some time delay or check needed */
  delay = 1000;
  usleep (delay);

  /* Urb 114 (C) ep= <<<< 00000000: 76 00000000: c0 0c 40 00 00 02 01 00  */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0x0200, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 115 (write 40 0c 40 00 44 ff 01 00 val = 36 */
  val = 0x36;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff44, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 116 (read c0 0c 40 00 2c ff 01 00 val =36 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x40, 0xff2c, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 4000;
  usleep (delay);

  /* Urb 117 (write 40 0c 40 00 44 ff 01 00 val = 26 */
  val = 0x26;
  status =
    sanei_usb_control_msg (dev->fd, 0x40, 0x0c, 0x40, 0xff44, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 2000;
  usleep (delay);

  /* Urb 118 (C) ep= <<<< 00000000: 00 00 00000000: c0 04 4f 00 f0 02 02 00  */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x04, 0x4f, 0x02f0, 0x02, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

  /* Urb 119 (C) ep= <<<< 00000000: 79 00000000: c0 0c 52 00 00 00 01 00 */
  status =
    sanei_usb_control_msg (dev->fd, 0xc0, 0x0c, 0x52, 0x0000, 0x01, &val);
  if (status != SANE_STATUS_GOOD)
    goto done;
  command++;

  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 120 (C) ep= <<<< 00000000: 40 0c 0e 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 121 (C) ep= <<<< 00000000: 40 0c 0f 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 122 (C) ep= <<<< 00000000: 40 0c 10 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 123 (C) ep= <<<< 00000000: 40 0c 11 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 124 (C) ep= <<<< 00000000: 94 00000000: c0 0c 0e 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 125 (C) ep= <<<< 00000000: 02 00000000: c0 0c 0f 00 00 00 01 00 */
  /* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 126 (C) ep= <<<< 00000000: a4 00000000: c0 0c 10 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 127 (C) ep= <<<< 00000000: 07 00000000: c0 0c 11 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 128 (C) ep= <<<< 00000000: 40 04 0c 00 00 00 17 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 129 (C) ep= <<<< 00000000: 40 04 30 00 00 00 0a 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 130 (C) ep= <<<< 00000000: 40 04 40 00 00 00 03 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 131 (C) ep= <<<< 00000000: 40 0c aa 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 132 (C) ep= <<<< 00000000: 40 0c ad 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 133 (C) ep= <<<< 00000000: 40 0c ae 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 134 (C) ep= <<<< 00000000: 40 0c 1c 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 135 (C) ep= <<<< 00000000: 40 0c 1d 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 136 (C) ep= <<<< 00000000: 40 0c ac 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 137 (C) ep= <<<< 00000000: 36 00000000: c0 0c 40 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 138 (C) ep= <<<< 00000000: 40 0c 40 00 00 00 01 00 */
/* some time delay or check needed */
  delay = 6000;
  usleep (delay);

/*Urb 139 (C) ep= <<<< 00000000: 40 0c 40 00 00 00 01 00 */


done:
  if (status)
    {
      DBG (DBG_error, "logitech_fill_image failed for command %d: %s\n",
	   command, sane_strstatus (status));
    }

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

  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
logitech_copy_raw_to_frontend (Logitech_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;

  switch (dev->scan_mode)
    {
    case LOGITECH_LINEART:
      {
	/* Invert black and white. */
	unsigned char *src = dev->image + dev->image_begin;
	size_t i;

	for (i = 0; i < size; i++)
	  {
	    *buf = *src ^ 0xff;
	    src++;
	    buf++;
	  }
      }
      break;

    case LOGITECH_GRAY:
    case LOGITECH_COLOR:
      memcpy (buf, dev->image + dev->image_begin, size);
      break;
    }

  dev->image_begin += size;
}

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

  if (dev->scanning == SANE_TRUE)
    {

      /* Reset the scanner */
      logitech_reset_window (dev);
      logitech_close (dev);
    }

  dev->scanning = SANE_FALSE;

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

  return SANE_STATUS_CANCELLED;
}

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

/* Sane entry points */

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

  DBG_INIT ();

  DBG (DBG_sane_init, "sane_init\n");

  authorize = authorize;	/* silence gcc */

  DBG (DBG_error, "This is sane-logitech1 version %d.%d-%d\n", V_MAJOR,
       V_MINOR, BUILD);
  DBG (DBG_error, "(C) 2004 by Gerard Klaver\n");

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

  sanei_usb_init ();

  fp = sanei_config_open (LOGITECH1_CONFIG_FILE);
  if (!fp)
    {
      /* No default scanner? */
      DBG (DBG_warning, "configuration file not found (%s)\n",
	   LOGITECH1_CONFIG_FILE);

      return SANE_STATUS_GOOD;
    }

  while (sanei_config_read (line, sizeof (line), fp))
    {
      SANE_Word vendor;
      SANE_Word product;

      if (line[0] == '#')	/* ignore line comments */
	continue;
      len = strlen (line);

      if (!len)
	continue;		/* ignore empty lines */
      if (sscanf (line, "usb %i %i", &vendor, &product) == 2)
	{

	  sanei_usb_attach_matching_devices (line, attach_one);
	}
      else
	{
	  /* Garbage. Ignore. */
	  DBG (DBG_warning, "bad configuration line: \"%s\" - ignoring.\n",
	       line);
	}

    }

  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 local_only)
{
  Logitech_Scanner *dev;
  int i;

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

  local_only = local_only;	/* silence gcc */

  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)
{
  Logitech_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)
	    {
	      DBG (DBG_error, "sane_name %s, devicename %s\n", dev->sane.name,
		   devicename);
	      break;
	    }
	}

      DBG (DBG_error, "sane_name %s, devicename %s\n", dev->sane.name,
	   devicename);
      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;
    }

  logitech_init_options (dev);

  *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)
{
  Logitech_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)
{
  Logitech_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_Y:
	case OPT_BR_Y:
	case OPT_TL_X:
	case OPT_BR_X:
	case OPT_PREVIEW:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_THRESHOLD:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  *(SANE_Word *) val = dev->val[option].w;
	  return SANE_STATUS_GOOD;

	  /* string options */
	case OPT_MODE:
	case OPT_DITHER:
	case OPT_FILTER_COLOR:
	  strcpy (val, dev->val[option].s);
	  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)
	{
	  /* Numeric side-effect options */
	case OPT_TL_Y:
	case OPT_BR_Y:
	case OPT_TL_X:
	case OPT_BR_X:
	case OPT_THRESHOLD:
	case OPT_RESOLUTION:
	case OPT_BRIGHTNESS:
	case OPT_CONTRAST:
	case OPT_BLACK_LEVEL:
	case OPT_WHITE_LEVEL_R:
	case OPT_WHITE_LEVEL_G:
	case OPT_WHITE_LEVEL_B:
	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_PARAMS;
	    }
	  dev->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

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

	  /* String side-effect free options */
	case OPT_DITHER:
	  free (dev->val[option].s);
	  dev->val[option].s = (SANE_String) strdup (val);
	  return SANE_STATUS_GOOD;

	case OPT_FILTER_COLOR:
	  free (dev->val[option].s);
	  dev->val[option].s = (SANE_String) strdup (val);
	  return SANE_STATUS_GOOD;

	  /* String side-effect options */
	case OPT_MODE:
	  if (strcmp (dev->val[option].s, val) == 0)
	    return SANE_STATUS_GOOD;

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

	  dev->opt[OPT_DITHER].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_FILTER_COLOR].cap |= SANE_CAP_INACTIVE;
	  dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	  dev->opt[OPT_BLACK_LEVEL].cap &= ~SANE_CAP_INACTIVE;
	  /*dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	     dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	     dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
	   */
	  if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_LINEART) ==
	      0)
	    {
	      dev->scan_mode = LOGITECH_LINEART;
	      dev->depth = 8;
	      dev->opt[OPT_DITHER].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_FILTER_COLOR].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY) ==
		   0)
	    {
	      dev->scan_mode = LOGITECH_GRAY;
	      dev->depth = 8;

	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_FILTER_COLOR].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (dev->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR)
		   == 0)
	    {
	      dev->scan_mode = LOGITECH_COLOR;
	      dev->depth = 8;

	      dev->opt[OPT_WHITE_LEVEL_R].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_G].cap &= ~SANE_CAP_INACTIVE;
	      dev->opt[OPT_WHITE_LEVEL_B].cap &= ~SANE_CAP_INACTIVE;
	    }
	  /* The Logitech supports only a handful of resolution. */
	  if (dev->resolutions_list != NULL)
	    {
	      int i;

	      dev->opt[OPT_RESOLUTION].constraint_type =
		SANE_CONSTRAINT_WORD_LIST;
	      dev->opt[OPT_RESOLUTION].constraint.word_list =
		dev->resolutions_list;

	      /* If the resolution isn't in the list, set a default. */
	      for (i = 1; i <= dev->resolutions_list[0]; i++)
		{
		  if (dev->resolutions_list[i] >= dev->val[OPT_RESOLUTION].w)
		    break;
		}
	      if (i > dev->resolutions_list[0])
		{
		  /* Too big. Take default. */
		  dev->val[OPT_RESOLUTION].w = DEF_RESOLUTION;
		}
	      else
		{
		  /* Take immediate superioir value. */
		  dev->val[OPT_RESOLUTION].w = dev->resolutions_list[i];
		}
	    }

	  /* String side-effect options */

	  if (info)
	    {
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	    }
	  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)
{
  Logitech_Scanner *dev = handle;

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

  if (!(dev->scanning))
    {

      /* Setup the parameters for the scan. These values will be re-used
       * in the SET WINDOWS command. */
      if (dev->val[OPT_PREVIEW].w == SANE_TRUE)
	{

	  dev->x_resolution = 150;	/* will later be changed to 100 */
	  dev->y_resolution = 150;	/* will later be changed to 100 */

	  dev->x_tl = 0;
	  dev->y_tl = 0;
	  dev->x_br = mmToIlu (SANE_UNFIX (dev->hw->x_range.max));
	  dev->y_br = mmToIlu (SANE_UNFIX (dev->hw->y_range.max));

	}
      else
	{
	  dev->x_resolution = dev->val[OPT_RESOLUTION].w;
	  dev->y_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));
	}

      if (dev->x_resolution > dev->hw->x_resolution_max)
	{
	  dev->x_resolution = dev->hw->x_resolution_max;
	}

      /* 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;


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

      dev->params.last_frame = SANE_TRUE;

      switch (dev->scan_mode)
	{
	case LOGITECH_LINEART:
	  dev->params.format = SANE_FRAME_GRAY;
	  dev->params.pixels_per_line =
	    ((dev->width * dev->x_resolution) /
	     dev->hw->x_resolution_max) & ~0x7;
	  dev->params.bytes_per_line = dev->params.pixels_per_line / 8;
	  dev->params.depth = 1;
	  dev->color_adjust = NULL;
	  break;
	case LOGITECH_GRAY:
	  dev->params.format = SANE_FRAME_GRAY;
	  dev->params.pixels_per_line =
	    ((dev->width * dev->x_resolution) / dev->hw->x_resolution_max);
	  dev->params.bytes_per_line = dev->params.pixels_per_line;
	  dev->params.depth = 8;
	  dev->color_adjust = NULL;
	  break;
	case LOGITECH_COLOR:
	  dev->params.format = SANE_FRAME_RGB;
	  dev->params.pixels_per_line =
	    ((dev->width * dev->x_resolution) / dev->hw->x_resolution_max);
	  dev->params.bytes_per_line = dev->params.pixels_per_line * 3;
	  dev->params.depth = 8;

	  if (dev->resolutions_list != NULL)
	    {
	      /* This scanner has a fixed number of supported
	       * resolutions. Find the color shift for that
	       * resolution. */

	      int i;
	      for (i = 0;
		   dev->hw->color_adjust[i].resolution_y != dev->y_resolution;
		   i++);

	      dev->color_adjust = &dev->hw->color_adjust[i];
	    }
	  else
	    {
	      dev->color_adjust = &dev->hw->color_adjust[0];
	    }
	  break;
	}

      dev->params.lines =
	(dev->length * dev->y_resolution) / dev->hw->x_resolution_max;
    }

  /* 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)
{
  Logitech_Scanner *dev = handle;
  SANE_Status status;

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

  if (!(dev->scanning))
    {
      sane_get_parameters (dev, NULL);

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

      /* Initialize the scanner. */
      status = logitech_scanner_init (dev);
      if (status)
	{
	  DBG (DBG_error, "ERROR: failed to init the scanner\n");
	  logitech_close (dev);
	  return status;
	}

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

      /* do sheetfed calibration */
      /*     status = logitech_do_calibration (dev);
         if (status)
         {
         logitech_close (dev);
         return status;
         }
       */
      status = logitech_set_window (dev);
      if (status)
	{
	  logitech_close (dev);
	  return status;
	}

      status = logitech_get_scan_size (dev);
      if (status)
	{
	  logitech_close (dev);
	  return status;
	}

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

      /* Rasters are meaningfull only in color mode. */
      dev->raster_size = dev->params.pixels_per_line;
      dev->raster_real = dev->params.lines * 3;
      dev->raster_num = 0;
      dev->line = 0;

      status = logitech_scan (dev);
      if (status)
	{
	  logitech_close (dev);
	  return status;
	}

      status = logitech_wait_for_data (dev);
      if (status)
	{
	  logitech_close (dev);
	  return status;
	}
    }

  dev->image_end = 0;
  dev->image_begin = 0;

  dev->bytes_left = dev->params.bytes_per_line * dev->params.lines;
  dev->real_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;
  Logitech_Scanner *dev = handle;
  size_t size;
  int buf_offset;		/* offset into buf */

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

  *len = 0;

  if (!(dev->scanning))
    {
      DBG (DBG_proc, "sane_read: before do_cancel enter\n");
      /* 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 = logitech_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;
	}
      logitech_copy_raw_to_frontend (dev, buf + buf_offset, &size);

      buf_offset += size;

      dev->bytes_left -= size;
      *len += size;
      if (dev->bytes_left < size)
	dev->bytes_left = 0;
      /* sheetfed scanner so end is detected instead of x and y */

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

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

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{

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

  handle = handle;		/* silence gcc */
  non_blocking = non_blocking;	/* silence gcc */


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

  return SANE_STATUS_UNSUPPORTED;
}

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

  handle = handle;		/* silence gcc */
  fd = fd;			/* silence gcc */

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

  return SANE_STATUS_UNSUPPORTED;
}

void
sane_cancel (SANE_Handle handle)
{
  Logitech_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)
{
  Logitech_Scanner *dev = handle;
  Logitech_Scanner *dev_tmp;

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

  do_cancel (dev);
  logitech_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;
	}
    }

  logitech_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");
}
