/* sane - Scanner Access Now Easy.
   Copyright (C) 2007 Jeremy Johnson
   This file is part of a SANE backend for Ricoh IS450
   and IS420 family of HS2P Scanners using the SCSI controller.

   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, see <https://www.gnu.org/licenses/>.

   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.

*/
/* SANE-FLOW-DIAGRAM

   - sane_init() : initialize backend, attach scanners
   . - sane_get_devices() : query list of scanner-devices
   . - sane_open() : open a particular scanner-device
   . . - attach : to the device
   . . . init_options : initialize SANE_OPTIONS array
   . . - 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_cancel() : cancel operation
   . - sane_close() : close opened scanner-device
   - sane_exit() : terminate use of backend
*/
#define BUILD 1

/* Begin includes */
#include "../include/sane/config.h"

#include <limits.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

#include "../include/sane/sane.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/sanei_thread.h"

#define BACKEND_NAME hs2p
#include "../include/sane/sanei_backend.h"

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif

#include "hs2p-scsi.c"

/* Begin macros */
#define MIN(x,y) ((x)<(y) ? (x) : (y))
#define MAX(x,y) ((x)>(y) ? (x) : (y))

/* Begin static constants */
static int num_devices = 0;
static HS2P_Device *first_dev = NULL;
static HS2P_Scanner *first_handle = NULL;

static SANE_Char inquiry_data[255] = "HS2P scanner";
/*
static SANE_Int disable_optional_frames = 0;
static SANE_Int fake_inquiry = 0;
*/

static HS2P_HWEntry HS2P_Device_List[] = {
  {"RICOH", "IS450"},
  {"RICOH", "IS430"},		/*untested */
  {"RICOH", "IS420"},		/*untested */
  {"RICOH", "IS01"},		/*untested */
  {"RICOH", "IS02"},		/*untested */
  {NULL, NULL}			/*sentinel */
};

#if 0
static int
allblank (const char *s)
{
  while (s && *s)
    if (!isspace (*s++))
      return 0;

  return 1;
}
#endif

static size_t
max_string_size (SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;
  DBG (DBG_proc, ">> max_string_size\n");

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

  DBG (DBG_proc, "<< max_string_size\n");
  return max_size;
}

static void
trim_spaces (char *s, size_t n)
{
  for (s += (n - 1); n > 0; n--, s--)
    {
      if (*s && !isspace (*s))
	break;
      *s = '\0';
    }
}
static SANE_Bool
is_device_supported (char *device)
{
  HS2P_HWEntry *hw;

  for (hw = &HS2P_Device_List[0]; hw->mfg != NULL; hw++)
    if (strncmp (device, hw->model, strlen (hw->model)) == 0)
      break;			/* found a match */

  return (hw == NULL) ? SANE_FALSE : SANE_TRUE;
}

static SANE_Int
get_list_index (const char *list[], char *s)	/* sequential search */
{
  SANE_Int i;

  for (i = 0; list[i]; i++)
    if (strcmp (s, list[i]) == 0)
      return i;			/* FOUND */

  /* unknown paper_list strings are treated as 'custom'     */
  /* unknown compression_list strings are treated as 'none' */
  /* unknown scan_source_list strings are treated as 'ADF'  */
  return 0;
}

static SANE_Int
get_val_id_strndx (struct val_id *vi, int len, SANE_Int val)
{
  int i;
  for (i = 0; i < len; i++)
    if (vi[i].val == val)
      return vi[i].id;		/* FOUND */
  return vi[0].id;		/* NOT FOUND so let's default to first */
}

static SANE_Status
init_options (HS2P_Scanner * s)
{
  SANE_Int i;
  DBG (DBG_proc, ">> init_options\n");

  memset (s->opt, 0, sizeof (s->opt));
  memset (s->val, 0, sizeof (s->val));

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

  s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;


  /*
   * "Scan Mode" GROUP:
   */
  s->opt[OPT_MODE_GROUP].name = "";
  s->opt[OPT_MODE_GROUP].title = SANE_TITLE_SCAN_MODE_GROUP;
  s->opt[OPT_MODE_GROUP].desc = "";
  s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_MODE_GROUP].cap = 0;
  s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Preview: */
  s->opt[OPT_PREVIEW].name = SANE_NAME_PREVIEW;
  s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  s->opt[OPT_PREVIEW].desc = SANE_DESC_PREVIEW;
  s->opt[OPT_PREVIEW].type = SANE_TYPE_BOOL;
  s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_PREVIEW].w = SANE_FALSE;

  /* Inquiry */
  s->opt[OPT_INQUIRY].name = SANE_NAME_INQUIRY;
  s->opt[OPT_INQUIRY].title = SANE_TITLE_INQUIRY;
  s->opt[OPT_INQUIRY].desc = SANE_DESC_INQUIRY;
  s->opt[OPT_INQUIRY].type = SANE_TYPE_STRING;
  s->opt[OPT_INQUIRY].size = sizeof (inquiry_data);
  s->opt[OPT_INQUIRY].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_INQUIRY].s = strdup (inquiry_data);
  s->opt[OPT_INQUIRY].cap = SANE_CAP_SOFT_DETECT;	/* Display Only */

  /* Scan mode */
  s->opt[OPT_SCAN_MODE].name = SANE_NAME_SCAN_MODE;
  s->opt[OPT_SCAN_MODE].title = SANE_TITLE_SCAN_MODE;
  s->opt[OPT_SCAN_MODE].desc = SANE_DESC_SCAN_MODE;
  s->opt[OPT_SCAN_MODE].type = SANE_TYPE_STRING;
  s->opt[OPT_SCAN_MODE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_SCAN_MODE].size =
    max_string_size ((SANE_String_Const *) scan_mode_list);
  s->opt[OPT_SCAN_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_SCAN_MODE].constraint.string_list =
    (SANE_String_Const *) & scan_mode_list[0];
  s->val[OPT_SCAN_MODE].s = strdup (scan_mode_list[0]);
  s->image_composition = LINEART;

  /* Standard resolutions */
  s->opt[OPT_RESOLUTION].name = "std-" SANE_NAME_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].title = "Std-" SANE_TITLE_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].desc = "Std " SANE_DESC_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->opt[OPT_RESOLUTION].constraint.word_list = s->hw->info.resStdList;
  s->val[OPT_RESOLUTION].w = s->hw->info.default_res;

  /* X Resolution */
  s->opt[OPT_X_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  s->opt[OPT_X_RESOLUTION].title = SANE_TITLE_SCAN_X_RESOLUTION;
  s->opt[OPT_X_RESOLUTION].desc = "X " SANE_DESC_SCAN_RESOLUTION;
  s->opt[OPT_X_RESOLUTION].type = SANE_TYPE_INT;
  s->opt[OPT_X_RESOLUTION].unit = SANE_UNIT_DPI;
  s->opt[OPT_X_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_X_RESOLUTION].constraint.range = &(s->hw->info.xres_range);
  s->val[OPT_X_RESOLUTION].w = s->hw->info.resBasicX;

  /* Y Resolution */
  s->opt[OPT_Y_RESOLUTION].name = SANE_NAME_SCAN_Y_RESOLUTION;
  s->opt[OPT_Y_RESOLUTION].title = SANE_TITLE_SCAN_Y_RESOLUTION;
  s->opt[OPT_Y_RESOLUTION].desc = "Y " SANE_DESC_SCAN_RESOLUTION;
  s->opt[OPT_Y_RESOLUTION].type = SANE_TYPE_INT;
  s->opt[OPT_Y_RESOLUTION].unit = SANE_UNIT_DPI;
  s->opt[OPT_Y_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_Y_RESOLUTION].constraint.range = &(s->hw->info.yres_range);
  s->val[OPT_Y_RESOLUTION].w = s->hw->info.resBasicY;

  /* Compression */
  s->opt[OPT_COMPRESSION].name = SANE_NAME_COMPRESSION;
  s->opt[OPT_COMPRESSION].title = SANE_TITLE_COMPRESSION;
  s->opt[OPT_COMPRESSION].desc = SANE_DESC_COMPRESSION;
  s->opt[OPT_COMPRESSION].type = SANE_TYPE_STRING;
  s->opt[OPT_COMPRESSION].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_COMPRESSION].size =
    max_string_size ((SANE_String_Const *) compression_list);
  s->opt[OPT_COMPRESSION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_COMPRESSION].constraint.string_list =
    (SANE_String_Const *) & compression_list[0];
  s->val[OPT_COMPRESSION].s = strdup (compression_list[0]);
  if (s->hw->info.supports_MH == SANE_FALSE ||	/* MH  G3 1-D       */
      s->hw->info.supports_MR == SANE_FALSE ||	/* MR  G3 2-D       */
      s->hw->info.supports_MMR == SANE_FALSE ||	/* MMR G4 2-D       */
      s->hw->info.supports_MHB == SANE_FALSE)	/* MH byte boundary */
    {
      s->opt[OPT_COMPRESSION].cap |= SANE_CAP_INACTIVE;
    }



  /*
   * "Geometry" GROUP:
   */
  s->opt[OPT_GEOMETRY_GROUP].name = "";
  s->opt[OPT_GEOMETRY_GROUP].title = SANE_TITLE_GEOMETRY_GROUP;
  s->opt[OPT_GEOMETRY_GROUP].desc = "";
  s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_GEOMETRY_GROUP].cap = 0;
  s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Auto Size Recognition available if IPU installed */
  s->opt[OPT_AUTO_SIZE].name = SANE_NAME_AUTO_SIZE;
  s->opt[OPT_AUTO_SIZE].title = SANE_TITLE_AUTO_SIZE;
  s->opt[OPT_AUTO_SIZE].desc = SANE_DESC_AUTO_SIZE;
  s->opt[OPT_AUTO_SIZE].type = SANE_TYPE_BOOL;
  s->opt[OPT_AUTO_SIZE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_AUTO_SIZE].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_AUTO_SIZE].w = SANE_FALSE;
  if (!s->hw->info.supports_sizerecognition)
    s->opt[OPT_AUTO_SIZE].cap |= SANE_CAP_INACTIVE;

  /* Pad short documents to requested length with white space */
  s->opt[OPT_PADDING].name = SANE_NAME_PADDING;
  s->opt[OPT_PADDING].title = SANE_TITLE_PADDING;
  s->opt[OPT_PADDING].desc = SANE_DESC_PADDING;
  s->opt[OPT_PADDING].type = SANE_TYPE_BOOL;
  s->opt[OPT_PADDING].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_PADDING].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_PADDING].w = SANE_TRUE;
  /*if (!s->hw->info.hasADF)
     s->opt[OPT_PADDING].cap |= SANE_CAP_INACTIVE;
     FIXME: compare to user setting, not the existence of FB?
     if (!strcmp (scan_source_list, "FB"))
     s->opt[OPT_PADDING].cap |= SANE_CAP_INACTIVE; */
  /* Permanently disable OPT_PADDING */
  s->opt[OPT_PADDING].cap |= SANE_CAP_INACTIVE;

  /* Paper Orientation */
  s->opt[OPT_PAGE_ORIENTATION].name = SANE_NAME_ORIENTATION;
  s->opt[OPT_PAGE_ORIENTATION].title = SANE_TITLE_ORIENTATION;
  s->opt[OPT_PAGE_ORIENTATION].desc = SANE_DESC_ORIENTATION;
  s->opt[OPT_PAGE_ORIENTATION].type = SANE_TYPE_STRING;
  s->opt[OPT_PAGE_ORIENTATION].size = max_string_size (orientation_list);
  s->opt[OPT_PAGE_ORIENTATION].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_PAGE_ORIENTATION].constraint.string_list = &orientation_list[0];
  s->val[OPT_PAGE_ORIENTATION].s = strdup (orientation_list[0]);

  /* Paper Size */
  s->opt[OPT_PAPER_SIZE].name = SANE_NAME_PAPER_SIZE;
  s->opt[OPT_PAPER_SIZE].title = SANE_TITLE_PAPER_SIZE;
  s->opt[OPT_PAPER_SIZE].desc = SANE_DESC_PAPER_SIZE;
  s->opt[OPT_PAPER_SIZE].type = SANE_TYPE_STRING;
  s->opt[OPT_PAPER_SIZE].size = max_string_size (paper_list);
  s->opt[OPT_PAPER_SIZE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_PAPER_SIZE].constraint.string_list = &paper_list[0];
  s->val[OPT_PAPER_SIZE].s = strdup (paper_list[0]);

  /* top-left x */
  s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
  s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_X].constraint.range = &(s->hw->info.x_range);
  s->val[OPT_TL_X].w = SANE_FIX (0.0);

  /* top-left y */
  s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
  s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_Y].constraint.range = &(s->hw->info.y_range);
  s->val[OPT_TL_Y].w = SANE_FIX (0.0);

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

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

  DBG (DBG_info, "INIT_OPTIONS: ul(x,y) = (%d,%d)    br(x,y) = (%d,%d)\n",
       (unsigned) SANE_UNFIX (s->val[OPT_TL_X].w),
       (unsigned) SANE_UNFIX (s->val[OPT_TL_Y].w),
       (unsigned) SANE_UNFIX (s->val[OPT_BR_X].w),
       (unsigned) SANE_UNFIX (s->val[OPT_BR_Y].w));
  /* Autoborder */
  /* Rotation   */
  /* Deskew     */



  /*
   * "Feeder" GROUP:
   */
  s->opt[OPT_FEEDER_GROUP].name = "";
  s->opt[OPT_FEEDER_GROUP].title = SANE_TITLE_FEEDER_GROUP;
  s->opt[OPT_FEEDER_GROUP].desc = "";
  s->opt[OPT_FEEDER_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_FEEDER_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_FEEDER_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Scan Source */
  s->opt[OPT_SCAN_SOURCE].name = SANE_NAME_SCAN_SOURCE;
  s->opt[OPT_SCAN_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
  s->opt[OPT_SCAN_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
  s->opt[OPT_SCAN_SOURCE].type = SANE_TYPE_STRING;
  s->opt[OPT_SCAN_SOURCE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_SCAN_SOURCE].size = max_string_size (scan_source_list);
  s->opt[OPT_SCAN_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_SCAN_SOURCE].constraint.string_list =
    (SANE_String_Const *) & scan_source_list[0];
  s->val[OPT_SCAN_SOURCE].s = strdup (scan_source_list[0]);
  if (!s->hw->info.hasADF)
    s->opt[OPT_SCAN_SOURCE].cap |= SANE_CAP_INACTIVE;

  /* Duplex: */
  s->opt[OPT_DUPLEX].name = SANE_NAME_DUPLEX;
  s->opt[OPT_DUPLEX].title = SANE_TITLE_DUPLEX;
  s->opt[OPT_DUPLEX].desc = SANE_DESC_DUPLEX;
  s->opt[OPT_DUPLEX].type = SANE_TYPE_BOOL;
  s->opt[OPT_DUPLEX].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_DUPLEX].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_DUPLEX].w = s->hw->info.default_duplex;
  if (!s->hw->info.hasDuplex)
    s->opt[OPT_DUPLEX].cap |= SANE_CAP_INACTIVE;

  /* Prefeed: */
  s->opt[OPT_PREFEED].name = SANE_NAME_PREFEED;
  s->opt[OPT_PREFEED].title = SANE_TITLE_PREFEED;
  s->opt[OPT_PREFEED].desc = SANE_DESC_PREFEED;
  s->opt[OPT_PREFEED].type = SANE_TYPE_BOOL;
  s->opt[OPT_PREFEED].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_PREFEED].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_PREFEED].w = SANE_FALSE;
  s->opt[OPT_PREFEED].cap |= SANE_CAP_INACTIVE;

  /* Endorser: */
  s->opt[OPT_ENDORSER].name = SANE_NAME_ENDORSER;
  s->opt[OPT_ENDORSER].title = SANE_TITLE_ENDORSER;
  s->opt[OPT_ENDORSER].desc = SANE_DESC_ENDORSER;
  s->opt[OPT_ENDORSER].type = SANE_TYPE_BOOL;
  s->opt[OPT_ENDORSER].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_ENDORSER].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_ENDORSER].w = s->hw->info.endorser_control;
  if (!s->hw->info.hasEndorser)
    s->opt[OPT_ENDORSER].cap |= SANE_CAP_INACTIVE;

  /* Endorser String: */
  s->opt[OPT_ENDORSER_STRING].name = SANE_NAME_ENDORSER_STRING;
  s->opt[OPT_ENDORSER_STRING].title = SANE_TITLE_ENDORSER_STRING;
  s->opt[OPT_ENDORSER_STRING].desc = SANE_DESC_ENDORSER_STRING;
  s->opt[OPT_ENDORSER_STRING].type = SANE_TYPE_STRING;
  s->opt[OPT_ENDORSER_STRING].cap =
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_ENDORSER_STRING].size = sizeof (s->hw->info.endorser_string);
  s->opt[OPT_ENDORSER_STRING].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_ENDORSER_STRING].s = strdup (s->hw->info.endorser_string);
  if (!s->hw->info.hasEndorser)
    s->opt[OPT_ENDORSER_STRING].cap |= SANE_CAP_INACTIVE;

  /* Batch     */
  /* Check ADF */
  /* timeout ADF */
  /* timeout Manual */

  /*
   * "Enhancement" GROUP:
   */
  s->opt[OPT_ENHANCEMENT_GROUP].name = "";
  s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_TITLE_ENHANCEMENT_GROUP;
  s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
  s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_ENHANCEMENT_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Halftone Type */
  s->opt[OPT_HALFTONE_CODE].name = SANE_NAME_HALFTONE_CODE;
  s->opt[OPT_HALFTONE_CODE].title = SANE_TITLE_HALFTONE_CODE;
  s->opt[OPT_HALFTONE_CODE].desc = SANE_DESC_HALFTONE_CODE;
  s->opt[OPT_HALFTONE_CODE].type = SANE_TYPE_STRING;
  s->opt[OPT_HALFTONE_CODE].size = max_string_size (halftone_code);
  s->opt[OPT_HALFTONE_CODE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_HALFTONE_CODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_HALFTONE_CODE].constraint.string_list =
    (SANE_String_Const *) & halftone_code[0];
  s->val[OPT_HALFTONE_CODE].s = strdup (halftone_code[0]);
  if (s->image_composition == LINEART)
    s->opt[OPT_HALFTONE_CODE].cap |= SANE_CAP_INACTIVE;

  /* Halftone patterns */
  s->opt[OPT_HALFTONE_PATTERN].name = SANE_NAME_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].desc = SANE_DESC_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].type = SANE_TYPE_STRING;
  s->opt[OPT_HALFTONE_PATTERN].size =
    max_string_size ((SANE_String_Const *) halftone_pattern_list);
  s->opt[OPT_HALFTONE_PATTERN].cap =
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_HALFTONE_PATTERN].constraint.string_list =
    (SANE_String_Const *) & halftone_pattern_list[0];
  s->val[OPT_HALFTONE_PATTERN].s = strdup (halftone_pattern_list[0]);
  if (s->image_composition == LINEART)
    s->opt[OPT_HALFTONE_CODE].cap |= SANE_CAP_INACTIVE;

  /* Gray Filter */
  s->opt[OPT_GRAYFILTER].name = SANE_NAME_GRAYFILTER;
  s->opt[OPT_GRAYFILTER].title = SANE_TITLE_GRAYFILTER;
  s->opt[OPT_GRAYFILTER].desc = SANE_DESC_GRAYFILTER;
  s->opt[OPT_GRAYFILTER].type = SANE_TYPE_STRING;
  s->opt[OPT_GRAYFILTER].size = max_string_size (grayfilter_list);
  s->opt[OPT_GRAYFILTER].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_GRAYFILTER].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_GRAYFILTER].constraint.string_list =
    (SANE_String_Const *) & grayfilter_list[0];
  s->val[OPT_GRAYFILTER].s = strdup (grayfilter_list[0]);

  /* Scan Wait Mode */
  s->opt[OPT_SCAN_WAIT_MODE].name = SANE_NAME_SCAN_WAIT_MODE;
  s->opt[OPT_SCAN_WAIT_MODE].title = SANE_TITLE_SCAN_WAIT_MODE;
  s->opt[OPT_SCAN_WAIT_MODE].desc = SANE_DESC_SCAN_WAIT_MODE;
  s->opt[OPT_SCAN_WAIT_MODE].type = SANE_TYPE_BOOL;
  s->opt[OPT_SCAN_WAIT_MODE].unit = SANE_UNIT_NONE;
  s->val[OPT_SCAN_WAIT_MODE].w =
    (s->hw->info.scan_wait_mode) ? SANE_TRUE : SANE_FALSE;

  /* Brightness */
  s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  s->opt[OPT_BRIGHTNESS].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS].constraint.range = &s->hw->info.brightness_range;
  s->val[OPT_BRIGHTNESS].w = 128;

  /* Threshold */
  s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
  s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
  s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
  s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
  s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_THRESHOLD].constraint.range = &s->hw->info.threshold_range;
  s->val[OPT_THRESHOLD].w = 128;

  /* Contrast */
  s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  s->opt[OPT_CONTRAST].type = SANE_TYPE_INT;
  s->opt[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  s->opt[OPT_CONTRAST].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST].constraint.range = &s->hw->info.contrast_range;
  s->val[OPT_CONTRAST].w = 128;

  /* Gamma */
  s->opt[OPT_GAMMA].name = SANE_NAME_GAMMA;
  s->opt[OPT_GAMMA].title = SANE_TITLE_GAMMA;
  s->opt[OPT_GAMMA].desc = SANE_DESC_GAMMA;
  s->opt[OPT_GAMMA].type = SANE_TYPE_STRING;
  s->opt[OPT_GAMMA].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_GAMMA].size = max_string_size ((SANE_String_Const *) gamma_list);
  /*
     s->opt[OPT_GAMMA].type = SANE_TYPE_INT;
     s->opt[OPT_GAMMA].unit = SANE_UNIT_NONE;
     s->opt[OPT_GAMMA].constraint_type = SANE_CONSTRAINT_RANGE;
     s->opt[OPT_GAMMA].constraint.range = &u8_range;
     s->val[OPT_GAMMA].w = 0;
   */
  s->opt[OPT_GAMMA].type = SANE_TYPE_STRING;
  s->opt[OPT_GAMMA].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_GAMMA].constraint.string_list =
    (SANE_String_Const *) & gamma_list[0];
  s->val[OPT_GAMMA].s = strdup (gamma_list[0]);

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

  /* grayscale gamma vector */
  s->opt[OPT_GAMMA_VECTOR_GRAY].name = SANE_NAME_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR_GRAY].title = SANE_TITLE_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR_GRAY].desc = SANE_DESC_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR_GRAY].type = SANE_TYPE_INT;
  s->opt[OPT_GAMMA_VECTOR_GRAY].cap |=
    SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_GAMMA_VECTOR_GRAY].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_GRAY].size = GAMMA_LENGTH * sizeof (SANE_Word);
  s->opt[OPT_GAMMA_VECTOR_GRAY].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_GRAY].constraint.range = &u8_range;
  s->val[OPT_GAMMA_VECTOR_GRAY].wa = s->gamma_table;
  s->opt[OPT_GAMMA_VECTOR_GRAY].cap |= SANE_CAP_INACTIVE;


  /* Control Panel */
  /* ACE Function  */
  /* ACE Sensitivity */

  /* Binary Smoothing Filter */
  s->opt[OPT_SMOOTHING].name = SANE_NAME_SMOOTHING;
  s->opt[OPT_SMOOTHING].title = SANE_TITLE_SMOOTHING;
  s->opt[OPT_SMOOTHING].desc = SANE_DESC_SMOOTHING;
  s->opt[OPT_SMOOTHING].type = SANE_TYPE_BOOL;
  s->opt[OPT_SMOOTHING].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_SMOOTHING].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_SMOOTHING].w = SANE_FALSE;
  if (!s->hw->info.hasIPU)
    s->opt[OPT_SMOOTHING].cap |= SANE_CAP_INACTIVE;

  /* Binary Noise Removal Filter */
  s->opt[OPT_NOISEREMOVAL].name = SANE_NAME_NOISEREMOVAL;
  s->opt[OPT_NOISEREMOVAL].title = SANE_TITLE_NOISEREMOVAL;
  s->opt[OPT_NOISEREMOVAL].desc = SANE_DESC_NOISEREMOVAL;
  s->opt[OPT_NOISEREMOVAL].type = SANE_TYPE_STRING;
  s->opt[OPT_NOISEREMOVAL].size = max_string_size (noisematrix_list);
  s->opt[OPT_NOISEREMOVAL].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NOISEREMOVAL].constraint.string_list =
    (SANE_String_Const *) & noisematrix_list[0];
  s->val[OPT_NOISEREMOVAL].s = strdup (noisematrix_list[0]);
  if (!s->hw->info.hasIPU)
    s->opt[OPT_NOISEREMOVAL].cap |= SANE_CAP_INACTIVE;

  /* Automatic Separation */
  s->opt[OPT_AUTOSEP].name = SANE_NAME_AUTOSEP;
  s->opt[OPT_AUTOSEP].title = SANE_TITLE_AUTOSEP;
  s->opt[OPT_AUTOSEP].desc = SANE_DESC_AUTOSEP;
  s->opt[OPT_AUTOSEP].type = SANE_TYPE_STRING;
  s->opt[OPT_AUTOSEP].size = max_string_size (auto_separation_list);
  s->opt[OPT_AUTOSEP].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_AUTOSEP].constraint.string_list =
    (SANE_String_Const *) & auto_separation_list[0];
  s->val[OPT_AUTOSEP].s = strdup (auto_separation_list[0]);
  if (!s->hw->info.hasIPU)
    s->opt[OPT_AUTOSEP].cap |= SANE_CAP_INACTIVE;

  /* Automatic Binarization */
  s->opt[OPT_AUTOBIN].name = SANE_NAME_AUTOBIN;
  s->opt[OPT_AUTOBIN].title = SANE_TITLE_AUTOBIN;
  s->opt[OPT_AUTOBIN].desc = SANE_DESC_AUTOBIN;
  s->opt[OPT_AUTOBIN].type = SANE_TYPE_STRING;
  s->opt[OPT_AUTOBIN].size = max_string_size (auto_binarization_list);
  s->opt[OPT_AUTOBIN].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_AUTOBIN].constraint.string_list =
    (SANE_String_Const *) & auto_binarization_list[0];
  s->val[OPT_AUTOBIN].s = strdup (auto_binarization_list[0]);
  if (!s->hw->info.hasIPU)
    s->opt[OPT_AUTOBIN].cap |= SANE_CAP_INACTIVE;

  /* SECTION
   * The IS450 supports up to 4 Section; The IS420 supports up to 6 Sections
   * For each struct window_section[i] we need to fill in ulx,uly,width,height,etc
   * NOT YET IMPLEMENTED
   */

  /* Negative */
  s->opt[OPT_NEGATIVE].name = SANE_NAME_NEGATIVE;
  s->opt[OPT_NEGATIVE].title = SANE_TITLE_NEGATIVE;
  s->opt[OPT_NEGATIVE].desc = SANE_DESC_NEGATIVE;
  s->opt[OPT_NEGATIVE].type = SANE_TYPE_BOOL;
  s->opt[OPT_NEGATIVE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NEGATIVE].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_NEGATIVE].w = SANE_FALSE;

  /* White Balance */
  s->opt[OPT_WHITE_BALANCE].name = SANE_NAME_WHITE_BALANCE;
  s->opt[OPT_WHITE_BALANCE].title = SANE_TITLE_WHITE_BALANCE;
  s->opt[OPT_WHITE_BALANCE].desc = SANE_DESC_WHITE_BALANCE;
  s->opt[OPT_WHITE_BALANCE].type = SANE_TYPE_BOOL;
  s->opt[OPT_WHITE_BALANCE].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  s->opt[OPT_WHITE_BALANCE].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_WHITE_BALANCE].w = SANE_FALSE;	/* F/T = Relative/Absolute White */

  /*
   * "Miscellaneous" GROUP:
   */
  s->opt[OPT_MISCELLANEOUS_GROUP].name = "";
  s->opt[OPT_MISCELLANEOUS_GROUP].title = SANE_TITLE_MISCELLANEOUS_GROUP;
  s->opt[OPT_MISCELLANEOUS_GROUP].desc = "";
  s->opt[OPT_MISCELLANEOUS_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_MISCELLANEOUS_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_MISCELLANEOUS_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* Padding Type: */
  s->opt[OPT_PADDING_TYPE].name = SANE_NAME_PADDING_TYPE;
  s->opt[OPT_PADDING_TYPE].title = SANE_TITLE_PADDING_TYPE;
  s->opt[OPT_PADDING_TYPE].desc = SANE_DESC_PADDING_TYPE;
  s->opt[OPT_PADDING_TYPE].type = SANE_TYPE_STRING;
  s->opt[OPT_PADDING_TYPE].cap = SANE_CAP_SOFT_DETECT;	/* Display only */
  s->opt[OPT_PADDING_TYPE].size = max_string_size (paddingtype_list);
  /*
     s->opt[OPT_PADDING_TYPE].size = sizeof((paddingtype_list[ get_paddingtype_strndx(TRUNCATE) ]));
     s->opt[OPT_PADDING_TYPE].constraint_type = SANE_CONSTRAINT_NONE;
   */
  s->opt[OPT_PADDING_TYPE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_PADDING_TYPE].constraint.string_list =
    (SANE_String_Const *) & paddingtype_list[0];
  s->val[OPT_PADDING_TYPE].s =
    strdup (paddingtype_list[get_paddingtype_strndx (TRUNCATE)]);
  DBG (DBG_info, "PADDINGTYPE =%s size=%d\n", s->val[OPT_PADDING_TYPE].s,
       s->opt[OPT_PADDING_TYPE].size);

  /* Bit Order
     s->opt[OPT_BITORDER].name = SANE_NAME_BITORDER;
     s->opt[OPT_BITORDER].title = SANE_TITLE_BITORDER;
     s->opt[OPT_BITORDER].desc = SANE_DESC_BITORDER;
     s->opt[OPT_BITORDER].type = SANE_TYPE_WORD;
     s->opt[OPT_BITORDER].cap = SANE_CAP_SOFT_DETECT;
     s->opt[OPT_BITORDER].constraint_type = SANE_CONSTRAINT_NONE
     s->val[OPT_BITORDER].w =  0x7;
   */

  /* Self Diagnostics */
  s->opt[OPT_SELF_DIAGNOSTICS].name = SANE_NAME_SELF_DIAGNOSTICS;
  s->opt[OPT_SELF_DIAGNOSTICS].title = SANE_TITLE_SELF_DIAGNOSTICS;
  s->opt[OPT_SELF_DIAGNOSTICS].desc = SANE_DESC_SELF_DIAGNOSTICS;
  s->opt[OPT_SELF_DIAGNOSTICS].type = SANE_TYPE_BUTTON;

  /* Optical Diagnostics */
  s->opt[OPT_OPTICAL_ADJUSTMENT].name = SANE_NAME_OPTICAL_ADJUSTMENT;
  s->opt[OPT_OPTICAL_ADJUSTMENT].title = SANE_TITLE_OPTICAL_ADJUSTMENT;
  s->opt[OPT_OPTICAL_ADJUSTMENT].desc = SANE_DESC_OPTICAL_ADJUSTMENT;
  s->opt[OPT_OPTICAL_ADJUSTMENT].type = SANE_TYPE_BUTTON;

  /* MAINTENANCE DATA */
  s->opt[OPT_DATA_GROUP].name = "";
  s->opt[OPT_DATA_GROUP].title = "Maintenance Data";
  s->opt[OPT_DATA_GROUP].desc = "";
  s->opt[OPT_DATA_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_DATA_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_DATA_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_UPDATE].name = "Update";
  s->opt[OPT_UPDATE].title = "Update";
  s->opt[OPT_UPDATE].desc = "Update scanner data";
  s->opt[OPT_UPDATE].type = SANE_TYPE_BUTTON;
  s->opt[OPT_NREGX_ADF].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGX_ADF].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGX_ADF].name = "# registers in main-scanning in ADF mode";
  s->opt[OPT_NREGX_ADF].title = "# registers in main-scanning in ADF mode";
  s->opt[OPT_NREGX_ADF].desc = "# registers in main-scanning in ADF mode";
  s->opt[OPT_NREGX_ADF].type = SANE_TYPE_INT;
  s->opt[OPT_NREGX_ADF].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGX_ADF].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGX_ADF].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGY_ADF].name = "# registers in sub-scanning in ADF mode";
  s->opt[OPT_NREGY_ADF].title = "# registers in sub-scanning in ADF mode";
  s->opt[OPT_NREGY_ADF].desc = "# registers in sub-scanning in ADF mode";
  s->opt[OPT_NREGY_ADF].type = SANE_TYPE_INT;
  s->opt[OPT_NREGY_ADF].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGY_ADF].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGY_ADF].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGX_BOOK].name = "# registers in main-scanning in book mode";
  s->opt[OPT_NREGX_BOOK].title = "# registers in main-scanning in book mode";
  s->opt[OPT_NREGX_BOOK].desc = "# registers in main-scanning in book mode";
  s->opt[OPT_NREGX_BOOK].type = SANE_TYPE_INT;
  s->opt[OPT_NREGX_BOOK].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGX_BOOK].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGX_BOOK].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGY_BOOK].name = "# registers in sub-scanning in book mode";
  s->opt[OPT_NREGY_BOOK].title = "# registers in sub-scanning in book mode";
  s->opt[OPT_NREGY_BOOK].desc = "# registers in sub-scanning in book mode";
  s->opt[OPT_NREGY_BOOK].type = SANE_TYPE_INT;
  s->opt[OPT_NREGY_BOOK].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGY_BOOK].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGY_BOOK].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NSCANS_ADF].name = "# ADF Scans";
  s->opt[OPT_NSCANS_ADF].title = "# ADF Scans";
  s->opt[OPT_NSCANS_ADF].desc = "# ADF Scans";
  s->opt[OPT_NSCANS_ADF].type = SANE_TYPE_INT;
  s->opt[OPT_NSCANS_ADF].unit = SANE_UNIT_NONE;
  s->opt[OPT_NSCANS_ADF].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NSCANS_ADF].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NSCANS_BOOK].name = "# BOOK Scans";
  s->opt[OPT_NSCANS_BOOK].title = "# BOOK Scans";
  s->opt[OPT_NSCANS_BOOK].desc = "# BOOK Scans";
  s->opt[OPT_NSCANS_BOOK].type = SANE_TYPE_INT;
  s->opt[OPT_NSCANS_BOOK].unit = SANE_UNIT_NONE;
  s->opt[OPT_NSCANS_BOOK].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NSCANS_BOOK].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_LAMP_TIME].name = "LAMP TIME";
  s->opt[OPT_LAMP_TIME].title = "LAMP TIME";
  s->opt[OPT_LAMP_TIME].desc = "LAMP TIME";
  s->opt[OPT_LAMP_TIME].type = SANE_TYPE_INT;
  s->opt[OPT_LAMP_TIME].unit = SANE_UNIT_NONE;
  s->opt[OPT_LAMP_TIME].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_LAMP_TIME].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_EO_ODD].name = "E/O Balance ODD";
  s->opt[OPT_EO_ODD].title = "E/O Balance ODD";
  s->opt[OPT_EO_ODD].desc = "Adj. of E/O Balance in black level ODD";
  s->opt[OPT_EO_ODD].type = SANE_TYPE_INT;
  s->opt[OPT_EO_ODD].unit = SANE_UNIT_NONE;
  s->opt[OPT_EO_ODD].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_EO_ODD].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_EO_EVEN].name = "E/O Balance EVEN";
  s->opt[OPT_EO_EVEN].title = "E/O Balance EVEN";
  s->opt[OPT_EO_EVEN].desc = "Adj. of E/O Balance in black level EVEN";
  s->opt[OPT_EO_EVEN].type = SANE_TYPE_INT;
  s->opt[OPT_EO_EVEN].unit = SANE_UNIT_NONE;
  s->opt[OPT_EO_EVEN].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_EO_EVEN].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_BLACK_LEVEL_ODD].name = "Black Level ODD";
  s->opt[OPT_BLACK_LEVEL_ODD].title = "Black Level ODD";
  s->opt[OPT_BLACK_LEVEL_ODD].desc = "Adj. data in black level (ODD)";
  s->opt[OPT_BLACK_LEVEL_ODD].type = SANE_TYPE_INT;
  s->opt[OPT_BLACK_LEVEL_ODD].unit = SANE_UNIT_NONE;
  s->opt[OPT_BLACK_LEVEL_ODD].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_BLACK_LEVEL_ODD].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_BLACK_LEVEL_EVEN].name = "Black Level EVEN";
  s->opt[OPT_BLACK_LEVEL_EVEN].title = "Black Level EVEN";
  s->opt[OPT_BLACK_LEVEL_EVEN].desc = "Adj. data in black level (EVEN)";
  s->opt[OPT_BLACK_LEVEL_EVEN].type = SANE_TYPE_INT;
  s->opt[OPT_BLACK_LEVEL_EVEN].unit = SANE_UNIT_NONE;
  s->opt[OPT_BLACK_LEVEL_EVEN].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_BLACK_LEVEL_EVEN].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_WHITE_LEVEL_ODD].name = "White Level ODD";
  s->opt[OPT_WHITE_LEVEL_ODD].title = "White Level ODD";
  s->opt[OPT_WHITE_LEVEL_ODD].desc = "Adj. data in White level (ODD)";
  s->opt[OPT_WHITE_LEVEL_ODD].type = SANE_TYPE_INT;
  s->opt[OPT_WHITE_LEVEL_ODD].unit = SANE_UNIT_NONE;
  s->opt[OPT_WHITE_LEVEL_ODD].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_WHITE_LEVEL_ODD].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_WHITE_LEVEL_EVEN].name = "White Level EVEN";
  s->opt[OPT_WHITE_LEVEL_EVEN].title = "White Level EVEN";
  s->opt[OPT_WHITE_LEVEL_EVEN].desc = "Adj. data in White level (EVEN)";
  s->opt[OPT_WHITE_LEVEL_EVEN].type = SANE_TYPE_INT;
  s->opt[OPT_WHITE_LEVEL_EVEN].unit = SANE_UNIT_NONE;
  s->opt[OPT_WHITE_LEVEL_EVEN].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_WHITE_LEVEL_EVEN].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_WHITE_LEVEL_EVEN].name = "White Level EVEN";
  s->opt[OPT_WHITE_LEVEL_EVEN].title = "White Level EVEN";
  s->opt[OPT_WHITE_LEVEL_EVEN].desc = "Adj. data in White level (EVEN)";
  s->opt[OPT_WHITE_LEVEL_EVEN].type = SANE_TYPE_INT;
  s->opt[OPT_WHITE_LEVEL_EVEN].unit = SANE_UNIT_NONE;
  s->opt[OPT_WHITE_LEVEL_EVEN].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_WHITE_LEVEL_EVEN].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_DENSITY].name = "Density Adjustment";
  s->opt[OPT_DENSITY].title = "Density Adjustment";
  s->opt[OPT_DENSITY].desc = "Density adjustment of std. white board";
  s->opt[OPT_DENSITY].type = SANE_TYPE_INT;
  s->opt[OPT_DENSITY].unit = SANE_UNIT_NONE;
  s->opt[OPT_DENSITY].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_DENSITY].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_FIRST_ADJ_WHITE_ODD].name = "1st adj. in white level (ODD)";
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].title = "1st adj. in white level (ODD)";
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].desc = "1st adj. in white level (ODD)";
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].type = SANE_TYPE_INT;
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].unit = SANE_UNIT_NONE;
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_FIRST_ADJ_WHITE_ODD].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].name = "1st adj. in white level (EVEN)";
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].title = "1st adj. in white level (EVEN)";
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].desc = "1st adj. in white level (EVEN)";
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].type = SANE_TYPE_INT;
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].unit = SANE_UNIT_NONE;
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_FIRST_ADJ_WHITE_EVEN].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGX_REVERSE].name = "# registers of main-scanning of backside";
  s->opt[OPT_NREGX_REVERSE].title =
    "# registers of main-scanning of backside";
  s->opt[OPT_NREGX_REVERSE].desc =
    "# registers of main-scanning of ADF backside";
  s->opt[OPT_NREGX_REVERSE].type = SANE_TYPE_INT;
  s->opt[OPT_NREGX_REVERSE].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGX_REVERSE].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGX_REVERSE].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NREGY_REVERSE].name = "# registers of sub-scanning of backside";
  s->opt[OPT_NREGY_REVERSE].title = "# registers of sub-scanning of backside";
  s->opt[OPT_NREGY_REVERSE].desc =
    "# registers of sub-scanning of ADF backside";
  s->opt[OPT_NREGY_REVERSE].type = SANE_TYPE_INT;
  s->opt[OPT_NREGY_REVERSE].unit = SANE_UNIT_NONE;
  s->opt[OPT_NREGY_REVERSE].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NREGY_REVERSE].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NSCANS_REVERSE_ADF].name = "# of scans of reverse side in ADF";
  s->opt[OPT_NSCANS_REVERSE_ADF].title = "# of scans of reverse side in ADF";
  s->opt[OPT_NSCANS_REVERSE_ADF].desc = "# of scans of reverse side in ADF";
  s->opt[OPT_NSCANS_REVERSE_ADF].type = SANE_TYPE_INT;
  s->opt[OPT_NSCANS_REVERSE_ADF].unit = SANE_UNIT_NONE;
  s->opt[OPT_NSCANS_REVERSE_ADF].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NSCANS_REVERSE_ADF].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_REVERSE_TIME].name = "LAMP TIME (reverse)";
  s->opt[OPT_REVERSE_TIME].title = "LAMP TIME (reverse)";
  s->opt[OPT_REVERSE_TIME].desc = "LAMP TIME (reverse)";
  s->opt[OPT_REVERSE_TIME].type = SANE_TYPE_INT;
  s->opt[OPT_REVERSE_TIME].unit = SANE_UNIT_NONE;
  s->opt[OPT_REVERSE_TIME].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_REVERSE_TIME].constraint_type = SANE_CONSTRAINT_NONE;

  s->opt[OPT_NCHARS].name = "# of endorser characters";
  s->opt[OPT_NCHARS].title = "# of endorser characters";
  s->opt[OPT_NCHARS].desc = "# of endorser characters";
  s->opt[OPT_NCHARS].type = SANE_TYPE_INT;
  s->opt[OPT_NCHARS].unit = SANE_UNIT_NONE;
  s->opt[OPT_NCHARS].cap = SANE_CAP_SOFT_DETECT;
  s->opt[OPT_NCHARS].constraint_type = SANE_CONSTRAINT_NONE;

  DBG (DBG_proc, "<< init_options\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
attach (SANE_String_Const devname, int __sane_unused__ connType,
	HS2P_Device ** devp)
{
  SANE_Status status;
  HS2P_Device *dev;
  struct inquiry_standard_data ibuf;
  struct inquiry_vpd_data vbuf;
  struct inquiry_jis_data jbuf;
  size_t buf_size;
  int fd = -1;
  double mm;

  char device_string[60];

  unsigned int i;
  SANE_String *str;


  DBG (DBG_sane_proc, ">>> attach:\n");

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devname) == 0)
	{
	  if (devp)
	    *devp = dev;
	  return SANE_STATUS_GOOD;
	}
    }
  DBG (DBG_sane_proc, ">>> attach: opening \"%s\"\n", devname);

  /* sanei_scsi_open takes an option bufsize argument */
  status = sanei_scsi_open (devname, &fd, &sense_handler, &(dev->sense_data));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: open failed: %s\n",
	   sane_strstatus (status));
      return (status);
    }

  DBG (DBG_sane_proc, ">>> attach: opened %s fd=%d\n", devname, fd);

  DBG (DBG_sane_proc, ">>> attach: sending INQUIRY (standard data)\n");
  memset (&ibuf, 0, sizeof (ibuf));
  buf_size = sizeof (ibuf);
  status = inquiry (fd, &ibuf, &buf_size, 0, HS2P_INQUIRY_STANDARD_PAGE_CODE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: inquiry failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (fd);
      return (status);
    }

  DBG (DBG_info,
       ">>> attach: reported devtype='%d', vendor='%.8s', product='%.16s', revision='%.4s'\n",
       ibuf.devtype, ibuf.vendor, ibuf.product, ibuf.revision);
  DBG (DBG_info,
       ">>> attach: reported RMB=%#x Ver=%#x ResponseDataFormat=%#x Length=%#x Byte7=%#x\n",
       ibuf.rmb_evpd, ibuf.version, ibuf.response_data_format, ibuf.length,
       ibuf.byte7);

  if (ibuf.devtype != 6 || strncmp ((char *) ibuf.vendor, "RICOH   ", 8) != 0)
    {
      DBG (DBG_warning, ">>> attach: device is not a RICOH scanner\n");
      sanei_scsi_close (fd);
      return SANE_STATUS_INVAL;
    }
  else if (!is_device_supported ((char *) ibuf.product))
    {
      DBG (DBG_warning,
	   ">>> attach: device %s is not yet a supported RICOH scanner\n",
	   ibuf.product);
      sanei_scsi_close (fd);
      return SANE_STATUS_INVAL;
    }

  /* We should now have an open file descriptor to a supported hs2p scanner */
  DBG (DBG_sane_proc, ">>> attach: sending TEST_UNIT_READY\n");
  do
    {
      status = test_unit_ready (fd);
    }
  while (status == HS2P_SK_UNIT_ATTENTION);
  if (status != HS2P_SCSI_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: test unit ready failed (%s)\n",
	   sane_strstatus (status));
      sanei_scsi_close (fd);
      return (status);
    }

  DBG (DBG_sane_proc, ">>> attach: sending INQUIRY (vpd data)\n");
  memset (&vbuf, 0, sizeof (vbuf));
  buf_size = sizeof (vbuf);
  status = inquiry (fd, &vbuf, &buf_size, 1, HS2P_INQUIRY_VPD_PAGE_CODE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: inquiry (vpd data) failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (fd);
      return status;
    }
  print_vpd_info (&vbuf);

  DBG (DBG_sane_proc, ">>> attach: sending INQUIRY (jis data)\n");
  memset (&jbuf, 0, sizeof (jbuf));
  buf_size = sizeof (jbuf);
  status = inquiry (fd, &jbuf, &buf_size, 1, HS2P_INQUIRY_JIS_PAGE_CODE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: inquiry (jis data) failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (fd);
      return status;
    }
  print_jis_info (&jbuf);


  /* Fill in HS2P_Device {sane;info} */
  dev = malloc (sizeof (*dev));
  if (!dev)
    return SANE_STATUS_NO_MEM;
  memset (dev, 0, sizeof (*dev));

  /* Maximum Number of Sub-Sections of Main Scanning Window */
  if (strncmp ((char *) ibuf.product, "IS450", 5) == 0)
    {
      dev->info.max_win_sections = 4;
    }
  else if (strncmp ((char *) ibuf.product, "IS420", 5) == 0)
    {
      dev->info.max_win_sections = 6;
    }

  /* Some MODE SELECT scanner options */
  DBG (DBG_proc, ">>> attach: get_basic_measurement_unit\n");
  status =
    get_basic_measurement_unit (fd, &(dev->info.bmu), &(dev->info.mud));
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: get_basic_measurement_unit failed (%s)\n",
	   sane_strstatus (status));
      DBG (DBG_error, ">>> attach: setting to defaults\n");
      status = set_basic_measurement_unit (fd, MILLIMETERS);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error,
	       ">>> attach: set_basic_measurement_unit failed (%s)\n",
	       sane_strstatus (status));
	}
    }
  if ((status =
       get_connection_parameters (fd, &(dev->info.cxn))) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, ">>> attach: get_connection_parameters failed\n");
    }
  status = get_endorser_control (fd, &dev->info.endorser_control);
  if (status != SANE_STATUS_GOOD || dev->info.endorser_control != 0x01)
    {
      DBG (DBG_error,
	   ">>> attach: get_endorser_control failed: return value=%#02x\n",
	   dev->info.endorser_control);
      dev->info.endorser_control = 0x00;
    }
  if ((dev->info.service_mode = get_service_mode (fd)) != 0x00
      && dev->info.service_mode != 0x01)
    {
      DBG (DBG_error, ">>> attach: get_service_mode failed %#02x\n",
	   dev->info.service_mode);
      dev->info.service_mode = 0x00;
    }
  if ((dev->info.scan_wait_mode = get_scan_wait_mode (fd)) != 0x00
      && dev->info.scan_wait_mode != 0x01)
    {
      DBG (DBG_error,
	   ">>> attach: get_scan_wait_mode failed: return value=%#02x\n",
	   dev->info.scan_wait_mode);
      dev->info.scan_wait_mode = 0x00;
    }
  status = get_white_balance (fd, &dev->info.white_balance);
  if (status != SANE_STATUS_GOOD && dev->info.white_balance != 0x01)
    {
      DBG (DBG_error,
	   ">>> attach: get_white_balance failed: return value=%#02x\n",
	   dev->info.white_balance);
      dev->info.white_balance = RELATIVE_WHITE;
    }

  DBG (DBG_info, ">>> attach: flushing and closing fd=%d\n", fd);
  sanei_scsi_req_flush_all ();
  sanei_scsi_close (fd);

  dev->info.devtype = ibuf.devtype;
  snprintf (dev->info.vendor, 9, "%-.5s", ibuf.vendor);	/* RICOH */
  trim_spaces (dev->info.vendor, sizeof (dev->info.vendor));
  snprintf (dev->info.product, 16, "%-.16s", ibuf.product);	/* IS450 */
  trim_spaces (dev->info.product, sizeof (dev->info.product));
  snprintf (dev->info.revision, 5, "%-.4s", ibuf.revision);	/* 1R04 */
  trim_spaces (dev->info.revision, sizeof (dev->info.revision));

  /* SANE_Device sane information */
  dev->sane.name = strdup (devname);
  dev->sane.vendor =
    (strcmp (dev->info.vendor, "RICOH") ==
     0) ? strdup ("Ricoh") : strdup (dev->info.vendor);
  dev->sane.model = strdup (dev->info.product);
  dev->sane.type = strdup ("sheetfed scanner");
  /*
     dev->sane.email_backend_author = strdup("<Jeremy Johnson> jeremy@acjlaw.net");
     dev->sane.backend_website = strdup("http://www.acjlaw.net:8080/~jeremy/Ricoh");
   */
  /* read these values from backend configuration file using parse_configuration
     dev->sane.location = strdup();
     dev->sane.comment = strdup();
     dev->sane.backend_version_code = strdup();
   */
  /* NOT YET USED */
  /* dev->sane.backend_capablity_flags = 0x00; */

  /* set capabilities from vpd */
  /* adf_id: 0=none,1=simplex,2=duplex,3=ARDF,4=reserved;  should be 1 or 2 for IS450 family */
  dev->info.hasADF = vbuf.adf_id == 0 ? SANE_FALSE : SANE_TRUE;
  dev->info.hasSimplex = vbuf.adf_id == 1 ? SANE_TRUE : SANE_FALSE;
  dev->info.hasDuplex = vbuf.adf_id == 2 ? SANE_TRUE : SANE_FALSE;
  dev->info.hasARDF = vbuf.adf_id == 3 ? SANE_TRUE : SANE_FALSE;

  /* end_id 0=none,1=Yes,2=reserved;  should always be 0 or 1 */
  dev->info.hasEndorser = vbuf.end_id == 1 ? SANE_TRUE : SANE_FALSE;
  for (i = 0; i < 20; i++)
    dev->info.endorser_string[i] = '\0';

  /* ipu_id: Bit0: '0'-no IPU, '1'-has IPU
   *         Bit1: '0'-no extended board, '1'-has extended board;
   * should always be 0
   */
  dev->info.hasIPU = (vbuf.ipu_id & 0x01) == 0x01 ? SANE_TRUE : SANE_FALSE;
  dev->info.hasXBD = (vbuf.ipu_id & 0x02) == 0x02 ? SANE_TRUE : SANE_FALSE;


  /* Image Composition Byte is set to 0x37 (0011 0111) */
  dev->info.supports_lineart = (vbuf.imagecomposition & 0x01) == 0x01 ? SANE_TRUE : SANE_FALSE;	/* TRUE */
  dev->info.supports_dithering = (vbuf.imagecomposition & 0x02) == 0x02 ? SANE_TRUE : SANE_FALSE;	/* TRUE */
  dev->info.supports_errordiffusion = (vbuf.imagecomposition & 0x04) == 0x04 ? SANE_TRUE : SANE_FALSE;	/* TRUE */
  dev->info.supports_color = (vbuf.imagecomposition & 0x08) == 0x08 ? SANE_TRUE : SANE_FALSE;	/* FALSE */
  dev->info.supports_4bitgray = (vbuf.imagecomposition & 0x10) == 0x10 ? SANE_TRUE : SANE_FALSE;	/* TRUE */
  dev->info.supports_8bitgray = (vbuf.imagecomposition & 0x20) == 0x20 ? SANE_TRUE : SANE_FALSE;	/* TRUE */
  /* vbuf.imagecomposition & 0x40; FALSE */
  /* vbuf.imagecomposition & 0x80  FALSE reserved */
  str = &scan_mode_list[0];	/* array of string pointers */
  if (dev->info.supports_lineart)
    *str++ = strdup (SM_LINEART);
  if (dev->info.supports_dithering || dev->info.supports_errordiffusion)
    *str++ = strdup (SM_HALFTONE);
  if (dev->info.supports_color)
    *str++ = strdup (SM_COLOR);
  if (dev->info.supports_4bitgray)
    *str++ = strdup (SM_4BITGRAY);
  if (dev->info.supports_8bitgray)
    *str++ = strdup (SM_8BITGRAY);
  *str = NULL;

  snprintf (device_string, 60, "Flatbed%s%s%s%s%s%s",
	    dev->info.hasADF ? "/ADF" : "",
	    dev->info.hasDuplex ? "/Duplex" : "",
	    dev->info.hasEndorser ? "/Endorser" : "",
	    dev->info.hasIPU ? "/IPU" : "",
	    dev->info.supports_color ? " Color" : " B&W", " Scanner");
  dev->sane.type = strdup (device_string);

  /* ACE Image Data Processing  Binary Filters
   * For IS450 this is set to 0x18 (0001 1000) if IPU installed, else 0x00
   * For IS420 this is set to 0x3C (0011 1100) if IPU installed, else 0x00
   */
  dev->info.supports_whiteframing =
    ((vbuf.imagedataprocessing[0] & 0x01) == 0x01) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_blackframing =
    ((vbuf.imagedataprocessing[0] & 0x02) == 0x02) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_edgeextraction =
    ((vbuf.imagedataprocessing[0] & 0x04) == 0x04) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_noiseremoval =
    ((vbuf.imagedataprocessing[0] & 0x08) == 0x08) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_smoothing =
    ((vbuf.imagedataprocessing[0] & 0x10) == 0x10) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_linebolding =
    ((vbuf.imagedataprocessing[0] & 0x20) == 0x20) ? SANE_TRUE : SANE_FALSE;

  /* Compression Method is not supported for IS450
   *                    is     supported for IS420  */
  dev->info.supports_MH =
    ((vbuf.compression & 0x01) == 0x01) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_MR =
    ((vbuf.compression & 0x02) == 0x02) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_MMR =
    ((vbuf.compression & 0x04) == 0x04) ? SANE_TRUE : SANE_FALSE;
  dev->info.supports_MHB = ((vbuf.compression & 0x08) == 0x08) ? SANE_TRUE : SANE_FALSE;	/* MH Byte Boundary */

  /* compression_list[] will have variable number of elements, but the order will be fixed as follows: */
  str = &compression_list[0];
  *str++ = strdup ("none");
  if (dev->info.supports_MH)
    *str++ = strdup ("G3 1-D MH");
  if (dev->info.supports_MR)
    *str++ = strdup ("G3 2-D MR");
  if (dev->info.supports_MMR)
    *str++ = strdup ("G4 2-D MMR");
  if (dev->info.supports_MHB)
    *str++ = strdup ("MH Byte Boundary");
  *str = NULL;

  /* Marker Recognition is set to 0x00 */
  dev->info.supports_markerrecognition =
    ((vbuf.markerrecognition & 0x01) == 0x01) ? SANE_TRUE : SANE_FALSE;

  /* Size Recognition
   * For IS450 this is set to 0x01 when IPU installed; else 0x00
   * For IS420 this is set to 0x01
   */
  dev->info.supports_sizerecognition =
    ((vbuf.sizerecognition & 0x01) == 0x01) ? SANE_TRUE : SANE_FALSE;

  /* X Maximum Output Pixel in main scanning direction
   * For IS450 this is set to 0x1360 (4960)
   * For IS420 this is set to        (4880)
   * [MostSignificantByte LeastSignificantByte]
   */
  dev->info.xmaxoutputpixels =
    (vbuf.xmaxoutputpixels[0] << 8) | vbuf.xmaxoutputpixels[1];

  /* Set capabilities from jis VPD IDENTIFIER Page Code F0H */
  dev->info.resBasicX = _2btol (&jbuf.BasicRes.x[0]);	/* set to 400 */
  dev->info.resBasicY = _2btol (&jbuf.BasicRes.y[0]);	/* set to 400 */

  dev->info.resXstep = (jbuf.resolutionstep >> 4) & 0x0F;	/* set to 1   */
  dev->info.resYstep = jbuf.resolutionstep & 0x0F;	/* set to 1   */
  dev->info.resMaxX = _2btol (&jbuf.MaxRes.x[0]);	/* set to 800 */
  dev->info.resMaxY = _2btol (&jbuf.MaxRes.y[0]);	/* set to 800 */
  dev->info.resMinX = _2btol (&jbuf.MinRes.x[0]);	/* set to 100 for IS450 and 60 for IS420 */
  dev->info.resMinY = _2btol (&jbuf.MinRes.y[0]);	/* set to 100 for IS450 and 60 for IS420 */

  dev->info.xres_range.min = _2btol (&jbuf.MinRes.x[0]);	/* set to 100 for IS450 and 60 for IS420 */
  dev->info.xres_range.max = _2btol (&jbuf.MaxRes.x[0]);	/* set to 800 */
  dev->info.resXstep = (jbuf.resolutionstep >> 4) & 0x0F;	/* set to 1   */
  dev->info.xres_range.quant = dev->info.resXstep;

  dev->info.yres_range.min = _2btol (&jbuf.MinRes.y[0]);	/* set to 100 for IS450 and 60 for IS420 */
  dev->info.yres_range.max = _2btol (&jbuf.MaxRes.y[0]);	/* set to 800 */
  dev->info.resYstep = jbuf.resolutionstep & 0x0F;	/* set to 1   */
  dev->info.yres_range.quant = dev->info.resYstep;

  /* set the length of the list to zero first, then append standard resolutions */
  i = 0;
  if ((jbuf.standardres[0] & 0x80) == 0x80)
    dev->info.resStdList[++i] = 60;
  if ((jbuf.standardres[0] & 0x40) == 0x40)
    dev->info.resStdList[++i] = 75;
  if ((jbuf.standardres[0] & 0x20) == 0x20)
    dev->info.resStdList[++i] = 100;
  if ((jbuf.standardres[0] & 0x10) == 0x10)
    dev->info.resStdList[++i] = 120;
  if ((jbuf.standardres[0] & 0x08) == 0x08)
    dev->info.resStdList[++i] = 150;
  if ((jbuf.standardres[0] & 0x04) == 0x04)
    dev->info.resStdList[++i] = 160;
  if ((jbuf.standardres[0] & 0x02) == 0x02)
    dev->info.resStdList[++i] = 180;
  if ((jbuf.standardres[0] & 0x01) == 0x01)
    dev->info.resStdList[++i] = 200;
  if ((jbuf.standardres[1] & 0x80) == 0x80)
    dev->info.resStdList[++i] = 240;
  if ((jbuf.standardres[1] & 0x40) == 0x40)
    dev->info.resStdList[++i] = 300;
  if ((jbuf.standardres[1] & 0x20) == 0x20)
    dev->info.resStdList[++i] = 320;
  if ((jbuf.standardres[1] & 0x10) == 0x10)
    dev->info.resStdList[++i] = 400;
  if ((jbuf.standardres[1] & 0x08) == 0x08)
    dev->info.resStdList[++i] = 480;
  if ((jbuf.standardres[1] & 0x04) == 0x04)
    dev->info.resStdList[++i] = 600;
  if ((jbuf.standardres[1] & 0x02) == 0x02)
    dev->info.resStdList[++i] = 800;
  if ((jbuf.standardres[1] & 0x01) == 0x01)
    dev->info.resStdList[++i] = 1200;
  dev->info.resStdList[0] = i;	/* number of resolutions */
  if (dev->info.resStdList[0] == 0)
    {				/* make a default standard resolutions for 200 and 300dpi */
      DBG (DBG_warning, "attach: no standard resolutions reported\n");
      dev->info.resStdList[0] = 2;
      dev->info.resStdList[1] = 200;
      dev->info.resStdList[2] = 300;
      dev->info.resBasicX = dev->info.resBasicY = 300;
    }
  DBG (DBG_info, "attach: Window(W/L) = (%lu/%lu)\n",
       _4btol (&jbuf.Window.width[0]), _4btol (&jbuf.Window.length[0]));
  dev->info.winWidth = _4btol (&jbuf.Window.width[0]);
  dev->info.winHeight = _4btol (&jbuf.Window.length[0]);
  if (dev->info.winWidth <= 0)
    {
      dev->info.winWidth = (SANE_Int) (dev->info.resBasicX * 8.5);
      DBG (DBG_warning, "attach: invalid window width reported, using %d\n",
	   dev->info.winWidth);
    }
  if (dev->info.winHeight <= 0)
    {
      dev->info.winHeight = dev->info.resBasicY * 14;
      DBG (DBG_warning, "attach: invalid window height reported, using %d\n",
	   dev->info.winHeight);
    }
  /* 4692 / 400 * 25.4 = 297 */
  mm = (dev->info.resBasicX > 0) ?
    ((double) dev->info.winWidth / (double) dev->info.resBasicX *
     MM_PER_INCH) : 0.0;
  dev->info.x_range.min = SANE_FIX (0.0);
  dev->info.x_range.max = SANE_FIX (mm);
  dev->info.x_range.quant = SANE_FIX (0.0);
  DBG (DBG_info, "attach: winWidth=%d resBasicX=%d mm/in=%f mm=%f\n",
       dev->info.winWidth, dev->info.resBasicX, MM_PER_INCH, mm);

  mm = (dev->info.resBasicY > 0) ?
    ((double) dev->info.winHeight / (double) dev->info.resBasicY *
     MM_PER_INCH) : 0.0;
  dev->info.y_range.min = SANE_FIX (0.0);
  dev->info.y_range.max = SANE_FIX (mm);
  dev->info.y_range.quant = SANE_FIX (0.0);

  DBG (DBG_info, "attach: RANGE x_range.max=%f, y_range.max=%f\n",
       SANE_UNFIX (dev->info.x_range.max),
       SANE_UNFIX (dev->info.y_range.max));

  /* min, max, quantization  light-dark  1-255, 0 means default 128 */
  dev->info.brightness_range.min = 1;
  dev->info.brightness_range.max = 255;
  dev->info.brightness_range.quant = 1;
  /* min, max, quantization  white-black 1-255, 0 means default 128 */
  dev->info.contrast_range.min = 1;
  dev->info.contrast_range.max = 255;
  dev->info.contrast_range.quant = 1;
  /* min, max, quantization  low-high    1-255, 0 means default 128 */
  dev->info.threshold_range.min = 1;
  dev->info.threshold_range.max = 255;
  dev->info.threshold_range.quant = 1;

  /* jbuf.functions */
  dev->info.overflow_support =
    ((jbuf.functions & 0x01) == 0x01) ? SANE_TRUE : SANE_FALSE;
  dev->info.lineart_support =
    ((jbuf.functions & 0x02) == 0x02) ? SANE_TRUE : SANE_FALSE;
  dev->info.dither_support =
    ((jbuf.functions & 0x04) == 0x04) ? SANE_TRUE : SANE_FALSE;
  dev->info.grayscale_support =
    ((jbuf.functions & 0x08) == 0x08) ? SANE_TRUE : SANE_FALSE;

  /* set option defaults  */
  dev->info.default_res = dev->info.resBasicX;
  dev->info.default_xres = dev->info.resBasicX;
  dev->info.default_yres = dev->info.resBasicY;
  dev->info.default_imagecomposition = LINEART;
  dev->info.default_media = FLATBED;
  dev->info.default_duplex = SANE_FALSE;

  /* dev->info.autoborder_default = dev->info.canBorderRecog; */
  /*
     dev->info.batch_default = SANE_FALSE;
     dev->info.deskew_default = SANE_FALSE;
     dev->info.check_adf_default = SANE_FALSE;
     dev->info.timeout_adf_default = 0;
     dev->info.timeout_manual_default = 0;
   */
  /* dev->info.control_panel_default = dev->info.canACE; Image Data Processing */

  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    *devp = dev;

  DBG (DBG_sane_proc, "<<< attach:\n");
  return SANE_STATUS_GOOD;
}




/* SANE callback to attach a SCSI device */
static SANE_Status
attach_one_scsi (const char *devname)
{
  return attach (devname, CONNECTION_SCSI, NULL);
  /* return SANE_STATUS_GOOD; */
}

static void
parse_configuration_file (FILE * fp)
{
  char line[PATH_MAX], *s, *t;
  int linenumber;

  DBG (DBG_proc, ">> parse_configuration_file\n");

  if (fp == NULL)
    {
      DBG (DBG_proc,
	   ">> parse_configuration_file: No config file present!\n");
    }
  else
    {				/*parse configuration file */
      for (linenumber = 0; sanei_config_read (line, sizeof (line), fp);
	   linenumber++)
	{
	  DBG (DBG_proc,
	       ">> parse_configuration_file: parsing config line \"%s\"\n",
	       line);
	  if (line[0] == '#')
	    continue;		/* ignore line comments */
	  for (s = line; isspace (*s); ++s);	/* skip white space: */
	  for (t = s; *t != '\0'; t++);
	  for (--t; t > s && isspace (*t); t--);
	  *(++t) = '\0';	/*trim trailing space */
	  if (!strlen (s))
	    continue;		/* ignore empty lines */
	  if ((t = strstr (s, "scsi ")) != NULL)
	    {
	      /*  scsi VENDOR MODEL TYPE BUS CHANNEL ID LUN */
	      DBG (DBG_proc,
		   ">> parse_configuration_file: config file line %d: trying to attach SCSI: %s'\n",
		   linenumber, line);
	      sanei_config_attach_matching_devices (t, attach_one_scsi);
	    }
	  else if ((t = strstr (s, "/dev/")) != NULL)
	    {
	      /* /dev/scanner /dev/sg0 */
	      DBG (DBG_proc,
		   ">> parse_configuration_file: config file line %d: trying to attach SCSI: %s'\n",
		   linenumber, line);
	      sanei_config_attach_matching_devices (t, attach_one_scsi);
	    }
	  else if ((t = strstr (s, "option")) != NULL)
	    {
	      for (t += 6; isspace (*t); t++);	/* skip to flag */
	      /* if(strstr(t,"FLAG_VALUE")!=NULL) FLAG_VALUE=SANE_TRUE; */
	    }
	  else
	    {
	      DBG (DBG_proc,
		   ">> parse_configuration_file: config file line %d: OBSOLETE !! use the scsi keyword!\n",
		   linenumber);
	      DBG (DBG_proc,
		   ">> parse_configuration_file:   (see man sane-avision for details): trying to attach SCSI: %s'\n",
		   line);
	    }
	}
      fclose (fp);
    }
  DBG (DBG_proc, "<< parse_configuration_file\n");
  return;
}

static SANE_Status
do_cancel (HS2P_Scanner * s)
{
  SANE_Status status;
  DBG (DBG_sane_proc, ">> do_cancel\n");

  DBG (DBG_proc, "cancel: sending OBJECT POSITION\n");

  s->scanning = SANE_FALSE;
  s->cancelled = SANE_TRUE;
  s->EOM = SANE_FALSE;

  if (s->fd >= 0)
    {
      if ((status =
	   object_position (s->fd,
			    OBJECT_POSITION_UNLOAD)) != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "cancel: OBJECT POSITION failed\n");
	}
      sanei_scsi_req_flush_all ();
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
    }
  /*
     if (s->reader_pid > 0){
     int exit_status;
     sanei_thread_kill (s->reader_pid);
     sanei_thread_waitpid (s->reader_pid, &exit_status);
     s->reader_pid = 0;
     }
   */

  DBG (DBG_sane_proc, "<< do_cancel\n");
  return (SANE_STATUS_CANCELLED);
}


SANE_Status
sane_init (SANE_Int * version_code,
	   SANE_Auth_Callback __sane_unused__ authorize)
{
  FILE *fp;

  DBG_INIT ();			/* initialize SANE DEBUG */

  /*DBG (DBG_sane_init, "> sane_init (authorize = %p)\n", (void *) authorize); */
#if defined PACKAGE && defined VERSION
  DBG (DBG_sane_init, "> sane_init: hs2p backend version %d.%d-%d ("
       PACKAGE " " VERSION ")\n", SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, BUILD);
#endif
  /*
     sanei_thread_init ();
   */

  if (version_code)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0);


  if ((fp = sanei_config_open (HS2P_CONFIG_FILE)) != NULL)
    {
      parse_configuration_file (fp);
    }
  else
    {
      DBG (DBG_sane_init, "> sane_init: No config file \"%s\" present!\n",
	   HS2P_CONFIG_FILE);
    }

#if 0
  /* avision.c: search for all supported scanners on all scsi buses & channels */
  for (hw = &HS2P_Device_List[0]; hw->mfg != NULL; hw++)
    {
      sanei_scsi_find_devices (hw->mfg,	/*vendor */
			       hw->model,	/*model */
			       NULL,	/*all types */
			       -1,	/*all bus */
			       -1,	/*all channel */
			       -1,	/*all id */
			       -1,	/*all lun */
			       attach_one_scsi);	/*callback */
      DBG (2, "sane_init: %s %s\n", hw->mfg, hw->model);
    }
#endif

  DBG (DBG_sane_init, "< sane_init\n");
  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  HS2P_Device *dev, *next;
  DBG (DBG_proc, ">> sane_exit\n");

  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free ((void *) (SANE_String_Const *) dev->sane.name);
      free ((SANE_String_Const *) dev->sane.model);
      free (dev);
    }

  DBG (DBG_proc, "<< sane_exit\n");
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  static const SANE_Device **devlist = 0;
  HS2P_Device *dev;
  int i;
  DBG (DBG_proc, ">> sane_get_devices (local_only = %d)\n", local_only);

  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; dev; dev = dev->next)
    devlist[i++] = &dev->sane;
  devlist[i++] = 0;

  *device_list = devlist;

  DBG (DBG_proc, "<< sane_get_devices\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devnam, SANE_Handle * handle)
{
  SANE_Status status;
  HS2P_Device *dev;
  HS2P_Scanner *s;
  DBG (DBG_proc, "> sane_open\n");

  if (devnam[0] == '\0')
    {
      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devnam) == 0)
	    break;
	}
      if (!dev)
	{
	  status = attach (devnam, CONNECTION_SCSI, &dev);
	  if (status != SANE_STATUS_GOOD)
	    return (status);
	}
    }
  else
    {
      dev = first_dev;
    }
  if (!dev)
    return (SANE_STATUS_INVAL);

  s = malloc (sizeof (*s));
  if (!s)
    return SANE_STATUS_NO_MEM;
  memset (s, 0, sizeof (*s));	/* initialize */

  s->fd = -1;
  s->hw = dev;
  s->hw->info.bmu = s->bmu = MILLIMETERS;	/* 01H */
  s->hw->info.mud = s->mud = 1;	/* If the scale is MM or POINT, mud is fixed to 1 */
  s->bpp = 1;			/* supports 1,4,6,8 so we set to LINEART 1bpp     */
  /*
     s->scanning  = SANE_FALSE;
     s->cancelled =  SANE_FALSE;
   */
  /*
   */

  ScannerDump (s);
  init_options (s);

  s->next = first_handle;	/* insert newly opened handle into list of open handles: */
  first_handle = s;

  /* initialize our parameters here AND in sane_start?
     get_parameters(s, 0);
   */

  *handle = s;

  DBG (DBG_proc, "< sane_open\n");
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  HS2P_Scanner *s = (HS2P_Scanner *) handle;
  char **str;
  DBG (DBG_proc, ">> sane_close\n");

  if (s->fd != -1)
    sanei_scsi_close (s->fd);
  free (s);

  for (str = &compression_list[0]; *str; str++);
  free (*str);
  for (str = &scan_mode_list[0]; *str; str++);
  free (*str);

  DBG (DBG_proc, "<< sane_close\n");
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  HS2P_Scanner *s = handle;
  DBG (DBG_proc, ">> sane_get_option_descriptor: %d name=%s\n", option,
       s->opt[option].name);

  if ((unsigned) option >= NUM_OPTIONS)
    return (0);

  DBG (DBG_info, "<< sane_get_option_descriptor: name=%s\n",
       s->opt[option].name);
  return (s->opt + option);
}

#if 0
static SANE_Int
get_scan_mode_id (char *s)	/* sequential search */
{
  SANE_Int i;

  for (i = 0; scan_mode_list[i]; i++)
    if (strcmp (s, scan_mode_list[i]) == 0)
      break;

  /* unknown strings are treated as 'lineart' */
  return scan_mode_list[i] ? i : 0;
}
#endif
static SANE_Status
update_hs2p_data (HS2P_Scanner * s)
{

  DBG (DBG_proc, ">> update_hs2p_data\n");
  /* OPT_NREGX_ADF: */
  DBG (DBG_sane_option, "OPT_NREGX_ADF\n");
  s->val[OPT_NREGX_ADF].w = (SANE_Word) s->data.maintenance.nregx_adf;

  /* OPT_NREGY_ADF: */
  DBG (DBG_sane_option, "OPT_NREGY_ADF\n");
  s->val[OPT_NREGY_ADF].w = (SANE_Word) s->data.maintenance.nregx_book;

  /* OPT_NREGX_BOOK: */
  DBG (DBG_sane_option, "OPT_NREGX_BOOK\n");
  s->val[OPT_NREGX_BOOK].w = (SANE_Word) s->data.maintenance.nregx_book;

  /* OPT_NREGY_BOOK: */
  DBG (DBG_sane_option, "OPT_NREGY_BOOK\n");
  s->val[OPT_NREGY_BOOK].w = (SANE_Word) s->data.maintenance.nregy_book;

  /* OPT_NSCANS_ADF: */
  DBG (DBG_sane_option, "OPT_NSCANS_ADF\n");
  s->val[OPT_NSCANS_ADF].w =
    (SANE_Word) _4btol (&(s->data.maintenance.nscans_adf[0]));

  /* OPT_NSCANS_BOOK: */
  DBG (DBG_sane_option, "OPT_NSCANS_BOOK\n");
  s->val[OPT_NSCANS_BOOK].w =
    (SANE_Word) _4btol (&(s->data.maintenance.nscans_book[0]));

  /* OPT_LAMP_TIME: */
  DBG (DBG_sane_option, "OPT_LAMP_TIME\n");
  s->val[OPT_LAMP_TIME].w =
    (SANE_Word) _4btol (&(s->data.maintenance.lamp_time[0]));

  /* OPT_EO_ODD: */
  DBG (DBG_sane_option, "OPT_EO_ODD\n");
  s->val[OPT_EO_ODD].w = (SANE_Word) s->data.maintenance.eo_odd;

  /* OPT_EO_EVEN: */
  DBG (DBG_sane_option, "OPT_EO_EVEN\n");
  s->val[OPT_EO_EVEN].w = (SANE_Word) s->data.maintenance.eo_even;

  /* OPT_BLACK_LEVEL_ODD: */
  DBG (DBG_sane_option, "OPT_BLACK_LEVEL_ODD\n");
  s->val[OPT_BLACK_LEVEL_ODD].w =
    (SANE_Word) s->data.maintenance.black_level_odd;

  /* OPT_BLACK_LEVEL_EVEN: */
  DBG (DBG_sane_option, "OPT_BLACK_LEVEL_EVEN\n");
  s->val[OPT_BLACK_LEVEL_EVEN].w =
    (SANE_Word) s->data.maintenance.black_level_even;

  /* OPT_WHITE_LEVEL_ODD: */
  DBG (DBG_sane_option, "OPT_WHITE_LEVEL_ODD\n");
  s->val[OPT_WHITE_LEVEL_ODD].w =
    (SANE_Word) _2btol (&(s->data.maintenance.white_level_odd[0]));

  /* OPT_WHITE_LEVEL_EVEN: */
  DBG (DBG_sane_option, "OPT_WHITE_LEVEL_EVEN\n");
  s->val[OPT_WHITE_LEVEL_EVEN].w =
    (SANE_Word) _2btol (&(s->data.maintenance.white_level_even[0]));

  /* OPT_FIRST_ADJ_WHITE_ODD: */
  DBG (DBG_sane_option, "OPT_FIRST_ADJ_WHITE_ODD\n");
  s->val[OPT_FIRST_ADJ_WHITE_ODD].w =
    (SANE_Word) _2btol (&(s->data.maintenance.first_adj_white_odd[0]));

  /* OPT_FIRST_ADJ_WHITE_EVEN: */
  DBG (DBG_sane_option, "OPT_FIRST_ADJ_WHITE_EVEN\n");
  s->val[OPT_FIRST_ADJ_WHITE_EVEN].w =
    (SANE_Word) _2btol (&(s->data.maintenance.first_adj_white_even[0]));

  /* OPT_DENSITY: */
  DBG (DBG_sane_option, "OPT_DENSITY\n");
  s->val[OPT_DENSITY].w = (SANE_Word) s->data.maintenance.density_adj;

  /* OPT_NREGX_REVERSE: */
  DBG (DBG_sane_option, "OPT_NREGX_REVERSE\n");
  s->val[OPT_NREGX_REVERSE].w = (SANE_Word) s->data.maintenance.nregx_reverse;

  /* OPT_NREGY_REVERSE: */
  DBG (DBG_sane_option, "OPT_NREGY_REVERSE\n");
  s->val[OPT_NREGY_REVERSE].w = (SANE_Word) s->data.maintenance.nregy_reverse;

  /* OPT_NSCANS_REVERSE_ADF: */
  DBG (DBG_sane_option, "OPT_NSCANS_REVERSE_ADF\n");
  s->val[OPT_NSCANS_REVERSE_ADF].w =
    (SANE_Word) _4btol (&(s->data.maintenance.nscans_reverse_adf[0]));

  /* OPT_REVERSE_TIME: */
  DBG (DBG_sane_option, "OPT_REVERSE_TIME\n");
  s->val[OPT_REVERSE_TIME].w =
    (SANE_Word) _4btol (&(s->data.maintenance.reverse_time[0]));

  /* OPT_NCHARS: */
  DBG (DBG_sane_option, "OPT_NCHARS\n");
  s->val[OPT_NCHARS].w =
    (SANE_Word) _4btol (&(s->data.maintenance.nchars[0]));

  DBG (DBG_proc, "<< update_hs2p_data\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
hs2p_open (HS2P_Scanner * s)
{
  SANE_Status status;
  DBG (DBG_proc, ">> hs2p_open\n");
  DBG (DBG_info, ">> hs2p_open: trying to open: name=\"%s\" fd=%d\n",
       s->hw->sane.name, s->fd);
  if ((status =
       sanei_scsi_open (s->hw->sane.name, &s->fd, &sense_handler,
			&(s->hw->sense_data))) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sane_start: open of %s failed: %d %s\n",
	   s->hw->sane.name, status, sane_strstatus (status));
      return (status);
    }
  DBG (DBG_info, ">>hs2p_open: OPENED \"%s\" fd=%d\n", s->hw->sane.name,
       s->fd);

  if ((status = test_unit_ready (s->fd)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "hs2p_open: test_unit_ready() failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return status;
    }
  DBG (DBG_proc, "<< hs2p_open\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
hs2p_close (HS2P_Scanner * s)
{

  DBG (DBG_proc, ">> hs2p_close\n");

  release_unit (s->fd);
  sanei_scsi_close (s->fd);
  s->fd = -1;

  DBG (DBG_proc, "<< hs2p_close\n");
  return SANE_STATUS_GOOD;
}

#include <stdarg.h>
static SANE_Status
get_hs2p_data (HS2P_Scanner * s, ...)
{
  SANE_Status status;
  SANE_Byte *buf;
  size_t *len = &(s->data.bufsize);
  int dtc, fd = s->fd;
  u_long dtq = 0;		/* two bytes */
  va_list ap;

  DBG (DBG_proc, ">> get_hs2p_data\n");
  if (fd < 0)
    {
      status = hs2p_open (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "get_hs2p_data: error opening scanner: %s\n",
	       sane_strstatus (status));
	  return status;
	}
    }

  for (va_start (ap, s), dtc = va_arg (ap, int); dtc != DATA_TYPE_EOL;
       dtc = va_arg (ap, int))
    {
      DBG (DBG_proc, ">> get_hs2p_data 0x%2.2x\n", (int) dtc);
      switch (dtc)
	{
	case DATA_TYPE_GAMMA:
	  buf = &(s->data.gamma[0]);
	  *len = sizeof (s->data.gamma);
	  break;
	case DATA_TYPE_ENDORSER:
	  buf = &(s->data.endorser[0]);
	  *len = sizeof (s->data.endorser);
	  break;
	case DATA_TYPE_SIZE:
	  buf = &(s->data.size);
	  *len = sizeof (s->data.size);
	  break;
	case DATA_TYPE_PAGE_LEN:
	  buf = s->data.nlines;
	  *len = sizeof (s->data.nlines);
	  break;
	case DATA_TYPE_MAINTENANCE:
	  buf = (SANE_Byte *) & (s->data.maintenance);
	  *len = sizeof (s->data.maintenance);
	  break;
	case DATA_TYPE_ADF_STATUS:
	  buf = &(s->data.adf_status);
	  *len = sizeof (s->data.adf_status);
	  break;
	case DATA_TYPE_IMAGE:
	case DATA_TYPE_HALFTONE:
	default:
	  DBG (DBG_info, "Data Type Code %2.2x not handled.\n", dtc);
	  return SANE_STATUS_INVAL;
	}
      DBG (DBG_info,
	   "get_hs2p_data calling read_data for dtc=%2.2x and bufsize=%lu\n",
	   (int) dtc, (u_long) * len);
      status = read_data (s->fd, buf, len, (SANE_Byte) dtc, dtq);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "get_scanner_data: ERROR %s\n",
	       sane_strstatus (status));
	}
    }
  va_end (ap);

  if (fd < 0)
    {				/* need to return fd to original state */
      status = hs2p_close (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "get_hs2p_data: error closing fd: %s\n",
	       sane_strstatus (status));
	}
    }
  DBG (DBG_proc, "<< get_hs2p_data: %d\n", status);
  return (status);
}

static SANE_Status
print_maintenance_data (MAINTENANCE_DATA * d)
{
  DBG (DBG_proc, ">> print_maintenance_data: \n");

  DBG (DBG_LEVEL, "nregx_adf = %d\n", d->nregx_adf);
  DBG (DBG_LEVEL, "nregy_adf = %d\n", d->nregy_adf);

  DBG (DBG_LEVEL, "nregx_book = %d\n", d->nregx_book);
  DBG (DBG_LEVEL, "nregy_book = %d\n", d->nregy_book);

  DBG (DBG_LEVEL, "nscans_adf = %lu\n", _4btol (&(d->nscans_adf[0])));
  DBG (DBG_LEVEL, "nscans_adf = %lu\n", _4btol (&(d->nscans_adf[0])));

  DBG (DBG_LEVEL, "lamp time = %lu\n", _4btol (&(d->lamp_time[0])));

  DBG (DBG_LEVEL, "eo_odd = %d\n", d->eo_odd);
  DBG (DBG_LEVEL, "eo_even = %d\n", d->eo_even);

  DBG (DBG_LEVEL, "black_level_odd = %d\n", d->black_level_odd);
  DBG (DBG_LEVEL, "black_level_even = %d\n", d->black_level_even);

  DBG (DBG_LEVEL, "white_level_odd = %lu\n",
       _2btol (&(d->white_level_odd[0])));
  DBG (DBG_LEVEL, "white_level_even = %lu\n",
       _2btol (&(d->white_level_even[0])));

  DBG (DBG_LEVEL, "first_adj_white_odd = %lu\n",
       _2btol (&(d->first_adj_white_odd[0])));
  DBG (DBG_LEVEL, "first_adj_white_even = %lu\n",
       _2btol (&(d->first_adj_white_even[0])));

  DBG (DBG_LEVEL, "density_adj = %d\n", d->density_adj);

  DBG (DBG_LEVEL, "nregx_reverse = %d\n", d->nregx_reverse);
  DBG (DBG_LEVEL, "nregy_reverse = %d\n", d->nregy_reverse);

  DBG (DBG_LEVEL, "nscans_reverse_adf = %lu\n",
       _4btol (&(d->nscans_reverse_adf[0])));

  DBG (DBG_LEVEL, "reverse_time = %lu\n", _4btol (&(d->reverse_time[0])));

  DBG (DBG_LEVEL, "nchars = %lu\n", _4btol (&(d->nchars[0])));

  DBG (DBG_proc, "<< print_maintenance_data: \n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  HS2P_Scanner *s = handle;
  SANE_Status status;
  SANE_Word cap;
  SANE_String_Const name;
  SANE_Int paper_id;



  name = s->opt[option].name ? s->opt[option].name : "(nil)";
  if (info)
    *info = 0;
  DBG (DBG_proc, ">> sane_control_option: %s option=%d name=%s\n",
       action == SANE_ACTION_GET_VALUE ? "SET" : "GET", option, name);

  if (s->scanning)
    return (SANE_STATUS_DEVICE_BUSY);
  if (option >= NUM_OPTIONS)
    return (SANE_STATUS_INVAL);

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

  if (action == SANE_ACTION_GET_VALUE)
    {
      DBG (DBG_proc, "sane_control_option get_value option=%d\n", option);
      switch (option)
	{
	  /* word options: */
	case OPT_RESOLUTION:
	case OPT_X_RESOLUTION:
	case OPT_Y_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_BRIGHTNESS:
	case OPT_THRESHOLD:
	case OPT_CONTRAST:
	case OPT_NUM_OPTS:
	  *(SANE_Word *) val = s->val[option].w;
	  return (SANE_STATUS_GOOD);

	  /* bool options: */
	  /*case OPT_AUTOBORDER: case OPT_DESKEW: case OPT_CHECK_ADF: case OPT_BATCH: */
	case OPT_PREVIEW:
	case OPT_SCAN_WAIT_MODE:
	case OPT_DUPLEX:
	case OPT_AUTO_SIZE:
	case OPT_NEGATIVE:
	case OPT_ENDORSER:
	case OPT_SMOOTHING:
	case OPT_WHITE_BALANCE:
	case OPT_PREFEED:
	case OPT_CUSTOM_GAMMA:
	case OPT_PADDING:
	  *(SANE_Bool *) val = s->val[option].w;
	  return (SANE_STATUS_GOOD);

	  /* string options: */
	  /* case OPT_ADF:      */
	  /* case OPT_BITORDER: */
	  /* case OPT_ROTATION  */
	  /* case OPT_SECTION:  */
	case OPT_INQUIRY:
	case OPT_SCAN_SOURCE:
	case OPT_PAGE_ORIENTATION:
	case OPT_PAPER_SIZE:
	case OPT_SCAN_MODE:
	case OPT_ENDORSER_STRING:
	case OPT_COMPRESSION:
	case OPT_NOISEREMOVAL:
	case OPT_GRAYFILTER:
	case OPT_HALFTONE_CODE:
	case OPT_HALFTONE_PATTERN:
	case OPT_GAMMA:
	case OPT_AUTOSEP:
	case OPT_AUTOBIN:
	case OPT_PADDING_TYPE:
	  DBG (DBG_proc, "STRING=%s\n", s->val[option].s);
	  strcpy (val, s->val[option].s);
	  return (SANE_STATUS_GOOD);
	  DBG (DBG_proc, "sizeof(val)=%lu sizeof(s)=%lu\n",
	       (u_long) sizeof (val), (u_long) sizeof (s->val[option].s));
	  return (SANE_STATUS_GOOD);

	  /* gamma */
	case OPT_GAMMA_VECTOR_GRAY:
	  memcpy (val, s->val[option].wa, s->opt[option].size);
	  return SANE_STATUS_GOOD;

	  /* MAINTENANCE DATA */
	case OPT_DATA_GROUP:
	case OPT_UPDATE:
	  return SANE_STATUS_GOOD;
	case OPT_NREGX_ADF:
	  DBG (DBG_sane_option, "OPT_NREGX_ADF\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregx_adf;
	  return SANE_STATUS_GOOD;
	case OPT_NREGY_ADF:
	  DBG (DBG_sane_option, "OPT_NREGY_ADF\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregx_book;
	  return SANE_STATUS_GOOD;
	case OPT_NREGX_BOOK:
	  DBG (DBG_sane_option, "OPT_NREGX_BOOK\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregx_book;
	  return SANE_STATUS_GOOD;
	case OPT_NREGY_BOOK:
	  DBG (DBG_sane_option, "OPT_NREGY_BOOK\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregy_book;
	  return SANE_STATUS_GOOD;
	case OPT_NSCANS_ADF:
	  DBG (DBG_sane_option, "OPT_NSCANS_ADF\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.nscans_adf[0]));
	  return SANE_STATUS_GOOD;
	case OPT_NSCANS_BOOK:
	  DBG (DBG_sane_option, "OPT_NSCANS_BOOK\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.nscans_book[0]));
	  return SANE_STATUS_GOOD;
	case OPT_LAMP_TIME:
	  DBG (DBG_sane_option, "OPT_LAMP_TIME\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.lamp_time[0]));
	  return SANE_STATUS_GOOD;
	case OPT_EO_ODD:
	  DBG (DBG_sane_option, "OPT_EO_ODD\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.eo_odd;
	  return SANE_STATUS_GOOD;
	case OPT_EO_EVEN:
	  DBG (DBG_sane_option, "OPT_EO_EVEN\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.eo_even;
	  return SANE_STATUS_GOOD;
	case OPT_BLACK_LEVEL_ODD:
	  DBG (DBG_sane_option, "OPT_BLACK_LEVEL_ODD\n");
	  *(SANE_Word *) val =
	    (SANE_Word) s->data.maintenance.black_level_odd;
	  return SANE_STATUS_GOOD;
	case OPT_BLACK_LEVEL_EVEN:
	  DBG (DBG_sane_option, "OPT_BLACK_LEVEL_EVEN\n");
	  *(SANE_Word *) val =
	    (SANE_Word) s->data.maintenance.black_level_even;
	  return SANE_STATUS_GOOD;
	case OPT_WHITE_LEVEL_ODD:
	  DBG (DBG_sane_option, "OPT_WHITE_LEVEL_ODD\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _2btol (&(s->data.maintenance.white_level_odd[0]));
	  return SANE_STATUS_GOOD;
	case OPT_WHITE_LEVEL_EVEN:
	  DBG (DBG_sane_option, "OPT_WHITE_LEVEL_EVEN\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _2btol (&(s->data.maintenance.white_level_even[0]));
	  return SANE_STATUS_GOOD;
	case OPT_FIRST_ADJ_WHITE_ODD:
	  DBG (DBG_sane_option, "OPT_FIRST_ADJ_WHITE_ODD\n");
	  *(SANE_Word *) val =
	    (SANE_Word)
	    _2btol (&(s->data.maintenance.first_adj_white_odd[0]));
	  return SANE_STATUS_GOOD;
	case OPT_FIRST_ADJ_WHITE_EVEN:
	  DBG (DBG_sane_option, "OPT_FIRST_ADJ_WHITE_EVEN\n");
	  *(SANE_Word *) val =
	    (SANE_Word)
	    _2btol (&(s->data.maintenance.first_adj_white_even[0]));
	  return SANE_STATUS_GOOD;
	case OPT_DENSITY:
	  DBG (DBG_sane_option, "OPT_DENSITY\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.density_adj;
	  return SANE_STATUS_GOOD;
	case OPT_NREGX_REVERSE:
	  DBG (DBG_sane_option, "OPT_NREGX_REVERSE\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregx_reverse;
	  return SANE_STATUS_GOOD;
	case OPT_NREGY_REVERSE:
	  DBG (DBG_sane_option, "OPT_NREGY_REVERSE\n");
	  *(SANE_Word *) val = (SANE_Word) s->data.maintenance.nregy_reverse;
	  return SANE_STATUS_GOOD;
	case OPT_NSCANS_REVERSE_ADF:
	  DBG (DBG_sane_option, "OPT_NSCANS_REVERSE_ADF\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.nscans_reverse_adf[0]));
	  return SANE_STATUS_GOOD;
	case OPT_REVERSE_TIME:
	  DBG (DBG_sane_option, "OPT_REVERSE_TIME\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.reverse_time[0]));
	  return SANE_STATUS_GOOD;
	case OPT_NCHARS:
	  DBG (DBG_sane_option, "OPT_NCHARS\n");
	  *(SANE_Word *) val =
	    (SANE_Word) _4btol (&(s->data.maintenance.nchars[0]));
	  return (SANE_STATUS_GOOD);

	default:
	  DBG (DBG_proc, "sane_control_option:invalid option number %d\n",
	       option);
	  return SANE_STATUS_INVAL;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      DBG (DBG_proc, "sane_control_option set_value\n");
      switch (s->opt[option].type)
	{
	case SANE_TYPE_BOOL:
	case SANE_TYPE_INT:
	  DBG (DBG_proc, "sane_control_option: set_value %s [#%d] to %d\n",
	       name, option, *(SANE_Word *) val);
	  break;
	case SANE_TYPE_FIXED:
	  DBG (DBG_proc, "sane_control_option: set_value %s [#%d] to %f\n",
	       name, option, SANE_UNFIX (*(SANE_Word *) val));
	  break;
	case SANE_TYPE_STRING:
	  DBG (DBG_proc, "sane_control_option: set_value %s [#%d] to %s\n",
	       name, option, (char *) val);
	  break;
	case SANE_TYPE_BUTTON:
	  DBG (DBG_proc, "sane_control_option: set_value %s [#%d]\n",
	       name, option);
	  update_hs2p_data (s);
	  break;
	default:
	  DBG (DBG_proc, "sane_control_option: set_value %s [#%d]\n", name,
	       option);
	}

      if (!SANE_OPTION_IS_SETTABLE (cap))
	return (SANE_STATUS_INVAL);
      if ((status =
	   sanei_constrain_value (s->opt + option, val,
				  info)) != SANE_STATUS_GOOD)
	return status;

      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	  s->opt[OPT_AUTO_SIZE].cap |= SANE_CAP_INACTIVE;	/* disable auto size */
	  /* make sure that paper-size is set to custom */
	  if (info && s->val[option].w != *(SANE_Word *) val)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  s->val[option].w = *(SANE_Word *) val;
	  /* resets the paper format to user defined */
	  if (strcmp (s->val[OPT_PAPER_SIZE].s, paper_list[0]) != 0)	/* CUSTOM PAPER SIZE */
	    {
	      if (info)
		*info |= SANE_INFO_RELOAD_OPTIONS;
	      if (s->val[OPT_PAPER_SIZE].s)
		free (s->val[OPT_PAPER_SIZE].s);
	      s->val[OPT_PAPER_SIZE].s = strdup (paper_list[0]);	/* CUSTOM PAPER SIZE */
	    }
	  /* fall through */
	case OPT_X_RESOLUTION:
	case OPT_Y_RESOLUTION:
	  if (info && s->val[option].w != *(SANE_Word *) val)
	    *info |= SANE_INFO_RELOAD_PARAMS;

	  /* fall through */
	  /*case OPT_ACE_FUNCTION: case OPT_ACE_SENSITIVITY: */
	case OPT_BRIGHTNESS:
	case OPT_THRESHOLD:
	case OPT_CONTRAST:
	case OPT_NUM_OPTS:
	  s->val[option].w = *(SANE_Word *) val;
	  return (SANE_STATUS_GOOD);

	  /* string options */
	case OPT_NOISEREMOVAL:
	case OPT_AUTOSEP:
	case OPT_AUTOBIN:
	case OPT_COMPRESSION:
	case OPT_PADDING_TYPE:
	case OPT_GRAYFILTER:
	case OPT_HALFTONE_CODE:
	case OPT_HALFTONE_PATTERN:
	case OPT_ENDORSER_STRING:
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
	  return SANE_STATUS_GOOD;

	  /* boolean options: */
	case OPT_PREVIEW:
	case OPT_DUPLEX:
	case OPT_NEGATIVE:
	case OPT_SCAN_WAIT_MODE:
	case OPT_ENDORSER:
	case OPT_SMOOTHING:
	case OPT_WHITE_BALANCE:
	case OPT_PREFEED:
	case OPT_PADDING:
	  s->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	case OPT_GAMMA_VECTOR_GRAY:
	  memcpy (s->val[option].wa, val, s->opt[option].size);
	  return SANE_STATUS_GOOD;

	  /* options with side effect */
	case OPT_GAMMA:
	  if (strcmp (s->val[option].s, (SANE_String) val))
	    {
	      if (!strcmp ((SANE_String) val, "User"))
		{
		  s->val[OPT_CUSTOM_GAMMA].b = SANE_TRUE;
		  s->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
		  /* Brightness and Contrast do not work when downloading Gamma Table */
		  s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
		  s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
		}
	      else
		{
		  s->val[OPT_CUSTOM_GAMMA].b = SANE_FALSE;
		  s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
		  s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
		  s->opt[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
		}
	      if (info)
		*info |= SANE_INFO_RELOAD_OPTIONS;
	    }
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
	  return SANE_STATUS_GOOD;

	case OPT_CUSTOM_GAMMA:
	  s->val[OPT_CUSTOM_GAMMA].w = *(SANE_Word *) val;
	  if (s->val[OPT_CUSTOM_GAMMA].w)
	    {
	      s->opt[OPT_GAMMA_VECTOR_GRAY].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      s->opt[OPT_GAMMA_VECTOR_GRAY].cap |= SANE_CAP_INACTIVE;
	    }
	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;
	  return SANE_STATUS_GOOD;

	case OPT_RESOLUTION:
	  if (info && s->val[option].w != *(SANE_Word *) val)
	    *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
	  s->val[option].w = *(SANE_Word *) val;
	  s->val[OPT_X_RESOLUTION].w = *(SANE_Word *) val;
	  s->val[OPT_Y_RESOLUTION].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;
	case OPT_SCAN_SOURCE:
	  /* a string option */
	  /*    Since the scanner ejects the sheet in ADF mode
	   * it is impossible to scan multiple sections in one document
	   *    In ADF mode, because of mechanical limitations:
	   * the minimum document size is (x,y)=(69mm x 120mm)
	   */
	  if (info && strcmp ((char *) s->val[option].s, (char *) val))
	    *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
	  if (!strcmp ("ADF", (SANE_String) val))
	    {
	      s->opt[OPT_ENDORSER].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_ENDORSER_STRING].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_PREFEED].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_DUPLEX].cap &= ~SANE_CAP_INACTIVE;
	      /*s->opt[OPT_PADDING].cap &= ~SANE_CAP_INACTIVE; */
	    }
	  else
	    {			/* Flatbed */
	      s->opt[OPT_ENDORSER].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_ENDORSER_STRING].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_PREFEED].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_DUPLEX].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_PADDING].cap |= SANE_CAP_INACTIVE;
	    }
	  return SANE_STATUS_GOOD;
	case OPT_SCAN_MODE:
	  /* a string option */
	  /* scan mode != lineart disables compression, setting it to  'none' */
	  if (strcmp (s->val[option].s, (SANE_String) val))
	    {
	      if (info)
		*info |= SANE_INFO_RELOAD_OPTIONS;
	      if (!strcmp (SM_LINEART, (SANE_String) val))
		{
		  s->image_composition = LINEART;
		  s->opt[OPT_COMPRESSION].cap &= ~SANE_CAP_INACTIVE;	/* enable compression control */
		  s->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;	/* enable threshold control   */
		  s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;	/* disable brightness control */
		  s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;	/* disable contrast control   */
		  s->opt[OPT_GAMMA].cap |= SANE_CAP_INACTIVE;	/* disable gamma              */
		  s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;	/* disable gamma              */
		  s->opt[OPT_GAMMA_VECTOR_GRAY].cap |= SANE_CAP_INACTIVE;	/* disable gamma              */
		  s->opt[OPT_HALFTONE_CODE].cap |= SANE_CAP_INACTIVE;	/* disable halftone code      */
		  s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;	/* disable halftone pattern   */
		}
	      else
		{
		  if (!strcmp (SM_HALFTONE, (SANE_String) val))
		    {
		      s->image_composition = HALFTONE;
		      s->opt[OPT_HALFTONE_CODE].cap &= ~SANE_CAP_INACTIVE;	/* enable halftone code    */
		      s->opt[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;	/* enable halftone pattern */
		    }
		  else if (!strcmp (SM_4BITGRAY, (SANE_String) val) ||
			   !strcmp (SM_6BITGRAY, (SANE_String) val) ||
			   !strcmp (SM_8BITGRAY, (SANE_String) val))
		    {
		      s->image_composition = GRAYSCALE;
		      s->opt[OPT_GAMMA].cap &= ~SANE_CAP_INACTIVE;	/* enable  gamma            */
		      s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;	/* enable  brightness       */
		      s->opt[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;	/* enable  contrast         */
		      s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;	/* disable threshold        */
		      s->opt[OPT_COMPRESSION].cap |= SANE_CAP_INACTIVE;	/* disable compression      */
		      s->opt[OPT_HALFTONE_CODE].cap |= SANE_CAP_INACTIVE;	/* disable halftone code    */
		      s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;	/* disable halftone pattern */
		      if (s->val[OPT_COMPRESSION].s
			  && get_compression_id (s->val[OPT_COMPRESSION].s) !=
			  0)
			{
			  free (s->val[OPT_COMPRESSION].s);
			  s->val[OPT_COMPRESSION].s =
			    strdup (compression_list[0]);
			}
		    }
		}
	      free (s->val[option].s);
	      s->val[option].s = strdup (val);
	    }
	  return SANE_STATUS_GOOD;

	case OPT_PAGE_ORIENTATION:
	  if (strcmp (s->val[option].s, (SANE_String) val))
	    {
	      free (s->val[option].s);
	      s->val[option].s = strdup (val);
	      if (info)
		*info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
	    }
	  /* set val to current selected paper size */
	  paper_id = get_paper_id ((SANE_String) s->val[OPT_PAPER_SIZE].s);
	  goto paper_id;
	case OPT_PAPER_SIZE:
	  /* a string option */
	  /* changes geometry options, therefore _RELOAD_PARAMS and _RELOAD_OPTIONS */
	  s->opt[OPT_AUTO_SIZE].cap |= SANE_CAP_INACTIVE;	/* disable auto size */
	  if (strcmp (s->val[option].s, (SANE_String) val))
	    {
	      paper_id = get_paper_id ((SANE_String) val);

	      /* paper_id 0 is a special case (custom) that
	       * disables the paper size control of geometry
	       */
	    paper_id:
	      if (paper_id != 0)
		{
		  double x_max, y_max, x, y, temp;

		  x_max = SANE_UNFIX (s->hw->info.x_range.max);
		  y_max = SANE_UNFIX (s->hw->info.y_range.max);

		  /* a dimension of 0.0 (or less) is replaced with the max value */
		  x = (paper_sizes[paper_id].width <= 0.0) ? x_max :
		    paper_sizes[paper_id].width;
		  y = (paper_sizes[paper_id].length <= 0.0) ? y_max :
		    paper_sizes[paper_id].length;

		  if (info)
		    *info |=
		      SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;

		  if (!strcmp (s->val[OPT_PAGE_ORIENTATION].s, LANDSCAPE))	/* swap */
		    {
		      temp = y_max;
		      y_max = x_max;
		      x_max = temp;
		      temp = y;
		      y = x;
		      x = temp;
		    }

		  s->val[OPT_TL_X].w = SANE_FIX (0.0);
		  s->val[OPT_TL_Y].w = SANE_FIX (0.0);
		  s->val[OPT_BR_X].w = SANE_FIX (MIN (x, x_max));
		  s->val[OPT_BR_Y].w = SANE_FIX (MIN (y, y_max));
		}
	      free (s->val[option].s);
	      s->val[option].s = strdup (val);
	    }
	  return SANE_STATUS_GOOD;
	case OPT_UPDATE:	/* SANE_TYPE_BUTTON */
	  DBG (DBG_info,
	       "OPT_UPDATE: ready to call get_hs2p_data: fd=%d\n", s->fd);
	  get_hs2p_data (s,
			 /* DATA_TYPE_GAMMA, */
			 /* DATA_TYPE_ENDORSER, */
			 /* DATA_TYPE_SIZE, */
			 /* DATA_TYPE_PAGE_LEN, */
			 DATA_TYPE_MAINTENANCE,
			 /* DATA_TYPE_ADF_STATUS, */
			 /* DATA_TYPE_IMAGE, */
			 /* DATA_TYPE_HALFTONE, */
			 DATA_TYPE_EOL);	/* va_list end */
	  update_hs2p_data (s);
	  if (DBG_LEVEL >= DBG_info)
	    print_maintenance_data (&(s->data.maintenance));
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
	  return SANE_STATUS_GOOD;
	}
      return (SANE_STATUS_GOOD);
    }

  DBG (DBG_proc, "<< sane_control_option\n");
  return (SANE_STATUS_INVAL);

}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  HS2P_Scanner *s = handle;
  DBG (DBG_proc, ">> sane_get_parameters\n");

  if (!s->scanning)
    {
      int width, length, xres, yres;
      const char *mode;

      memset (&s->params, 0, sizeof (s->params));	/* CLEAR SANE_Parameters */

      width =
	(int) (SANE_UNFIX (s->val[OPT_BR_X].w) -
	       SANE_UNFIX (s->val[OPT_TL_X].w));
      length =
	(int) (SANE_UNFIX (s->val[OPT_BR_Y].w) -
	       SANE_UNFIX (s->val[OPT_TL_Y].w));
      xres = s->val[OPT_X_RESOLUTION].w;
      yres = s->val[OPT_Y_RESOLUTION].w;
      DBG (DBG_proc,
	   ">>sane_get_parameters: (W/L)=(%d/%d) (xres/yres)=(%d/%d) mud=%d\n",
	   width, length, xres, yres, s->hw->info.mud);

      /* make best-effort guess at what parameters will look like once scanning starts.  */
      if (xres > 0 && yres > 0 && width > 0 && length > 0)
	{			/* convert from mm to pixels */
	  s->params.pixels_per_line =
	    width * xres / s->hw->info.mud / MM_PER_INCH;
	  s->params.lines = length * yres / s->hw->info.mud / MM_PER_INCH;
	}

      mode = s->val[OPT_SCAN_MODE].s;
      if ((strcmp (mode, SM_LINEART) == 0) ||
	  (strcmp (mode, SM_HALFTONE)) == 0)
	{
	  s->params.format = SANE_FRAME_GRAY;
	  s->params.bytes_per_line = s->params.pixels_per_line / 8;
	  /* if the scanner truncates to the byte boundary, so: chop! */
	  s->params.pixels_per_line = s->params.bytes_per_line * 8;
	  s->params.depth = 1;
	}
      else			/* if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0) */
	{
	  s->params.format = SANE_FRAME_GRAY;
	  s->params.bytes_per_line = s->params.pixels_per_line;
	  s->params.depth = 8;
	}
      s->params.last_frame = SANE_TRUE;
    }
  else
    DBG (DBG_proc, "sane_get_parameters: scanning, so can't get params\n");

  if (params)
    *params = s->params;

  DBG (DBG_proc,
       "%d pixels per line, %d bytes per line, %d lines high, total %lu bytes, "
       "dpi=%ld\n", s->params.pixels_per_line, s->params.bytes_per_line,
       s->params.lines, (u_long) s->bytes_to_read,
       (long) SANE_UNFIX (s->val[OPT_Y_RESOLUTION].w));

  DBG (DBG_proc, "<< sane_get_parameters\n");
  return (SANE_STATUS_GOOD);
}

static SANE_Status
set_window_data (HS2P_Scanner * s, SWD * wbuf)
{
  struct hs2p_window_data *data;
  int i, nwin, id, xres, yres, xmax, ymax;
  long ulx, uly, width, length, number, bytes;
  double offset;

  DBG (DBG_proc, ">> set_window_data: sizeof(*wbuf)=%lu; window len=%lu\n",
       (u_long) sizeof (*wbuf), (u_long) sizeof (wbuf->data));

  /* initialize our window buffer with zeros */
  DBG (DBG_proc, ">> set_window_data: CLEARING wbuf\n");
  memset (wbuf, 0, sizeof (*wbuf));

  /* Header */
  DBG (DBG_proc,
       ">> set_window_data: writing Window Descriptor Length =%lu\n",
       (u_long) sizeof (wbuf->data));
  _lto2b (sizeof (wbuf->data), &wbuf->hdr.len[0]);

  /* X-Axis Resolution 100-800dpi in 1 dpi steps */
  xres = s->val[OPT_X_RESOLUTION].w;
  if (xres < s->hw->info.resMinX || xres > s->hw->info.resMaxX)
    {
      DBG (DBG_error, "XRESOLUTION %d IS NOT WITHIN [%d, %d]\n", xres,
	   s->hw->info.resMinX, s->hw->info.resMaxX);
      return (SANE_STATUS_INVAL);
    }

  /* Y-Axis Resolution 100-800dpi in 1 dpi steps */
  yres = s->val[OPT_Y_RESOLUTION].w;
  if (yres < s->hw->info.resMinY || yres > s->hw->info.resMaxY)
    {
      DBG (DBG_error, "YRESOLUTION %d IS NOT WITHIN [%d, %d]\n", yres,
	   s->hw->info.resMinY, s->hw->info.resMaxY);
      return (SANE_STATUS_INVAL);
    }

  ulx = (long) SANE_UNFIX (s->val[OPT_TL_X].w);
  uly = (long) SANE_UNFIX (s->val[OPT_TL_Y].w);
  DBG (DBG_info, "set_window_data: upperleft=(%ld,%ld)\n", ulx, uly);

  width = (long) SANE_UNFIX (s->val[OPT_BR_X].w - s->val[OPT_TL_X].w);	/* Window Width */
  length = (long) SANE_UNFIX (s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w);	/* Window Length */
  DBG (DBG_info, "set_window_data: WxL= %ld x %ld\n", width, length);

  /* NOTE: the width in inches converted to byte unit must be the following values or less
   * Binary:       620 bytes
   * 4-bits gray: 2480 bytes
   * 8-bits gray: 4960 bytes
   */
  if (!strcmp (s->val[OPT_SCAN_MODE].s, SM_LINEART))
    {
      bytes = (width / MM_PER_INCH) * (s->val[OPT_X_RESOLUTION].w / 8.0);
      if (bytes > 620)
	{
	  DBG (DBG_error,
	       "width in pixels too large: width=%ld x-resolution=%d bytes=%ld\n",
	       width, s->val[OPT_X_RESOLUTION].w, bytes);
	  return (SANE_STATUS_INVAL);
	}
    }
  else if (!strcmp (s->val[OPT_SCAN_MODE].s, SM_4BITGRAY))
    {
      bytes = (width / MM_PER_INCH) * (s->val[OPT_X_RESOLUTION].w / 2.0);
      if (bytes > 2480)
	{
	  DBG (DBG_error,
	       "width in pixels too large: width=%ld x-resolution=%d bytes=%ld\n",
	       width, s->val[OPT_X_RESOLUTION].w, bytes);
	  return (SANE_STATUS_INVAL);
	}
    }
  else if (!strcmp (s->val[OPT_SCAN_MODE].s, SM_8BITGRAY))
    {
      bytes = (width / MM_PER_INCH) * (s->val[OPT_X_RESOLUTION].w);
      if (bytes > 4960)
	{
	  DBG (DBG_error,
	       "width in pixels too large: width=%ld x-resolution=%d bytes=%ld\n",
	       width, s->val[OPT_X_RESOLUTION].w, bytes);
	  return (SANE_STATUS_INVAL);
	}
    }


  if (strcmp (s->val[OPT_SCAN_SOURCE].s, scan_source_list[ADF]) == 0)
    {
      offset = (SANE_UNFIX (s->hw->info.x_range.max) - width) / 2.0;
      DBG (DBG_info, "set_window_data: ADF origin offset=%f\n", offset);

      ulx += (long) offset;
    }


  if (strcmp (s->val[OPT_SCAN_SOURCE].s, scan_source_list[FB]) == 0)
    {				/* FB */
      xmax = 298;		/*mm */
      ymax = 432;
    }
  else
    {				/* ADF */
      xmax = 298;
      ymax = 2000;
    }

  /* Boundary Conditions when BMU = MM */
  number = ulx + width;
  if (number <= 0 || number > xmax)
    {
      DBG (DBG_error, "NOT WITHIN BOUNDS: ulx=%ld width=%ld sum=%ld\n",
	   ulx, width, number);
      return (SANE_STATUS_INVAL);
    }
  number = uly + length;
  if (number <= 0 || number > ymax)
    {
      DBG (DBG_error, "NOT WITHIN BOUNDS: uly=%ld length=%ld sum=%ld\n",
	   uly, length, number);
      return (SANE_STATUS_INVAL);
    }



  /* For each window (up to 2 if we're duplexing) */
  nwin = (s->val[OPT_DUPLEX].w == SANE_TRUE) ? 2 : 1;
  for (i = 0; i < nwin; i++)
    {
      data = &(wbuf->data[i]);
      data->window_id = i;
      data->auto_bit &= 0xFE;	/* Auto bit set to 0 since auto function isn't supported */

      _lto2b (xres, &data->xres[0]);	/* Set X resolution */
      _lto2b (yres, &data->yres[0]);	/* Set Y resolution */

      _lto4b (ulx, &data->ulx[0]);	/* X-Axis Upper Left */
      _lto4b (uly, &data->uly[0]);	/* Y-Axis Upper Left */

      _lto4b (width, &data->width[0]);	/* Window Width */
      _lto4b (length, &data->length[0]);	/* Window Length */






      data->brightness = s->val[OPT_BRIGHTNESS].w;	/* black-white: 1-255; 0 is default 128 */
      data->threshold = s->val[OPT_THRESHOLD].w;	/* light-dark:  1-255; 0 is default 128 */
      data->contrast = s->val[OPT_CONTRAST].w;	/* low-high:    1-255: 0 is default 128 */
      if (data->brightness == 128)
	data->brightness = 0;
      if (data->threshold == 128)
	data->threshold = 0;
      if (data->contrast == 128)
	data->contrast = 0;

      data->image_composition = s->image_composition;
      data->bpp = s->bpp = s->params.depth;

      /* Byte 27, 347 Halftone Code: if HALFTONE, then either DITHER or ERROR_DIFFUSION */
      if (s->image_composition == HALFTONE)
	{			/* Then let's use pattern selected by user */
	  data->halftone_code =
	    (get_halftone_code_id (s->val[OPT_HALFTONE_CODE].s) ==
	     0) ? DITHER : ERROR_DIFFUSION;
	  data->halftone_id =
	    get_halftone_pattern_val (s->val[OPT_HALFTONE_PATTERN].s);
	}
      else
	{
	  data->halftone_code = DITHER;	/* 00H reserved */
	  data->halftone_id = 0x01;	/* 00H reserved */
	}



      /* Byte 29, 349: RIF:reserved:padding type */
      if (data->image_composition == LINEART
	  || data->image_composition == HALFTONE)
	{
	  if (s->val[OPT_NEGATIVE].w)
	    data->byte29 |= (1 << 7);	/* set bit 7 */
	  else
	    data->byte29 &= ~(1 << 7);	/* unset bit 7 */
	}
      /* Padding Type */
      data->byte29 |=
	(paddingtype[get_paddingtype_id (s->val[OPT_PADDING_TYPE].s)].
	 val & 0x07);

      /* Bit Ordering:
       *     Manual Says DEFAULT: [1111 1111][1111 1000]
       * Bits15-8 reserved;
       * Bit7: '0'-Normal '1'-Mirroring
       * Bit6-4: Reserved
       * Bit3: '0'-arrangement from MSB in grayscale mode
       *       '1'-arrangement from LSB in grayscale mode
       *    2: '0'-unpacked 4-bits grayscale [DEFAULT]
       *       '1'-packed 4-bits grayscale
       *    1: '0'-output from LSB of each word [DEFAULT]
       *       '1'-output from MSB of each word
       *    0: '0'-output from bit 0 of each byte [DEFAULT]
       *       '1'-output from bit 7 of each byte
       */
      _lto2b (0x007, &data->bit_ordering[0]);	/* Set to Packed4bitGray, MSB, MSbit */

      /* Compression Type and Argument NOT SUPPORTED in this scanner */
      data->compression_type = 0x00;
      data->compression_arg = 0x02;

      /* Byte42:  MRIF:Filtering:GammaID */
      if (data->image_composition == GRAYSCALE)
	{
	  if (s->val[OPT_NEGATIVE].w)
	    data->byte42 &= ~(1 << 7);	/* unset bit 7 */
	  else
	    data->byte42 |= (1 << 7);	/* set bit 7 */
	  data->byte42 |= (get_grayfilter_val (s->val[OPT_GRAYFILTER].s) & (7 << 4));	/* set bits 6-4 to GRAYFILTER */
	}
      else
	{
	  data->byte42 &= ~(1 << 7);	/* unset bit 7 */
	  data->byte42 &= ~(7 << 4);	/* unset bits 6-4 */
	}
      /* Bytes 45, 365 Binary Filtering for lineart and halftone can be set when option IPU is installed */
      if ((id = get_noisematrix_id (s->val[OPT_NOISEREMOVAL].s)) != 0)
	{
	  data->binary_filtering |= (1 << 7);	/* set bit 7 */
	  data->binary_filtering |= noisematrix[id].val;	/* 00H, 01H, 02H; 03H:Reserved */
	}
      if (s->val[OPT_SMOOTHING].w == SANE_TRUE)
	data->binary_filtering |= (1 << 6);	/* set bit 6 */

      /* Automatic separation, automatic binarization, and SECTION is available if Image Processing Unit is installed */
      if (s->hw->info.hasIPU)
	{
	  /* Byte 48: Automatic Separation */
	  data->automatic_separation =
	    get_auto_separation_val (s->val[OPT_AUTOSEP].s);
	  /* Byte 50: Automatic Binarization */
	  data->automatic_binarization =
	    get_auto_binarization_val (s->val[OPT_AUTOBIN].s);
	  /* fill in values for each section
	     for(j=0; j<NumSec; j++){
	     wbuf[i].winsec[j].ulx
	     wbuf[i].winsec[j].uly
	     wbuf[i].winsec[j].width
	     wbuf[i].winsec[j].length
	     wbuf[i].winsec[j].binary_filtering
	     wbuf[i].winsec[j].threshold
	     wbuf[i].winsec[j].image_composition
	     wbuf[i].winsec[j].halftone_id
	     wbuf[i].winsec[j].halftone_arg
	     }
	   */
	}
    }
  DBG (DBG_proc, "<< set_window_data\n");
  return (SANE_STATUS_GOOD);
}

SANE_Status
sane_start (SANE_Handle handle)	/* begin scanning */
{
  HS2P_Scanner *s = handle;
  SANE_Status status;
  SWD wbuf;			/* Set Window Data: hdr + data */
  GWD gbuf;			/* Get Window Data: hdr + data */
  SANE_Byte mode, prefeed, mwt = 0;

  DBG (DBG_proc, ">> sane_start\n");
  s->cancelled = SANE_FALSE;

  if (s->another_side)
    {
      /* Number of bytes to read for one side of sheet */
      s->bytes_to_read = s->params.bytes_per_line * s->params.lines;
      DBG (DBG_info,
	   "SIDE#2 %d pixels per line, %d bytes, %d lines high, dpi=%d\n",
	   s->params.pixels_per_line, s->params.bytes_per_line,
	   s->params.lines, (int) s->val[OPT_Y_RESOLUTION].w);
      s->scanning = SANE_TRUE;
      s->cancelled = SANE_FALSE;
      s->another_side = SANE_FALSE;	/* This is side 2, so no more sides */
      DBG (DBG_proc, "<< sane_start\n");
      return (SANE_STATUS_GOOD);
    }

  if (s->scanning)
    {
      DBG (DBG_info, "sane_start: device busy\n");
      return SANE_STATUS_DEVICE_BUSY;
    }

  /* Let's start a new scan */

  if ((status = sane_get_parameters (s, 0)) != SANE_STATUS_GOOD)
    {				/* get preliminary parameters */
      DBG (DBG_error, "sane_start: sane_get_parameters failed: %s\n",
	   sane_strstatus (status));
      return (status);
    }

  DBG (DBG_info, ">> sane_start: trying to open: name=\"%s\" fd=%d\n",
       s->hw->sane.name, s->fd);
  if ((status =
       sanei_scsi_open (s->hw->sane.name, &s->fd, &sense_handler,
			&(s->hw->sense_data))) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sane_start: open of %s failed: %d %s\n",
	   s->hw->sane.name, status, sane_strstatus (status));
      return (status);
    }
  DBG (DBG_info, ">>sane_start: OPENED \"%s\" fd=%d\n", s->hw->sane.name,
       s->fd);

  if ((status = test_unit_ready (s->fd)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sane_start: test_unit_ready() failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return status;
    }


  if ((status = reserve_unit (s->fd)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sane_start: reserve_unit() failed: %s\n",
	   sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  /* NOW SET UP SCANNER ONCE PER BATCH */

  DBG (DBG_info, "sane_start: setting basic measurement unit to mm\n");
  if ((status = set_basic_measurement_unit (s->fd, s->hw->info.bmu)))
    {
      DBG (DBG_error, "set_basic_measurment_unit failed: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  if (get_scan_source_id (s->val[OPT_SCAN_SOURCE].s) == 0)
    {
      mode = FLATBED;
    }
  else
    {
      mode = (s->val[OPT_DUPLEX].w) ? DUPLEX : SIMPLEX;
    }

  prefeed = s->val[OPT_PREFEED].w ? 0x04 : 0x00;
  DBG (DBG_info, "sane_start: setting scan source to %d %s\n", mode,
       (SANE_String) s->val[OPT_SCAN_SOURCE].s);
  DBG (DBG_info, "sane_start: setting prefeed to %d\n", prefeed);
  if ((status =
       set_adf_control (s->fd, &mode, &prefeed, &mwt)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "sane_start: error set_adf_control: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (SANE_STATUS_INVAL);
    }


  DBG (DBG_info, "sane_start: setting endorser control to %d\n",
       s->val[OPT_ENDORSER].w);
  if ((status =
       set_endorser_control (s->fd,
			     &s->val[OPT_ENDORSER].w)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "set_endorser_control failed: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
  if (s->val[OPT_ENDORSER].w)
    {
      DBG (DBG_info, "sane_start: setting endorser string to %s\n",
	   s->val[OPT_ENDORSER_STRING].s);
      if ((status =
	   set_endorser_string (s->fd,
				(SANE_String) s->val[OPT_ENDORSER_STRING].
				s)) != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "set_endorser_string failed: %s\n",
	       sane_strstatus (status));
	  release_unit (s->fd);
	  sanei_scsi_close (s->fd);
	  s->fd = -1;
	  return (status);
	}
    }

  DBG (DBG_info, "sane_start: setting scan_wait_mode to %d\n",
       s->val[OPT_SCAN_WAIT_MODE].w);
  if ((status =
       set_scan_wait_mode (s->fd,
			   s->val[OPT_SCAN_WAIT_MODE].w)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "set_scan_wait_mode failed: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
  DBG (DBG_info, "sane_start: setting white_balance to %d\n",
       s->val[OPT_WHITE_BALANCE].w);
  if ((status =
       set_white_balance (s->fd,
			  &s->val[OPT_WHITE_BALANCE].w)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "set_white_balance failed: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  if (s->val[OPT_CUSTOM_GAMMA].b)
    {				/* Custom Gamma needs to be sent to scanner */
      DBG (DBG_info, "sane_start: setting custom gamma\n");
      if ((status = hs2p_send_gamma (s)))
	{
	  DBG (DBG_error, "hs2p_send_gamma failed: %s\n",
	       sane_strstatus (status));
	  release_unit (s->fd);
	  sanei_scsi_close (s->fd);
	  s->fd = -1;
	  return (status);
	}
      /* We succeeded, so we don't need to upload this vector again (unless user modifies gamma table) */
      s->val[OPT_CUSTOM_GAMMA].b = SANE_FALSE;
    }


  DBG (DBG_info, "sane_start: filling in window data buffer \n");
  if ((status = set_window_data (s, &wbuf)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "set_window_data failed: %s\n",
	   sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
  DBG (DBG_info, "sane_start: sending SET WINDOW DATA\n");
  if ((status = set_window (s->fd, &wbuf)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "SET WINDOW DATA failed: %s\n",
	   sane_strstatus (status));
      print_window_data (&wbuf);
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
  DBG (DBG_info, "sane_start: sending GET WINDOW\n");
  memset (&gbuf, 0, sizeof (gbuf));	/* CLEAR wbuf */
  if ((status = get_window (s->fd, &gbuf)) != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "GET WINDOW failed: %s\n", sane_strstatus (status));
      release_unit (s->fd);
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  /* DONE WITH SETTING UP SCANNER ONCE PER BATCH */

  s->EOM = SANE_FALSE;
  if (mode != FLATBED)
    {
      if ((status =
	   get_hs2p_data (s, DATA_TYPE_ADF_STATUS,
			  DATA_TYPE_EOL)) != SANE_STATUS_GOOD)
	{
	  DBG (DBG_error, "sane_start: error reading adf_status:  %s\n",
	       sane_strstatus (status));
	  return (status);
	}
      if ((s->data.adf_status & 0x01) == 0x01)
	{
	  DBG (DBG_warning, "sane_start: No document on ADF\n");
	  return (SANE_STATUS_NO_DOCS);
	}
      else if ((s->data.adf_status & 0x02) == 0x02)
	{
	  DBG (DBG_warning, "sane_start: ADF cover open!\n");
	  return (SANE_STATUS_COVER_OPEN);
	}
    }


  status = trigger_scan (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "start of scan failed: %s\n", sane_strstatus (status));
      print_window_data (&wbuf);
      /* this line introduced not to freeze xscanimage */
      /*do_cancel (s); */
      return status;
    }
  /* Wait for scanner to become ready to transmit data */
  status = hs2p_wait_ready (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (DBG_error, "GET DATA STATUS failed: %s\n",
	   sane_strstatus (status));
      return (status);
    }

  s->another_side = (mode == DUPLEX) ? SANE_TRUE : SANE_FALSE;
  /* Number of bytes to read for one side of sheet */
  DBG (DBG_info, "ANOTHER SIDE = %s\n", (s->another_side) ? "TRUE" : "FALSE");
  s->bytes_to_read = s->params.bytes_per_line * s->params.lines;
  DBG (DBG_info, "%d pixels per line, %d bytes, %d lines high, dpi=%d\n",
       s->params.pixels_per_line, s->params.bytes_per_line,
       s->params.lines, (int) s->val[OPT_Y_RESOLUTION].w);
  s->scanning = SANE_TRUE;
  s->cancelled = SANE_FALSE;

  DBG (DBG_proc, "<< sane_start\n");
  return (SANE_STATUS_GOOD);
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  HS2P_Scanner *s = handle;
  SANE_Status status;
  size_t nread, bytes_requested, i, start;
  SANE_Byte color;
  DBG (DBG_proc, ">> sane_read\n");

  *len = 0;

  DBG (DBG_info, "sane_read: bytes left to read: %ld\n",
       (u_long) s->bytes_to_read);

  if (s->bytes_to_read == 0)
    {				/* We've reached the end of one side of sheet */
      if (!s->another_side)
	{
	  do_cancel (s);
	  return (SANE_STATUS_EOF);
	}
      else
	{
	  /* let frontend call sane_start again to reset bytes_to_read */
	  DBG (DBG_proc, "<< sane_read: getting another side\n");
	  return (SANE_STATUS_EOF);
	}
    }

  if (s->cancelled)
    {
      DBG (DBG_info, "sane_read: cancelled!\n");
      return SANE_STATUS_CANCELLED;
    }
  if (!s->scanning)
    {
      DBG (DBG_info, "sane_read: scanning is false!\n");
      return (do_cancel (s));
    }

  nread = max_len;
  if (nread > s->bytes_to_read)
    nread = s->bytes_to_read;
  bytes_requested = nread;
  start = 0;

pad:
  if (s->EOM)
    {
      if (s->val[OPT_PADDING].w)
	{
	  DBG (DBG_info, "sane_read s->EOM padding from %ld to %ld\n",
	       (u_long) start, (u_long) bytes_requested);
	  color = (s->val[OPT_NEGATIVE].w) ? 0 : 255;
	  /* pad to requested length */
	  for (i = start; i < bytes_requested; i++)
	    buf[i] = color;
	  nread = bytes_requested;	/* we've padded to bytes_requested */
	  *len = nread;
	  s->bytes_to_read -= nread;
	}
      else			/* TRUNCATE: should never reach here */
	{
	  *len = nread;
	  s->bytes_to_read = 0;	/* EOM */
	}
    }
  else
    {
      DBG (DBG_info, "sane_read: trying to read %ld bytes\n", (u_long) nread);
      status = read_data (s->fd, buf, &nread, DATA_TYPE_IMAGE, DTQ);
      switch (status)
	{
	case SANE_STATUS_NO_DOCS:
	  DBG (DBG_error, "sane_read: End-Of-Medium detected\n");
	  s->EOM = SANE_TRUE;
	  /*
	   * If status != SANE_STATUS_GOOD, then sense_handler() has already
	   * been called and the sanei_* functions have already gotten the
	   * sense data buffer (which apparently clears the error condition)
	   * so the following doesn't work:
	   get_sense_data (s->fd, &(s->hw->sense_data));
	   print_sense_data (&(s->hw->sense_data));
	   */
	  start = (isset_ILI (s->hw->sense_data)) ?	/* Invalid Length Indicator */
	    bytes_requested - _4btol (s->hw->sense_data.information) : nread;
	  goto pad;
	  break;
	case SANE_STATUS_GOOD:
	  *len = nread;
	  s->bytes_to_read -= nread;
	  break;
	default:
	  DBG (DBG_error, "sane_read: read error\n");
	  do_cancel (s);
	  return (SANE_STATUS_IO_ERROR);
	}
    }
  DBG (DBG_proc, "<< sane_read\n");
  return (SANE_STATUS_GOOD);
}


void
sane_cancel (SANE_Handle handle)
{
  HS2P_Scanner *s = handle;
  DBG (DBG_proc, ">> sane_cancel\n");

  if (s->scanning)
    {				/* if batchmode is enabled, then call set_window to abort the batch
				   if (_OPT_VAL_WORD(s, OPT_BATCH) == SANE_TRUE) {
				   DBG(5, "sane_cancel: calling set_window to abort batch\n");
				   set_window(s, BH_BATCH_ABORT);
				   }   */
      do_cancel (s);
    }



  DBG (DBG_proc, "<< sane_cancel\n");
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  DBG (DBG_proc, ">> sane_set_io_mode (handle = %p, non_blocking = %d)\n",
       handle, non_blocking);
  DBG (DBG_proc, "<< sane_set_io_mode\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
#ifdef NONBLOCKSUPPORTED
  HS2P_Scanner *s = handle;
#endif
  DBG (DBG_proc, ">> sane_get_select_fd (handle = %p, fd = %p)\n", handle,
       (void *) fd);

#ifdef NONBLOCKSUPPORTED
  if (s->fd < 0)
    {
      DBG (DBG_proc, "<< sane_get_select_fd\n");
      return SANE_STATUS_INVAL;
    }
  *fd = s->fd;
  return SANE_STATUS_GOOD;
#else
  (void) handle;
  (void) fd;			/* get rid of compiler warning */
  DBG (DBG_proc, "<< sane_get_select_fd\n");
  return SANE_STATUS_UNSUPPORTED;
#endif
}
