/* sane - Scanner Access Now Easy.

   Copyright (C) 2002 Sergey Vlasov <vsu@altlinux.ru>
   Copyright (C) 2002 - 2007 Henning Geinitz <sane@geinitz.org>
   Copyright (C) 2009 Stéphane Voltz <stef.dev@free.fr> for sheetfed
                      calibration code.

   This file is part of the SANE package.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
   
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.
   
   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.
   
   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.
   
   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.
   
   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

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

/*
 * SANE backend for Grandtech GT-6801 and GT-6816 based scanners
 */

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

#define BUILD 84
#define MAX_DEBUG
#define WARMUP_TIME 60
#define CALIBRATION_HEIGHT 2.5
#define SHORT_TIMEOUT (1 * 1000)
#define LONG_TIMEOUT (30 * 1000)

/* Use a reader process if possible (usually faster) */
#if defined (HAVE_SYS_SHM_H) && (!defined (USE_PTHREAD)) && (!defined (HAVE_OS2_H))
#define USE_FORK
#define SHM_BUFFERS 10
#endif

#define TUNE_CALIBRATOR

/* Send coarse white or black calibration to stdout */
#if 0
#define SAVE_WHITE_CALIBRATION
#endif
#if 0
#define SAVE_BLACK_CALIBRATION
#endif

/* Debug calibration, print total brightness of the scanned image */
#if 0
#define DEBUG_BRIGHTNESS
#endif

/* Debug calibration, print black mark values */
#if 0
#define DEBUG_BLACK
#endif

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include <dirent.h>

#include "../include/_stdint.h"

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

#define BACKEND_NAME gt68xx

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

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

#include "gt68xx.h"
#include "gt68xx_high.c"
#include "gt68xx_devices.c"

static SANE_Int num_devices = 0;
static GT68xx_Device *first_dev = 0;
static GT68xx_Scanner *first_handle = 0;
static const SANE_Device **devlist = 0;
/* Array of newly attached devices */
static GT68xx_Device **new_dev = 0;
/* Length of new_dev array */
static SANE_Int new_dev_len = 0;
/* Number of entries alloced for new_dev */
static SANE_Int new_dev_alloced = 0;
/* Is this computer little-endian ?*/
SANE_Bool little_endian;
SANE_Bool debug_options = SANE_FALSE;

static SANE_String_Const mode_list[] = {
  SANE_VALUE_SCAN_MODE_COLOR,
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_LINEART,
  0
};

static SANE_String_Const gray_mode_list[] = {
  GT68XX_COLOR_RED,
  GT68XX_COLOR_GREEN,
  GT68XX_COLOR_BLUE,
  0
};

static SANE_String_Const source_list[] = {
  SANE_I18N ("Flatbed"),
  SANE_I18N ("Transparency Adapter"),
  0
};

static SANE_Range x_range = {
  SANE_FIX (0.0),               /* minimum */
  SANE_FIX (216.0),             /* maximum */
  SANE_FIX (0.0)                /* quantization */
};

static SANE_Range y_range = {
  SANE_FIX (0.0),               /* minimum */
  SANE_FIX (299.0),             /* maximum */
  SANE_FIX (0.0)                /* quantization */
};


static const SANE_Range offset_range = {
  -63,                          /* minimum */
  63,                           /* maximum */
  1                             /* quantization */
};

static SANE_Range gamma_range = {
  SANE_FIX (0.01),              /* minimum */
  SANE_FIX (5.0),               /* maximum */
  SANE_FIX (0.01)               /* quantization */
};

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

/* Test if this machine is little endian (from coolscan.c) */
static SANE_Bool
calc_little_endian (void)
{
  SANE_Int testvalue = 255;
  uint8_t *firstbyte = (uint8_t *) & testvalue;

  if (*firstbyte == 255)
    return SANE_TRUE;
  return SANE_FALSE;
}

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

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

static SANE_Status
get_afe_values (SANE_String_Const cp, GT68xx_AFE_Parameters * afe)
{
  SANE_Char *word, *end;
  int i;

  for (i = 0; i < 6; i++)
    {
      cp = sanei_config_get_string (cp, &word);
      if (word && *word)
        {
          long int long_value;
          errno = 0;
          long_value = strtol (word, &end, 0);

          if (end == word)
            {
              DBG (5, "get_afe_values: can't parse %d. parameter `%s'\n",
                   i + 1, word);
              free (word);
              word = 0;
              return SANE_STATUS_INVAL;
            }
          else if (errno)
            {
              DBG (5, "get_afe_values: can't parse %d. parameter `%s' "
                   "(%s)\n", i + 1, word, strerror (errno));
              free (word);
              word = 0;
              return SANE_STATUS_INVAL;
            }
          else if (long_value < 0)
            {
              DBG (5, "get_afe_values: %d. parameter < 0 (%d)\n", i + 1,
                   (int) long_value);
              free (word);
              word = 0;
              return SANE_STATUS_INVAL;
            }
          else if (long_value > 0x3f)
            {
              DBG (5, "get_afe_values: %d. parameter > 0x3f (%d)\n", i + 1,
                   (int) long_value);
              free (word);
              word = 0;
              return SANE_STATUS_INVAL;
            }
          else
            {
              DBG (5, "get_afe_values: %d. parameter set to 0x%02x\n", i + 1,
                   (int) long_value);
              switch (i)
                {
                case 0:
                  afe->r_offset = (SANE_Byte) long_value;
                  break;
                case 1:
                  afe->r_pga = (SANE_Byte) long_value;
                  break;
                case 2:
                  afe->g_offset = (SANE_Byte) long_value;
                  break;
                case 3:
                  afe->g_pga = (SANE_Byte) long_value;
                  break;
                case 4:
                  afe->b_offset = (SANE_Byte) long_value;
                  break;
                case 5:
                  afe->b_pga = (SANE_Byte) long_value;
                  break;
                }
              free (word);
              word = 0;
            }
        }
      else
        {
          DBG (5, "get_afe_values: option `afe' needs 6  parameters\n");
          return SANE_STATUS_INVAL;
        }
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
setup_scan_request (GT68xx_Scanner * s, GT68xx_Scan_Request * scan_request)
{

  if (s->dev->model->flags & GT68XX_FLAG_MIRROR_X)
    scan_request->x0 =
      s->opt[OPT_TL_X].constraint.range->max - s->val[OPT_BR_X].w;
  else
    scan_request->x0 = s->val[OPT_TL_X].w;
  scan_request->y0 = s->val[OPT_TL_Y].w;
  scan_request->xs = s->val[OPT_BR_X].w - s->val[OPT_TL_X].w;
  scan_request->ys = s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w;

  if (s->val[OPT_FULL_SCAN].w == SANE_TRUE)
    {
      scan_request->x0 -= s->dev->model->x_offset;
      scan_request->y0 -= (s->dev->model->y_offset);
      scan_request->xs += s->dev->model->x_offset;
      scan_request->ys += s->dev->model->y_offset;
    }

  scan_request->xdpi = s->val[OPT_RESOLUTION].w;
  if (scan_request->xdpi > s->dev->model->optical_xdpi)
    scan_request->xdpi = s->dev->model->optical_xdpi;
  scan_request->ydpi = s->val[OPT_RESOLUTION].w;

  if (IS_ACTIVE (OPT_BIT_DEPTH) && !s->val[OPT_PREVIEW].w)
    scan_request->depth = s->val[OPT_BIT_DEPTH].w;
  else
    scan_request->depth = 8;

  if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
    scan_request->color = SANE_TRUE;
  else
    scan_request->color = SANE_FALSE;

  if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_LINEART) == 0)
    {
      SANE_Int xs =
        SANE_UNFIX (scan_request->xs) * scan_request->xdpi / MM_PER_INCH +
        0.5;

      if (xs % 8)
        {
          scan_request->xs =
            SANE_FIX ((xs - (xs % 8)) * MM_PER_INCH / scan_request->xdpi);
          DBG (5, "setup_scan_request: lineart mode, %d pixels %% 8 = %d\n",
               xs, xs % 8);
        }
    }

  scan_request->calculate = SANE_FALSE;
  scan_request->lamp = SANE_TRUE;
  scan_request->mbs = SANE_FALSE;

  if (strcmp (s->val[OPT_SOURCE].s, "Transparency Adapter") == 0)
    scan_request->use_ta = SANE_TRUE;
  else
    scan_request->use_ta = SANE_FALSE;

  return SANE_STATUS_GOOD;
}

static SANE_Status
calc_parameters (GT68xx_Scanner * s)
{
  SANE_String val;
  SANE_Status status = SANE_STATUS_GOOD;
  GT68xx_Scan_Request scan_request;
  GT68xx_Scan_Parameters scan_params;

  DBG (5, "calc_parameters: start\n");
  val = s->val[OPT_MODE].s;

  s->params.last_frame = SANE_TRUE;
  if (strcmp (val, SANE_VALUE_SCAN_MODE_GRAY) == 0
      || strcmp (val, SANE_VALUE_SCAN_MODE_LINEART) == 0)
    s->params.format = SANE_FRAME_GRAY;
  else                          /* Color */
    s->params.format = SANE_FRAME_RGB;

  setup_scan_request (s, &scan_request);
  scan_request.calculate = SANE_TRUE;

  status = gt68xx_device_setup_scan (s->dev, &scan_request, SA_SCAN,
                                     &scan_params);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "calc_parameters: gt68xx_device_setup_scan returned: %s\n",
           sane_strstatus (status));
      return status;
    }

  if (strcmp (val, SANE_VALUE_SCAN_MODE_LINEART) == 0)
    s->params.depth = 1;
  else
    s->params.depth = scan_params.depth;

  s->params.lines = scan_params.pixel_ys;
  s->params.pixels_per_line = scan_params.pixel_xs;
  /* Inflate X if necessary */
  if (s->val[OPT_RESOLUTION].w > s->dev->model->optical_xdpi)
    s->params.pixels_per_line *=
      (s->val[OPT_RESOLUTION].w / s->dev->model->optical_xdpi);
  s->params.bytes_per_line = s->params.pixels_per_line;
  if (s->params.depth > 8)
    {
      s->params.depth = 16;
      s->params.bytes_per_line *= 2;
    }
  else if (s->params.depth == 1)
    s->params.bytes_per_line /= 8;

  if (s->params.format == SANE_FRAME_RGB)
    s->params.bytes_per_line *= 3;

  DBG (5, "calc_parameters: exit\n");
  return status;
}

static SANE_Status
create_bpp_list (GT68xx_Scanner * s, SANE_Int * bpp)
{
  int count;

  for (count = 0; bpp[count] != 0; count++)
    ;
  s->bpp_list[0] = count;
  for (count = 0; bpp[count] != 0; count++)
    {
      s->bpp_list[s->bpp_list[0] - count] = bpp[count];
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
init_options (GT68xx_Scanner * s)
{
  SANE_Int option, count;
  SANE_Status status;
  SANE_Word *dpi_list;
  GT68xx_Model *model = s->dev->model;
  SANE_Bool has_ta = SANE_FALSE;

  DBG (5, "init_options: start\n");

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

  for (option = 0; option < NUM_OPTIONS; ++option)
    {
      s->opt[option].size = sizeof (SANE_Word);
      s->opt[option].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
  s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
  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].type = SANE_TYPE_INT;
  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;

  /* "Mode" group: */
  s->opt[OPT_MODE_GROUP].title = SANE_I18N ("Scan Mode");
  s->opt[OPT_MODE_GROUP].desc = "";
  s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_MODE_GROUP].size = 0;
  s->opt[OPT_MODE_GROUP].cap = 0;
  s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* scan mode */
  s->opt[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  s->opt[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  s->opt[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  s->opt[OPT_MODE].type = SANE_TYPE_STRING;
  s->opt[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_MODE].size = max_string_size (mode_list);
  s->opt[OPT_MODE].constraint.string_list = mode_list;
  s->val[OPT_MODE].s = strdup (SANE_VALUE_SCAN_MODE_GRAY);

  /* scan mode */
  s->opt[OPT_GRAY_MODE_COLOR].name = "gray-mode-color";
  s->opt[OPT_GRAY_MODE_COLOR].title = SANE_I18N ("Gray mode color");
  s->opt[OPT_GRAY_MODE_COLOR].desc =
    SANE_I18N ("Selects which scan color is used "
               "gray mode (default: green).");
  s->opt[OPT_GRAY_MODE_COLOR].type = SANE_TYPE_STRING;
  s->opt[OPT_GRAY_MODE_COLOR].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_GRAY_MODE_COLOR].size = max_string_size (gray_mode_list);
  s->opt[OPT_GRAY_MODE_COLOR].constraint.string_list = gray_mode_list;
  s->val[OPT_GRAY_MODE_COLOR].s = strdup (GT68XX_COLOR_GREEN);

  /* scan source */
  s->opt[OPT_SOURCE].name = SANE_NAME_SCAN_SOURCE;
  s->opt[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
  s->opt[OPT_SOURCE].desc = SANE_DESC_SCAN_SOURCE;
  s->opt[OPT_SOURCE].type = SANE_TYPE_STRING;
  s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_SOURCE].size = max_string_size (source_list);
  s->opt[OPT_SOURCE].constraint.string_list = source_list;
  s->val[OPT_SOURCE].s = strdup ("Flatbed");
  status = gt68xx_device_get_ta_status (s->dev, &has_ta);
  if (status != SANE_STATUS_GOOD || !has_ta)
    DISABLE (OPT_SOURCE);

  /* 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].unit = SANE_UNIT_NONE;
  s->opt[OPT_PREVIEW].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_PREVIEW].w = SANE_FALSE;

  /* lamp on */
  s->opt[OPT_LAMP_OFF_AT_EXIT].name = SANE_NAME_LAMP_OFF_AT_EXIT;
  s->opt[OPT_LAMP_OFF_AT_EXIT].title = SANE_TITLE_LAMP_OFF_AT_EXIT;
  s->opt[OPT_LAMP_OFF_AT_EXIT].desc = SANE_DESC_LAMP_OFF_AT_EXIT;
  s->opt[OPT_LAMP_OFF_AT_EXIT].type = SANE_TYPE_BOOL;
  s->opt[OPT_LAMP_OFF_AT_EXIT].unit = SANE_UNIT_NONE;
  s->opt[OPT_LAMP_OFF_AT_EXIT].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_LAMP_OFF_AT_EXIT].w = SANE_TRUE;
  if (s->dev->model->is_cis && !(s->dev->model->flags & GT68XX_FLAG_CIS_LAMP))
    DISABLE (OPT_LAMP_OFF_AT_EXIT);

  /* bit depth */
  s->opt[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH;
  s->opt[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
  s->opt[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
  s->opt[OPT_BIT_DEPTH].type = SANE_TYPE_INT;
  s->opt[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->opt[OPT_BIT_DEPTH].size = sizeof (SANE_Word);
  s->opt[OPT_BIT_DEPTH].constraint.word_list = 0;
  s->opt[OPT_BIT_DEPTH].constraint.word_list = s->bpp_list;
  RIE (create_bpp_list (s, s->dev->model->bpp_gray_values));
  s->val[OPT_BIT_DEPTH].w = 8;
  if (s->opt[OPT_BIT_DEPTH].constraint.word_list[0] < 2)
    DISABLE (OPT_BIT_DEPTH);

  /* resolution */
  for (count = 0; model->ydpi_values[count] != 0; count++)
    ;
  dpi_list = malloc ((count + 1) * sizeof (SANE_Word));
  if (!dpi_list)
    return SANE_STATUS_NO_MEM;
  dpi_list[0] = count;
  for (count = 0; model->ydpi_values[count] != 0; count++)
    dpi_list[dpi_list[0] - count] = model->ydpi_values[count];
  s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].desc = 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 = dpi_list;
  s->val[OPT_RESOLUTION].w = 300;

  /* backtrack */
  s->opt[OPT_BACKTRACK].name = SANE_NAME_BACKTRACK;
  s->opt[OPT_BACKTRACK].title = SANE_TITLE_BACKTRACK;
  s->opt[OPT_BACKTRACK].desc = SANE_DESC_BACKTRACK;
  s->opt[OPT_BACKTRACK].type = SANE_TYPE_BOOL;
  s->val[OPT_BACKTRACK].w = SANE_FALSE;

  /* "Debug" group: */
  s->opt[OPT_DEBUG_GROUP].title = SANE_I18N ("Debugging Options");
  s->opt[OPT_DEBUG_GROUP].desc = "";
  s->opt[OPT_DEBUG_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_DEBUG_GROUP].size = 0;
  s->opt[OPT_DEBUG_GROUP].cap = 0;
  s->opt[OPT_DEBUG_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
  if (!debug_options)
    DISABLE (OPT_DEBUG_GROUP);

  /* auto warmup */
  s->opt[OPT_AUTO_WARMUP].name = "auto-warmup";
  s->opt[OPT_AUTO_WARMUP].title = SANE_I18N ("Automatic warmup");
  s->opt[OPT_AUTO_WARMUP].desc =
    SANE_I18N ("Warm-up until the lamp's brightness is constant "
               "instead of insisting on 60 seconds warm-up time.");
  s->opt[OPT_AUTO_WARMUP].type = SANE_TYPE_BOOL;
  s->opt[OPT_AUTO_WARMUP].unit = SANE_UNIT_NONE;
  s->opt[OPT_AUTO_WARMUP].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_AUTO_WARMUP].w = SANE_TRUE;
  if ((s->dev->model->is_cis
       && !(s->dev->model->flags & GT68XX_FLAG_CIS_LAMP)) || !debug_options)
    DISABLE (OPT_AUTO_WARMUP);

  /* full scan */
  s->opt[OPT_FULL_SCAN].name = "full-scan";
  s->opt[OPT_FULL_SCAN].title = SANE_I18N ("Full scan");
  s->opt[OPT_FULL_SCAN].desc =
    SANE_I18N ("Scan the complete scanning area including calibration strip. "
               "Be careful. Don't select the full height. For testing only.");
  s->opt[OPT_FULL_SCAN].type = SANE_TYPE_BOOL;
  s->opt[OPT_FULL_SCAN].unit = SANE_UNIT_NONE;
  s->opt[OPT_FULL_SCAN].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_FULL_SCAN].w = SANE_FALSE;
  if (!debug_options)
    DISABLE (OPT_FULL_SCAN);

  /* coarse calibration */
  s->opt[OPT_COARSE_CAL].name = "coarse-calibration";
  s->opt[OPT_COARSE_CAL].title = SANE_I18N ("Coarse calibration");
  s->opt[OPT_COARSE_CAL].desc =
    SANE_I18N ("Setup gain and offset for scanning automatically. If this "
               "option is disabled, options for setting the analog frontend "
               "parameters manually are provided. This option is enabled "
               "by default. For testing only.");
  s->opt[OPT_COARSE_CAL].type = SANE_TYPE_BOOL;
  s->opt[OPT_COARSE_CAL].unit = SANE_UNIT_NONE;
  s->opt[OPT_COARSE_CAL].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_COARSE_CAL].w = SANE_TRUE;
  if (!debug_options)
    DISABLE (OPT_COARSE_CAL);
  if (s->dev->model->flags & GT68XX_FLAG_SHEET_FED)
    {
      s->val[OPT_COARSE_CAL].w = SANE_FALSE;
      DISABLE (OPT_COARSE_CAL);
    }

  /* coarse calibration only once */
  s->opt[OPT_COARSE_CAL_ONCE].name = "coarse-calibration-once";
  s->opt[OPT_COARSE_CAL_ONCE].title =
    SANE_I18N ("Coarse calibration for first scan only");
  s->opt[OPT_COARSE_CAL_ONCE].desc =
    SANE_I18N ("Coarse calibration is only done for the first scan. Works "
               "with most scanners and can save scanning time. If the image "
               "brightness is different with each scan, disable this option. "
               "For testing only.");
  s->opt[OPT_COARSE_CAL_ONCE].type = SANE_TYPE_BOOL;
  s->opt[OPT_COARSE_CAL_ONCE].unit = SANE_UNIT_NONE;
  s->opt[OPT_COARSE_CAL_ONCE].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_COARSE_CAL_ONCE].w = SANE_FALSE;
  if (!debug_options)
    DISABLE (OPT_COARSE_CAL_ONCE);
  if (s->dev->model->flags & GT68XX_FLAG_SHEET_FED)
    DISABLE (OPT_COARSE_CAL_ONCE);

  /* calibration */
  s->opt[OPT_QUALITY_CAL].name = SANE_NAME_QUALITY_CAL;
  s->opt[OPT_QUALITY_CAL].title = SANE_TITLE_QUALITY_CAL;
  s->opt[OPT_QUALITY_CAL].desc = SANE_TITLE_QUALITY_CAL;
  s->opt[OPT_QUALITY_CAL].type = SANE_TYPE_BOOL;
  s->opt[OPT_QUALITY_CAL].unit = SANE_UNIT_NONE;
  s->opt[OPT_QUALITY_CAL].constraint_type = SANE_CONSTRAINT_NONE;
  s->val[OPT_QUALITY_CAL].w = SANE_TRUE;
  if (!debug_options)
    DISABLE (OPT_QUALITY_CAL);
  /* we disable image correction for scanners that can't calibrate */
  if ((s->dev->model->flags & GT68XX_FLAG_SHEET_FED)
    &&(!(s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)))
    {
      s->val[OPT_QUALITY_CAL].w = SANE_FALSE;
      DISABLE (OPT_QUALITY_CAL);
    }

  /* backtrack lines */
  s->opt[OPT_BACKTRACK_LINES].name = "backtrack-lines";
  s->opt[OPT_BACKTRACK_LINES].title = SANE_I18N ("Backtrack lines");
  s->opt[OPT_BACKTRACK_LINES].desc =
    SANE_I18N ("Number of lines the scan slider moves back when backtracking "
               "occurs. That happens when the scanner scans faster than the "
               "computer can receive the data. Low values cause faster scans "
               "but increase the risk of omitting lines.");
  s->opt[OPT_BACKTRACK_LINES].type = SANE_TYPE_INT;
  s->opt[OPT_BACKTRACK_LINES].unit = SANE_UNIT_NONE;
  s->opt[OPT_BACKTRACK_LINES].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BACKTRACK_LINES].constraint.range = &u8_range;
  if (s->dev->model->is_cis && !(s->dev->model->flags & GT68XX_FLAG_SHEET_FED))
    s->val[OPT_BACKTRACK_LINES].w = 0x10;
  else
    s->val[OPT_BACKTRACK_LINES].w = 0x3f;
  if (!debug_options)
    DISABLE (OPT_BACKTRACK_LINES);

  /* "Enhancement" group: */
  s->opt[OPT_ENHANCEMENT_GROUP].title = SANE_I18N ("Enhancement");
  s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
  s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
  s->opt[OPT_ENHANCEMENT_GROUP].size = 0;
  s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* internal gamma value */
  s->opt[OPT_GAMMA_VALUE].name = "gamma-value";
  s->opt[OPT_GAMMA_VALUE].title = SANE_I18N ("Gamma value");
  s->opt[OPT_GAMMA_VALUE].desc =
    SANE_I18N ("Sets the gamma value of all channels.");
  s->opt[OPT_GAMMA_VALUE].type = SANE_TYPE_FIXED;
  s->opt[OPT_GAMMA_VALUE].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VALUE].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VALUE].constraint.range = &gamma_range;
  s->opt[OPT_GAMMA_VALUE].cap |= SANE_CAP_EMULATED;
  s->val[OPT_GAMMA_VALUE].w = s->dev->gamma_value;

  /* 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 = &u8_range;
  s->val[OPT_THRESHOLD].w = 128;
  DISABLE (OPT_THRESHOLD);

  /* "Geometry" group: */
  s->opt[OPT_GEOMETRY_GROUP].title = SANE_I18N ("Geometry");
  s->opt[OPT_GEOMETRY_GROUP].desc = "";
  s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_GEOMETRY_GROUP].size = 0;
  s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  x_range.max = model->x_size;
  y_range.max = model->y_size;

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

  /* sensor group */
  s->opt[OPT_SENSOR_GROUP].name = SANE_NAME_SENSORS;
  s->opt[OPT_SENSOR_GROUP].title = SANE_TITLE_SENSORS;
  s->opt[OPT_SENSOR_GROUP].desc = SANE_DESC_SENSORS;
  s->opt[OPT_SENSOR_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_SENSOR_GROUP].constraint_type = SANE_CONSTRAINT_NONE;
  
  /* calibration needed */
  s->opt[OPT_NEED_CALIBRATION_SW].name = "need-calibration";
  s->opt[OPT_NEED_CALIBRATION_SW].title = SANE_I18N ("Need calibration");
  s->opt[OPT_NEED_CALIBRATION_SW].desc = SANE_I18N ("The scanner needs calibration for the current settings");
  s->opt[OPT_NEED_CALIBRATION_SW].type = SANE_TYPE_BOOL;
  s->opt[OPT_NEED_CALIBRATION_SW].unit = SANE_UNIT_NONE;
  if (s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)
    s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
  else
    s->opt[OPT_NEED_CALIBRATION_SW].cap = SANE_CAP_INACTIVE;
  s->val[OPT_NEED_CALIBRATION_SW].b = 0;

  /* document present sensor */
  s->opt[OPT_PAGE_LOADED_SW].name = SANE_NAME_PAGE_LOADED;
  s->opt[OPT_PAGE_LOADED_SW].title = SANE_TITLE_PAGE_LOADED;
  s->opt[OPT_PAGE_LOADED_SW].desc = SANE_DESC_PAGE_LOADED;
  s->opt[OPT_PAGE_LOADED_SW].type = SANE_TYPE_BOOL;
  s->opt[OPT_PAGE_LOADED_SW].unit = SANE_UNIT_NONE;
  if (s->dev->model->command_set->document_present)
    s->opt[OPT_PAGE_LOADED_SW].cap =
      SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
  else
    s->opt[OPT_PAGE_LOADED_SW].cap = SANE_CAP_INACTIVE;
  s->val[OPT_PAGE_LOADED_SW].b = 0;

  /* button group */
  s->opt[OPT_BUTTON_GROUP].name = "Buttons";
  s->opt[OPT_BUTTON_GROUP].title = SANE_I18N ("Buttons");
  s->opt[OPT_BUTTON_GROUP].desc = SANE_I18N ("Buttons");
  s->opt[OPT_BUTTON_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_BUTTON_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* calibrate button */
  s->opt[OPT_CALIBRATE].name = "calibrate";
  s->opt[OPT_CALIBRATE].title = SANE_I18N ("Calibrate");
  s->opt[OPT_CALIBRATE].desc =
    SANE_I18N ("Start calibration using special sheet");
  s->opt[OPT_CALIBRATE].type = SANE_TYPE_BUTTON;
  s->opt[OPT_CALIBRATE].unit = SANE_UNIT_NONE;
  if (s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)
  s->opt[OPT_CALIBRATE].cap =
      SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED |
      SANE_CAP_AUTOMATIC;
  else
    s->opt[OPT_CALIBRATE].cap = SANE_CAP_INACTIVE;
  s->val[OPT_CALIBRATE].b = 0;

  /* clear calibration cache button */
  s->opt[OPT_CLEAR_CALIBRATION].name = "clear";
  s->opt[OPT_CLEAR_CALIBRATION].title = SANE_I18N ("Clear calibration");
  s->opt[OPT_CLEAR_CALIBRATION].desc = SANE_I18N ("Clear calibration cache");
  s->opt[OPT_CLEAR_CALIBRATION].type = SANE_TYPE_BUTTON;
  s->opt[OPT_CLEAR_CALIBRATION].unit = SANE_UNIT_NONE;
  if (s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)
  s->opt[OPT_CLEAR_CALIBRATION].cap =
    SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED |
    SANE_CAP_AUTOMATIC;
  else
    s->opt[OPT_CLEAR_CALIBRATION].cap = SANE_CAP_INACTIVE;
  s->val[OPT_CLEAR_CALIBRATION].b = 0;


  RIE (calc_parameters (s));

  DBG (5, "init_options: exit\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
attach (SANE_String_Const devname, GT68xx_Device ** devp, SANE_Bool may_wait)
{
  GT68xx_Device *dev;
  SANE_Status status;

  DBG (5, "attach: start: devp %s NULL, may_wait = %d\n", devp ? "!=" : "==",
       may_wait);
  if (!devname)
    {
      DBG (1, "attach: devname == NULL\n");
      return SANE_STATUS_INVAL;
    }

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->file_name, devname) == 0)
        {
          if (devp)
            *devp = dev;
          dev->missing = SANE_FALSE;
          DBG (4, "attach: device `%s' was already in device list\n",
               devname);
          return SANE_STATUS_GOOD;
        }
    }

  DBG (4, "attach: trying to open device `%s'\n", devname);
  RIE (gt68xx_device_new (&dev));
  status = gt68xx_device_open (dev, devname);
  if (status == SANE_STATUS_GOOD)
    DBG (4, "attach: device `%s' successfully opened\n", devname);
  else
    {
      DBG (4, "attach: couldn't open device `%s': %s\n", devname,
           sane_strstatus (status));
      gt68xx_device_free (dev);
      if (devp)
        *devp = 0;
      return status;
    }

  if (!gt68xx_device_is_configured (dev))
    {
      GT68xx_Model *model = NULL;
      DBG (2, "attach: Warning: device `%s' is not listed in device table\n",
           devname);
      DBG (2,
           "attach: If you have manually added it, use override in gt68xx.conf\n");
      gt68xx_device_get_model ("unknown-scanner", &model);
      status = gt68xx_device_set_model (dev, model);
      if (status != SANE_STATUS_GOOD)
        {
          DBG (4, "attach: couldn't set model: %s\n",
               sane_strstatus (status));
          gt68xx_device_free (dev);
          if (devp)
            *devp = 0;
          return status;
        }
      dev->manual_selection = SANE_TRUE;
    }

  dev->file_name = strdup (devname);
  dev->missing = SANE_FALSE;
  if (!dev->file_name)
    return SANE_STATUS_NO_MEM;
  DBG (2, "attach: found %s flatbed scanner %s at %s\n", dev->model->vendor,
       dev->model->model, dev->file_name);
  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    *devp = dev;
  gt68xx_device_close (dev);
  DBG (5, "attach: exit\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one_device (SANE_String_Const devname)
{
  GT68xx_Device *dev;
  SANE_Status status;

  RIE (attach (devname, &dev, SANE_FALSE));

  if (dev)
    {
      /* Keep track of newly attached devices so we can set options as
         necessary.  */
      if (new_dev_len >= new_dev_alloced)
        {
          new_dev_alloced += 4;
          if (new_dev)
            new_dev =
              realloc (new_dev, new_dev_alloced * sizeof (new_dev[0]));
          else
            new_dev = malloc (new_dev_alloced * sizeof (new_dev[0]));
          if (!new_dev)
            {
              DBG (1, "attach_one_device: out of memory\n");
              return SANE_STATUS_NO_MEM;
            }
        }
      new_dev[new_dev_len++] = dev;
    }
  return SANE_STATUS_GOOD;
}

#if defined(_WIN32) || defined(HAVE_OS2_H)
# define PATH_SEP       "\\"
#else
# define PATH_SEP       "/"
#endif

static SANE_Status
download_firmware_file (GT68xx_Device * dev)
{
  SANE_Status status = SANE_STATUS_GOOD;
  SANE_Byte *buf = NULL;
  int size = -1;
  SANE_Char filename[PATH_MAX], dirname[PATH_MAX], basename[PATH_MAX];
  FILE *f;

  if (strncmp (dev->model->firmware_name, PATH_SEP, 1) != 0)
    {
      /* probably filename only */
      snprintf (filename, PATH_MAX, "%s%s%s%s%s%s%s",
                STRINGIFY (PATH_SANE_DATA_DIR),
                PATH_SEP, "sane", PATH_SEP, "gt68xx", PATH_SEP,
                dev->model->firmware_name);
      snprintf (dirname, PATH_MAX, "%s%s%s%s%s",
                STRINGIFY (PATH_SANE_DATA_DIR),
                PATH_SEP, "sane", PATH_SEP, "gt68xx");
      strncpy (basename, dev->model->firmware_name, PATH_MAX);
    }
  else
    {
      /* absolute path */
      char *pos;
      strncpy (filename, dev->model->firmware_name, PATH_MAX);
      strncpy (dirname, dev->model->firmware_name, PATH_MAX);
      pos = strrchr (dirname, PATH_SEP[0]);
      if (pos)
        pos[0] = '\0';
      strncpy (basename, pos + 1, PATH_MAX);
    }

  /* first, try to open with exact case */
  DBG (5, "download_firmware: trying %s\n", filename);
  f = fopen (filename, "rb");
  if (!f)
    {
      /* and now any case */
      DIR *dir;
      struct dirent *direntry;

      DBG (5,
           "download_firmware_file: Couldn't open firmware file `%s': %s\n",
           filename, strerror (errno));

      dir = opendir (dirname);
      if (!dir)
        {
          DBG (5, "download_firmware: couldn't open directory `%s': %s\n",
               dirname, strerror (errno));
          status = SANE_STATUS_INVAL;
        }
      if (status == SANE_STATUS_GOOD)
        {
          do
            {
              direntry = readdir (dir);
              if (direntry
                  && (strncasecmp (direntry->d_name, basename, PATH_MAX) ==
                      0))
                {
                  snprintf (filename, PATH_MAX, "%s%s%s",
                            dirname, PATH_SEP, direntry->d_name);
                  break;
                }
            }
          while (direntry != 0);
          if (direntry == 0)
            {
              DBG (5, "download_firmware: file `%s' not found\n", filename);
              status = SANE_STATUS_INVAL;
            }
          closedir (dir);
        }
      if (status == SANE_STATUS_GOOD)
        {
          DBG (5, "download_firmware: trying %s\n", filename);
          f = fopen (filename, "rb");
          if (!f)
            {
              DBG (5,
                   "download_firmware_file: Couldn't open firmware file `%s': %s\n",
                   filename, strerror (errno));
              status = SANE_STATUS_INVAL;
            }
        }

      if (status != SANE_STATUS_GOOD)
        {
          DBG (0, "Couldn't open firmware file (`%s'): %s\n",
               filename, strerror (errno));
        }
    }

  if (status == SANE_STATUS_GOOD)
    {
      fseek (f, 0, SEEK_END);
      size = ftell (f);
      fseek (f, 0, SEEK_SET);
      if (size == -1)
        {
          DBG (1, "download_firmware_file: error getting size of "
               "firmware file \"%s\": %s\n", filename, strerror (errno));
          status = SANE_STATUS_INVAL;
        }
    }

  if (status == SANE_STATUS_GOOD)
    {
      DBG (5, "firmware size: %d\n", size);
      buf = (SANE_Byte *) malloc (size);
      if (!buf)
        {
          DBG (1, "download_firmware_file: cannot allocate %d bytes "
               "for firmware\n", size);
          status = SANE_STATUS_NO_MEM;
        }
    }

  if (status == SANE_STATUS_GOOD)
    {
      int bytes_read = fread (buf, 1, size, f);
      if (bytes_read != size)
        {
          DBG (1, "download_firmware_file: problem reading firmware "
               "file \"%s\": %s\n", filename, strerror (errno));
          status = SANE_STATUS_INVAL;
        }
    }

  if (f)
    fclose (f);

  if (status == SANE_STATUS_GOOD)
    {
      status = gt68xx_device_download_firmware (dev, buf, size);
      if (status != SANE_STATUS_GOOD)
        {
          DBG (1, "download_firmware_file: firmware download failed: %s\n",
               sane_strstatus (status));
        }
    }

  if (buf)
    free (buf);

  return status;
}

/** probe for gt68xx devices
 * This function scan usb and try to attached to scanner
 * configured in gt68xx.conf .
 */
static SANE_Status probe_gt68xx_devices(void)
{
  SANE_Char line[PATH_MAX];
  SANE_Char *word;
  SANE_String_Const cp;
  SANE_Int linenumber;
  GT68xx_Device *dev;
  FILE *fp;

  /* set up for no new devices detected at first */
  new_dev = 0;
  new_dev_len = 0;
  new_dev_alloced = 0;

  /* mark already detected devices as missing, during device probe
   * detected devices will clear this flag */
  dev = first_dev;
  while(dev!=NULL)
    {
      dev->missing = SANE_TRUE;
      dev = dev->next;
    }

  fp = sanei_config_open (GT68XX_CONFIG_FILE);
  if (!fp)
    {
      /* default to /dev/usb/scanner instead of insisting on config file */
      DBG (3, "sane_init: couldn't open config file `%s': %s. Using "
           "/dev/usb/scanner directly\n", GT68XX_CONFIG_FILE,
           strerror (errno));
      attach ("/dev/usb/scanner", 0, SANE_FALSE);
      return SANE_STATUS_GOOD;
    }

  little_endian = calc_little_endian ();
  DBG (5, "sane_init: %s endian machine\n", little_endian ? "little" : "big");

  linenumber = 0;
  DBG (4, "sane_init: reading config file `%s'\n", GT68XX_CONFIG_FILE);
  while (sanei_config_read (line, sizeof (line), fp))
    {
      word = 0;
      linenumber++;

      cp = sanei_config_get_string (line, &word);
      if (!word || cp == line)
        {
          DBG (6, "sane_init: config file line %d: ignoring empty line\n",
               linenumber);
          if (word)
            free (word);
          continue;
        }
      if (word[0] == '#')
        {
          DBG (6, "sane_init: config file line %d: ignoring comment line\n",
               linenumber);
          free (word);
          continue;
        }

      if (strcmp (word, "firmware") == 0)
        {
          free (word);
          word = 0;
          cp = sanei_config_get_string (cp, &word);
          if (word)
            {
              int i;
              for (i = 0; i < new_dev_len; i++)
                {
                  new_dev[i]->model->firmware_name = word;
                  DBG (5, "sane_init: device %s: firmware will be loaded "
                       "from %s\n", new_dev[i]->model->name,
                       new_dev[i]->model->firmware_name);
                }
              if (i == 0)
                DBG (5, "sane_init: firmware %s can't be loaded, set device "
                     "first\n", word);
            }
          else
            {
              DBG (3, "sane_init: option `firmware' needs a parameter\n");
            }
        }
      else if (strcmp (word, "vendor") == 0)
        {
          free (word);
          word = 0;
          cp = sanei_config_get_string (cp, &word);
          if (word)
            {
              int i;

              for (i = 0; i < new_dev_len; i++)
                {
                  new_dev[i]->model->vendor = word;
                  DBG (5, "sane_init: device %s: vendor name set to %s\n",
                       new_dev[i]->model->name, new_dev[i]->model->vendor);
                }
              if (i == 0)
                DBG (5, "sane_init: can't set vendor name %s, set device "
                     "first\n", word);
            }
          else
            {
              DBG (3, "sane_init: option `vendor' needs a parameter\n");
            }
        }
      else if (strcmp (word, "model") == 0)
        {
          free (word);
          word = 0;
          cp = sanei_config_get_string (cp, &word);
          if (word)
            {
              int i;
              for (i = 0; i < new_dev_len; i++)
                {
                  new_dev[i]->model->model = word;
                  DBG (5, "sane_init: device %s: model name set to %s\n",
                       new_dev[i]->model->name, new_dev[i]->model->model);
                }
              if (i == 0)
                DBG (5, "sane_init: can't set model name %s, set device "
                     "first\n", word);
              free (word);
            }
          else
            {
              DBG (3, "sane_init: option `model' needs a parameter\n");
            }
        }
      else if (strcmp (word, "override") == 0)
        {
          free (word);
          word = 0;
          cp = sanei_config_get_string (cp, &word);
          if (word)
            {
              int i;
              for (i = 0; i < new_dev_len; i++)
                {
                  SANE_Status status;
                  GT68xx_Device *dev = new_dev[i];
                  GT68xx_Model *model;
                  if (gt68xx_device_get_model (word, &model) == SANE_TRUE)
                    {
                      status = gt68xx_device_set_model (dev, model);
                      if (status != SANE_STATUS_GOOD)
                        DBG (1, "sane_init: couldn't override model: %s\n",
                             sane_strstatus (status));
                      else
                        DBG (5, "sane_init: new model set to %s\n",
                             dev->model->name);
                    }
                  else
                    {
                      DBG (1, "sane_init: override: model %s not found\n",
                           word);
                    }
                }
              if (i == 0)
                DBG (5, "sane_init: can't override model to %s, set device "
                     "first\n", word);
              free (word);
            }
          else
            {
              DBG (3, "sane_init: option `override' needs a parameter\n");
            }
        }
      else if (strcmp (word, "afe") == 0)
        {
          GT68xx_AFE_Parameters afe = {0, 0, 0, 0, 0, 0};
          SANE_Status status;

          free (word);
          word = 0;

          status = get_afe_values (cp, &afe);
          if (status == SANE_STATUS_GOOD)
            {
              int i;
              for (i = 0; i < new_dev_len; i++)
                {
                  new_dev[i]->model->afe_params = afe;
                  DBG (5, "sane_init: device %s: setting new afe values\n",
                       new_dev[i]->model->name);
                }
              if (i == 0)
                DBG (5,
                     "sane_init: can't set afe values, set device first\n");
            }
          else
            DBG (3, "sane_init: can't set afe values\n");
        }
      else
        {
          new_dev_len = 0;
          DBG (4, "sane_init: config file line %d: trying to attach `%s'\n",
               linenumber, line);
          sanei_usb_attach_matching_devices (line, attach_one_device);
          if (word)
            free (word);
          word = 0;
        }
    }

  if (new_dev_alloced > 0)
    {
      new_dev_len = new_dev_alloced = 0;
      free (new_dev);
    }

  fclose (fp);
  return SANE_STATUS_GOOD;
}

/* -------------------------- SANE API functions ------------------------- */

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  SANE_Status status;

  DBG_INIT ();
#ifdef DBG_LEVEL
  if (DBG_LEVEL > 0)
    {
      DBG (5, "sane_init: debug options are enabled, handle with care\n");
      debug_options = SANE_TRUE;
    }
#endif
  DBG (2, "SANE GT68xx backend version %d.%d build %d from %s\n", SANE_CURRENT_MAJOR,
       V_MINOR, BUILD, PACKAGE_STRING);

  if (version_code)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, BUILD);

  DBG (5, "sane_init: authorize %s null\n", authorize ? "!=" : "==");

  sanei_usb_init ();

  num_devices = 0;
  first_dev = 0;
  first_handle = 0;
  devlist = 0;
  new_dev = 0;
  new_dev_len = 0;
  new_dev_alloced = 0;

  status = probe_gt68xx_devices ();
  DBG (5, "sane_init: exit\n");

  return status;
}

void
sane_exit (void)
{
  GT68xx_Device *dev, *next;

  DBG (5, "sane_exit: start\n");
  sanei_usb_exit();
  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      gt68xx_device_free (dev);
    }
  first_dev = 0;
  first_handle = 0;
  if (devlist)
    free (devlist);
  devlist = 0;

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

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  GT68xx_Device *dev;
  SANE_Int dev_num;

  DBG (5, "sane_get_devices: start: local_only = %s\n",
       local_only == SANE_TRUE ? "true" : "false");

  /* hot-plug case : detection of newly connected scanners */
  sanei_usb_scan_devices ();
  probe_gt68xx_devices ();

  if (devlist)
    free (devlist);

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

  dev_num = 0;
  dev = first_dev;
  while(dev!=NULL)
    {
      SANE_Device *sane_device;

      /* don't return devices that have been unplugged */
      if(dev->missing==SANE_FALSE)
        {
          sane_device = malloc (sizeof (*sane_device));
          if (!sane_device)
            return SANE_STATUS_NO_MEM;
          sane_device->name = dev->file_name;
          sane_device->vendor = dev->model->vendor;
          sane_device->model = dev->model->model;
          sane_device->type = strdup ("flatbed scanner");
          devlist[dev_num] = sane_device;
          dev_num++;
        }

      /* next device */
      dev = dev->next;
    }
  devlist[dev_num] = 0;

  *device_list = devlist;

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

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  GT68xx_Device *dev;
  SANE_Status status;
  GT68xx_Scanner *s;
  SANE_Bool power_ok;

  DBG (5, "sane_open: start (devicename = `%s')\n", devicename);

  if (devicename[0])
    {
      /* test for gt68xx short hand name */
      if(strcmp(devicename,"gt68xx")!=0)
        {
          for (dev = first_dev; dev; dev = dev->next)
            if (strcmp (dev->file_name, devicename) == 0)
              break;

          if (!dev)
            {
              DBG (5, "sane_open: couldn't find `%s' in devlist, trying attach\n",
                   devicename);
              RIE (attach (devicename, &dev, SANE_TRUE));
            }
          else
            DBG (5, "sane_open: found `%s' in devlist\n", dev->model->name);
        }
      else
        {
          dev = first_dev;
          if (dev)
            {
              devicename = dev->file_name;
              DBG (5, "sane_open: default empty devicename, using first device `%s'\n", devicename);
            }
        }
    }
  else
    {
      /* empty devicname -> use first device */
      dev = first_dev;
      if (dev)
        {
          devicename = dev->file_name;
          DBG (5, "sane_open: empty devicename, trying `%s'\n", devicename);
        }
    }

  if (!dev)
    return SANE_STATUS_INVAL;

  RIE (gt68xx_device_open (dev, devicename));
  RIE (gt68xx_device_activate (dev));

  if (dev->model->flags & GT68XX_FLAG_UNTESTED)
    {
      DBG (0, "WARNING: Your scanner is not fully supported or at least \n");
      DBG (0, "         had only limited testing. Please be careful and \n");
      DBG (0, "         report any failure/success to \n");
      DBG (0, "         sane-devel@lists.alioth.debian.org. Please provide as many\n");
      DBG (0, "         details as possible, e.g. the exact name of your\n");
      DBG (0, "         scanner and what does (not) work.\n");
    }

  if (dev->manual_selection)
    {
      DBG (0, "WARNING: You have manually added the ids of your scanner \n");
      DBG (0,
           "         to gt68xx.conf. Please use an appropriate override \n");
      DBG (0,
           "         for your scanner. Use extreme care and switch off \n");
      DBG (0,
           "         the scanner immediately if you hear unusual noise. \n");
      DBG (0, "         Please report any success to \n");
      DBG (0, "         sane-devel@lists.alioth.debian.org. Please provide as many\n");
      DBG (0, "         details as possible, e.g. the exact name of your\n");
      DBG (0, "         scanner, ids, settings etc.\n");

      if (strcmp (dev->model->name, "unknown-scanner") == 0)
        {
          GT68xx_USB_Device_Entry *entry;

          DBG (0,
               "ERROR: You haven't chosen an override in gt68xx.conf. Please use \n");
          DBG (0, "       one of the following: \n");

          for (entry = gt68xx_usb_device_list; entry->model; ++entry)
            {
              if (strcmp (entry->model->name, "unknown-scanner") != 0)
                DBG (0, "       %s\n", entry->model->name);
            }
          return SANE_STATUS_UNSUPPORTED;
        }
    }

  /* The firmware check is disabled by default because it may confuse
     some scanners: So the firmware is loaded everytime. */
#if 0
  RIE (gt68xx_device_check_firmware (dev, &firmware_loaded));
  firmware_loaded = SANE_FALSE;
  if (firmware_loaded)
    DBG (3, "sane_open: firmware already loaded, skipping load\n");
  else
    RIE (download_firmware_file (dev));
  /*  RIE (gt68xx_device_check_firmware (dev, &firmware_loaded)); */
  if (!firmware_loaded)
    {
      DBG (1, "sane_open: firmware still not loaded? Proceeding anyway\n");
      /* return SANE_STATUS_IO_ERROR; */
    }
#else
  RIE (download_firmware_file (dev));
#endif

  RIE (gt68xx_device_get_id (dev));

  if (!(dev->model->flags & GT68XX_FLAG_NO_STOP))
    RIE (gt68xx_device_stop_scan (dev));

  RIE (gt68xx_device_get_power_status (dev, &power_ok));
  if (power_ok)
    {
      DBG (5, "sane_open: power ok\n");
    }
  else
    {
      DBG (0, "sane_open: power control failure: check power plug!\n");
      return SANE_STATUS_IO_ERROR;
    }

  RIE (gt68xx_scanner_new (dev, &s));
  RIE (gt68xx_device_lamp_control (s->dev, SANE_TRUE, SANE_FALSE));
  gettimeofday (&s->lamp_on_time, 0);

  /* insert newly opened handle into list of open handles: */
  s->next = first_handle;
  first_handle = s;
  *handle = s;
  s->scanning = SANE_FALSE;
  s->first_scan = SANE_TRUE;
  s->gamma_table = 0;
  s->calibrated = SANE_FALSE;
  RIE (init_options (s));
  dev->gray_mode_color = 0x02;

  /* try to restore calibration from file */
  if((s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE))
    {
      /* error restoring calibration is non blocking */
      gt68xx_read_calibration(s);
    }

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

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  GT68xx_Scanner *prev, *s;
  GT68xx_Device *dev;

  DBG (5, "sane_close: start\n");

  /* remove handle from list of open handles: */
  prev = 0;
  for (s = first_handle; s; s = s->next)
    {
      if (s == handle)
        break;
      prev = s;
    }
  if (!s)
    {
      DBG (5, "close: invalid handle %p\n", handle);
      return;                   /* oops, not a handle we know about */
    }

  if (prev)
    prev->next = s->next;
  else
    first_handle = s->next;

  if (s->val[OPT_LAMP_OFF_AT_EXIT].w == SANE_TRUE)
    gt68xx_device_lamp_control (s->dev, SANE_FALSE, SANE_FALSE);

  dev = s->dev;
  
  free (s->val[OPT_MODE].s);
  free (s->val[OPT_GRAY_MODE_COLOR].s);
  free (s->val[OPT_SOURCE].s);
  free (dev->file_name);
  free ((void *)(size_t)s->opt[OPT_RESOLUTION].constraint.word_list);

  gt68xx_scanner_free (s);
  
  gt68xx_device_fix_descriptor (dev);

  gt68xx_device_deactivate (dev);
  gt68xx_device_close (dev);

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

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  GT68xx_Scanner *s = handle;

  if ((unsigned) option >= NUM_OPTIONS)
    return 0;
  DBG (5, "sane_get_option_descriptor: option = %s (%d)\n",
       s->opt[option].name, option);
  return s->opt + option;
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
                     SANE_Action action, void *val, SANE_Int * info)
{
  GT68xx_Scanner *s = handle;
  SANE_Status status = SANE_STATUS_GOOD;
  SANE_Word cap;
  SANE_Int myinfo = 0;

  DBG (5, "sane_control_option: start: action = %s, option = %s (%d)\n",
       (action == SANE_ACTION_GET_VALUE) ? "get" :
       (action == SANE_ACTION_SET_VALUE) ? "set" :
       (action == SANE_ACTION_SET_AUTO) ? "set_auto" : "unknown",
       s->opt[option].name, option);

  if (info)
    *info = 0;

  if (s->scanning)
    {
      DBG (1, "sane_control_option: don't call this function while "
           "scanning (option = %s (%d))\n", s->opt[option].name, option);

      return SANE_STATUS_DEVICE_BUSY;
    }
  if (option >= NUM_OPTIONS || option < 0)
    {
      DBG (1, "sane_control_option: option %d >= NUM_OPTIONS || option < 0\n",
           option);
      return SANE_STATUS_INVAL;
    }

  cap = s->opt[option].cap;

  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      DBG (2, "sane_control_option: option %d is inactive\n", option);
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {
      switch (option)
        {
          /* word options: */
        case OPT_NUM_OPTS:
        case OPT_RESOLUTION:
        case OPT_BIT_DEPTH:
        case OPT_FULL_SCAN:
        case OPT_COARSE_CAL:
        case OPT_COARSE_CAL_ONCE:
        case OPT_QUALITY_CAL:
        case OPT_BACKTRACK:
        case OPT_BACKTRACK_LINES:
        case OPT_PREVIEW:
        case OPT_LAMP_OFF_AT_EXIT:
        case OPT_AUTO_WARMUP:
        case OPT_GAMMA_VALUE:
        case OPT_THRESHOLD:
        case OPT_TL_X:
        case OPT_TL_Y:
        case OPT_BR_X:
        case OPT_BR_Y:
          *(SANE_Word *) val = s->val[option].w;
          break;
          /* string options: */
        case OPT_MODE:
        case OPT_GRAY_MODE_COLOR:
        case OPT_SOURCE:
          strcpy (val, s->val[option].s);
          break;
        case OPT_NEED_CALIBRATION_SW:
          *(SANE_Bool *) val = !s->calibrated;
          break;
        case OPT_PAGE_LOADED_SW:
          s->dev->model->command_set->document_present (s->dev, val);
          break;
        default:
          DBG (2, "sane_control_option: can't get unknown option %d\n",
               option);
        }
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      if (!SANE_OPTION_IS_SETTABLE (cap))
        {
          DBG (2, "sane_control_option: option %d is not settable\n", option);
          return SANE_STATUS_INVAL;
        }

      status = sanei_constrain_value (s->opt + option, val, &myinfo);

      if (status != SANE_STATUS_GOOD)
        {
          DBG (2, "sane_control_option: sanei_constrain_value returned %s\n",
               sane_strstatus (status));
          return status;
        }

      switch (option)
        {
        case OPT_RESOLUTION:
        case OPT_BIT_DEPTH:
        case OPT_FULL_SCAN:
        case OPT_PREVIEW:
        case OPT_TL_X:
        case OPT_TL_Y:
        case OPT_BR_X:
        case OPT_BR_Y:
          s->val[option].w = *(SANE_Word *) val;
          RIE (calc_parameters (s));
          myinfo |= SANE_INFO_RELOAD_PARAMS;
          break;
        case OPT_LAMP_OFF_AT_EXIT:
        case OPT_AUTO_WARMUP:
        case OPT_COARSE_CAL_ONCE:
        case OPT_BACKTRACK_LINES:
        case OPT_QUALITY_CAL:
        case OPT_GAMMA_VALUE:
        case OPT_THRESHOLD:
          s->val[option].w = *(SANE_Word *) val;
          break;
        case OPT_GRAY_MODE_COLOR:
          if (strcmp (s->val[option].s, val) != 0)
            {                   /* something changed */
              if (s->val[option].s)
                free (s->val[option].s);
              s->val[option].s = strdup (val);
            }
          break;
        case OPT_SOURCE:
          if (strcmp (s->val[option].s, val) != 0)
            {                   /* something changed */
              if (s->val[option].s)
                free (s->val[option].s);
              s->val[option].s = strdup (val);
              if (strcmp (s->val[option].s, "Transparency Adapter") == 0)
                {
                  RIE (gt68xx_device_lamp_control
                       (s->dev, SANE_FALSE, SANE_TRUE));
                  x_range.max = s->dev->model->x_size_ta;
                  y_range.max = s->dev->model->y_size_ta;
                }
              else
                {
                  RIE (gt68xx_device_lamp_control
                       (s->dev, SANE_TRUE, SANE_FALSE));
                  x_range.max = s->dev->model->x_size;
                  y_range.max = s->dev->model->y_size;
                }
              s->first_scan = SANE_TRUE;
              myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
              gettimeofday (&s->lamp_on_time, 0);
            }
          break;
        case OPT_MODE:
          if (s->val[option].s)
            free (s->val[option].s);
          s->val[option].s = strdup (val);
          if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_LINEART) == 0)
            {
              ENABLE (OPT_THRESHOLD);
              DISABLE (OPT_BIT_DEPTH);
              ENABLE (OPT_GRAY_MODE_COLOR);
            }
          else
            {
              DISABLE (OPT_THRESHOLD);
              if (strcmp (s->val[option].s, SANE_VALUE_SCAN_MODE_GRAY) == 0)
                {
                  RIE (create_bpp_list (s, s->dev->model->bpp_gray_values));
                  ENABLE (OPT_GRAY_MODE_COLOR);
                }
              else
                {
                  RIE (create_bpp_list (s, s->dev->model->bpp_color_values));
                  DISABLE (OPT_GRAY_MODE_COLOR);
                }
              if (s->bpp_list[0] < 2)
                DISABLE (OPT_BIT_DEPTH);
              else
                ENABLE (OPT_BIT_DEPTH);
            }
          RIE (calc_parameters (s));
          myinfo |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          break;

        case OPT_COARSE_CAL:
          s->val[option].w = *(SANE_Word *) val;
          if (s->val[option].w == SANE_TRUE)
            {
              ENABLE (OPT_COARSE_CAL_ONCE);
              s->first_scan = SANE_TRUE;
            }
          else
            {
              DISABLE (OPT_COARSE_CAL_ONCE);
            }
          myinfo |= SANE_INFO_RELOAD_OPTIONS;
          break;

        case OPT_BACKTRACK:
          s->val[option].w = *(SANE_Word *) val;
          if (s->val[option].w == SANE_TRUE)
            ENABLE (OPT_BACKTRACK_LINES);
          else
            DISABLE (OPT_BACKTRACK_LINES);
          myinfo |= SANE_INFO_RELOAD_OPTIONS;
          break;

        case OPT_CALIBRATE:
          status = gt68xx_sheetfed_scanner_calibrate (s);
          myinfo |= SANE_INFO_RELOAD_OPTIONS;
          break;
    
        case OPT_CLEAR_CALIBRATION:
          gt68xx_clear_calibration (s);
          myinfo |= SANE_INFO_RELOAD_OPTIONS;
          break;

        default:
          DBG (2, "sane_control_option: can't set unknown option %d\n",
               option);
        }
    }
  else
    {
      DBG (2, "sane_control_option: unknown action %d for option %d\n",
           action, option);
      return SANE_STATUS_INVAL;
    }
  if (info)
    *info = myinfo;

  DBG (5, "sane_control_option: exit\n");
  return status;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  GT68xx_Scanner *s = handle;
  SANE_Status status;

  DBG (5, "sane_get_parameters: start\n");

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

  DBG (4, "sane_get_parameters: format=%d, last_frame=%d, lines=%d\n",
       s->params.format, s->params.last_frame, s->params.lines);
  DBG (4, "sane_get_parameters: pixels_per_line=%d, bytes per line=%d\n",
       s->params.pixels_per_line, s->params.bytes_per_line);
  DBG (3, "sane_get_parameters: pixels %dx%dx%d\n",
       s->params.pixels_per_line, s->params.lines, 1 << s->params.depth);

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

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  GT68xx_Scanner *s = handle;
  GT68xx_Scan_Request scan_request;
  GT68xx_Scan_Parameters scan_params;
  SANE_Status status;
  SANE_Int i, gamma_size;
  unsigned int *buffer_pointers[3];
  SANE_Bool document;

  DBG (5, "sane_start: start\n");

  /* First make sure we have a current parameter set.  Some of the
     parameters will be overwritten below, but that's OK.  */
  RIE (calc_parameters (s));

  if (s->val[OPT_TL_X].w >= s->val[OPT_BR_X].w)
    {
      DBG (0, "sane_start: top left x >= bottom right x --- exiting\n");
      return SANE_STATUS_INVAL;
    }
  if (s->val[OPT_TL_Y].w >= s->val[OPT_BR_Y].w)
    {
      DBG (0, "sane_start: top left y >= bottom right y --- exiting\n");
      return SANE_STATUS_INVAL;
    }

  if (strcmp (s->val[OPT_GRAY_MODE_COLOR].s, GT68XX_COLOR_BLUE) == 0)
    s->dev->gray_mode_color = 0x01;
  else if (strcmp (s->val[OPT_GRAY_MODE_COLOR].s, GT68XX_COLOR_GREEN) == 0)
    s->dev->gray_mode_color = 0x02;
  else
    s->dev->gray_mode_color = 0x03;

  setup_scan_request (s, &scan_request);
  if (!s->first_scan && s->val[OPT_COARSE_CAL_ONCE].w == SANE_TRUE)
    s->auto_afe = SANE_FALSE;
  else
    s->auto_afe = s->val[OPT_COARSE_CAL].w;

  s->dev->gamma_value = s->val[OPT_GAMMA_VALUE].w;
  gamma_size = s->params.depth == 16 ? 65536 : 256;
  s->gamma_table = malloc (sizeof (SANE_Int) * gamma_size);
  if (!s->gamma_table)
    {
      DBG (1, "sane_start: couldn't malloc %d bytes for gamma table\n",
           gamma_size);
      return SANE_STATUS_NO_MEM;
    }
  for (i = 0; i < gamma_size; i++)
    {
      s->gamma_table[i] =
        (gamma_size - 1) * pow (((double) i + 1) / (gamma_size),
                                1.0 / SANE_UNFIX (s->dev->gamma_value)) + 0.5;
      if (s->gamma_table[i] > (gamma_size - 1))
        s->gamma_table[i] = (gamma_size - 1);
      if (s->gamma_table[i] < 0)
        s->gamma_table[i] = 0;
#if 0
      printf ("%d %d\n", i, s->gamma_table[i]);
#endif
    }

  if(!(s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE))
    {
      s->calib = s->val[OPT_QUALITY_CAL].w;
    }

  if (!(s->dev->model->flags & GT68XX_FLAG_NO_STOP))
    RIE (gt68xx_device_stop_scan (s->dev));

  if (!(s->dev->model->flags & GT68XX_FLAG_SHEET_FED))
    RIE (gt68xx_device_carriage_home (s->dev));

  gt68xx_scanner_wait_for_positioning (s);
  gettimeofday (&s->start_time, 0);

  if (s->val[OPT_BACKTRACK].w == SANE_TRUE)
    scan_request.backtrack = SANE_TRUE;
  else
    {
      if (s->val[OPT_RESOLUTION].w >= s->dev->model->ydpi_no_backtrack)
        scan_request.backtrack = SANE_FALSE;
      else
        scan_request.backtrack = SANE_TRUE;
    }

  if (scan_request.backtrack)
    scan_request.backtrack_lines = s->val[OPT_BACKTRACK_LINES].w;
  else
    scan_request.backtrack_lines = 0;

  /* don't call calibration for scanners that use sheetfed_calibrate */
  if(!(s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE))
    {
      RIE (gt68xx_scanner_calibrate (s, &scan_request));
    }
  else
    {
      s->calib = s->calibrated;
    }

  /* is possible, wait for document to be inserted before scanning */
  /* wait for 5 secondes max */
  if (s->dev->model->flags & GT68XX_FLAG_SHEET_FED
   && s->dev->model->command_set->document_present)
    {
      i=0;
      do
        {
          RIE(s->dev->model->command_set->document_present(s->dev,&document));
          if(document==SANE_FALSE)
            {
              i++;
              sleep(1);
            }
        } while ((i<5) && (document==SANE_FALSE));
      if(document==SANE_FALSE)
        {
          DBG (4, "sane_start: no doucment detected after %d s\n",i);
          return SANE_STATUS_NO_DOCS;
        }
    }

  /* some sheetfed scanners need a special operation to move
   * paper before starting real scan */
  if (s->dev->model->flags & GT68XX_FLAG_SHEET_FED)
    {
      RIE (gt68xx_sheetfed_move_to_scan_area (s, &scan_request));
    }

  /* restore calibration */
  if(  (s->dev->model->flags & GT68XX_FLAG_HAS_CALIBRATE)
     &&(s->calibrated == SANE_TRUE))
    {
      /* compute scan parameters */
      scan_request.calculate = SANE_TRUE;
      gt68xx_device_setup_scan (s->dev, &scan_request, SA_SCAN, &scan_params); 

      /* restore settings from calibration stored */
      memcpy(s->dev->afe,&(s->afe_params), sizeof(GT68xx_AFE_Parameters));
      RIE (gt68xx_assign_calibration (s, scan_params));
      scan_request.calculate = SANE_FALSE;
    }
 
  /* send scan request to the scanner */
  RIE (gt68xx_scanner_start_scan (s, &scan_request, &scan_params));

  for (i = 0; i < scan_params.overscan_lines; ++i)
    RIE (gt68xx_scanner_read_line (s, buffer_pointers));
  DBG (4, "sane_start: wanted: dpi=%d, x=%.1f, y=%.1f, width=%.1f, "
       "height=%.1f, color=%s\n", scan_request.xdpi,
       SANE_UNFIX (scan_request.x0),
       SANE_UNFIX (scan_request.y0), SANE_UNFIX (scan_request.xs),
       SANE_UNFIX (scan_request.ys), scan_request.color ? "color" : "gray");

  s->line = 0;
  s->byte_count = s->reader->params.pixel_xs;
  s->total_bytes = 0;
  s->first_scan = SANE_FALSE;

#ifdef DEBUG_BRIGHTNESS
  s->average_white = 0;
  s->max_white = 0;
  s->min_black = 255;
#endif

  s->scanning = SANE_TRUE;

  DBG (5, "sane_start: exit\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
           SANE_Int * len)
{
  GT68xx_Scanner *s = handle;
  SANE_Status status;
  static unsigned int *buffer_pointers[3];
  SANE_Int inflate_x;
  SANE_Bool lineart;
  SANE_Int i, color, colors;

  if (!s)
    {
      DBG (1, "sane_read: handle is null!\n");
      return SANE_STATUS_INVAL;
    }

  if (!buf)
    {
      DBG (1, "sane_read: buf is null!\n");
      return SANE_STATUS_INVAL;
    }

  if (!len)
    {
      DBG (1, "sane_read: len is null!\n");
      return SANE_STATUS_INVAL;
    }

  *len = 0;

  if (!s->scanning)
    {
      DBG (3, "sane_read: scan was cancelled, is over or has not been "
           "initiated yet\n");
      return SANE_STATUS_CANCELLED;
    }

  DBG (5, "sane_read: start (line %d of %d, byte_count %d of %d)\n",
       s->line, s->reader->params.pixel_ys, s->byte_count,
       s->reader->params.pixel_xs);

  if (s->line >= s->reader->params.pixel_ys
      && s->byte_count >= s->reader->params.pixel_xs)
    {
      DBG (4, "sane_read: nothing more to scan: EOF\n");
      return SANE_STATUS_EOF;
    }

  inflate_x = s->val[OPT_RESOLUTION].w / s->dev->model->optical_xdpi;
  if (inflate_x > 1)
    DBG (5, "sane_read: inflating x by factor %d\n", inflate_x);
  else
    inflate_x = 1;

  lineart = (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_LINEART) == 0)
    ? SANE_TRUE : SANE_FALSE;

  if (s->reader->params.color)
    colors = 3;
  else
    colors = 1;

  while ((*len) < max_len)
    {
      if (s->byte_count >= s->reader->params.pixel_xs)
        {
          if (s->line >= s->reader->params.pixel_ys)
            {
              DBG (4, "sane_read: scan complete: %d bytes, %d total\n",
                   *len, s->total_bytes);
              return SANE_STATUS_GOOD;
            }
          DBG (5, "sane_read: getting line %d of %d\n", s->line,
               s->reader->params.pixel_ys);
          RIE (gt68xx_scanner_read_line (s, buffer_pointers));
          s->line++;
          s->byte_count = 0;

          /* Apply gamma */
          for (color = 0; color < colors; color++)
            for (i = 0; i < s->reader->pixels_per_line; i++)
              {
                if (s->reader->params.depth > 8)
                  buffer_pointers[color][i] =
                    s->gamma_table[buffer_pointers[color][i]];
                else
                  buffer_pointers[color][i] =
                    (s->gamma_table[buffer_pointers[color][i] >> 8] << 8) +
                    (s->gamma_table[buffer_pointers[color][i] >> 8]);
              }
          /* mirror lines */
          if (s->dev->model->flags & GT68XX_FLAG_MIRROR_X)
            {
              unsigned int swap;

              for (color = 0; color < colors; color++)
                {
                  for (i = 0; i < s->reader->pixels_per_line / 2; i++)
                    {
                      swap = buffer_pointers[color][i];
                      buffer_pointers[color][i] =
                        buffer_pointers[color][s->reader->pixels_per_line -
                                               1 - i];
                      buffer_pointers[color][s->reader->pixels_per_line - 1 -
                                             i] = swap;
                    }
                }
            }
        }
      if (lineart)
        {
          SANE_Int bit;
          SANE_Byte threshold = s->val[OPT_THRESHOLD].w;

          buf[*len] = 0;
          for (bit = 7; bit >= 0; bit--)
            {
              SANE_Byte is_black =
                (((buffer_pointers[0][s->byte_count] >> 8) & 0xff) >
                 threshold) ? 0 : 1;
              buf[*len] |= (is_black << bit);
              if ((7 - bit) % inflate_x == (inflate_x - 1))
                s->byte_count++;
            }
        }
      else if (s->reader->params.color)
        {
          /* color */
          if (s->reader->params.depth > 8)
            {
              SANE_Int color = (s->total_bytes / 2) % 3;
              if ((s->total_bytes % 2) == 0)
                {
                  if (little_endian)
                    buf[*len] = buffer_pointers[color][s->byte_count] & 0xff;
                  else
                    buf[*len] =
                      (buffer_pointers[color][s->byte_count] >> 8) & 0xff;
                }
              else
                {
                  if (little_endian)
                    buf[*len] =
                      (buffer_pointers[color][s->byte_count] >> 8) & 0xff;
                  else
                    buf[*len] = buffer_pointers[color][s->byte_count] & 0xff;

                  if (s->total_bytes % (inflate_x * 6) == (inflate_x * 6 - 1))
                    s->byte_count++;
                }
            }
          else
            {
              SANE_Int color = s->total_bytes % 3;
              buf[*len] = (buffer_pointers[color][s->byte_count] >> 8) & 0xff;
              if (s->total_bytes % (inflate_x * 3) == (inflate_x * 3 - 1))
                s->byte_count++;
#ifdef DEBUG_BRIGHTNESS
              s->average_white += buf[*len];
              s->max_white =
                (buf[*len] > s->max_white) ? buf[*len] : s->max_white;
              s->min_black =
                (buf[*len] < s->min_black) ? buf[*len] : s->min_black;
#endif
            }
        }
      else
        {
          /* gray */
          if (s->reader->params.depth > 8)
            {
              if ((s->total_bytes % 2) == 0)
                {
                  if (little_endian)
                    buf[*len] = buffer_pointers[0][s->byte_count] & 0xff;
                  else
                    buf[*len] =
                      (buffer_pointers[0][s->byte_count] >> 8) & 0xff;
                }
              else
                {
                  if (little_endian)
                    buf[*len] =
                      (buffer_pointers[0][s->byte_count] >> 8) & 0xff;
                  else
                    buf[*len] = buffer_pointers[0][s->byte_count] & 0xff;
                  if (s->total_bytes % (2 * inflate_x) == (2 * inflate_x - 1))
                    s->byte_count++;
                }
            }
          else
            {
              buf[*len] = (buffer_pointers[0][s->byte_count] >> 8) & 0xff;
              if (s->total_bytes % inflate_x == (inflate_x - 1))
                s->byte_count++;
            }
        }
      (*len)++;
      s->total_bytes++;
    }

  DBG (4, "sane_read: exit (line %d of %d, byte_count %d of %d, %d bytes, "
       "%d total)\n",
       s->line, s->reader->params.pixel_ys, s->byte_count,
       s->reader->params.pixel_xs, *len, s->total_bytes);
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  GT68xx_Scanner *s = handle;

  DBG (5, "sane_cancel: start\n");

  if (s->scanning)
    {
      s->scanning = SANE_FALSE;
      if (s->total_bytes != (s->params.bytes_per_line * s->params.lines))
        DBG (1, "sane_cancel: warning: scanned %d bytes, expected %d "
             "bytes\n", s->total_bytes,
             s->params.bytes_per_line * s->params.lines);
      else
        {
          struct timeval now;
          int secs;

          gettimeofday (&now, 0);
          secs = now.tv_sec - s->start_time.tv_sec;

          DBG (3,
               "sane_cancel: scan finished, scanned %d bytes in %d seconds\n",
               s->total_bytes, secs);
#ifdef DEBUG_BRIGHTNESS
          DBG (1,
               "sane_cancel: average white: %d, max_white=%d, min_black=%d\n",
               s->average_white / s->total_bytes, s->max_white, s->min_black);
#endif

        }
      /* some scanners don't like this command when cancelling a scan */
      sanei_usb_set_timeout (SHORT_TIMEOUT);
      gt68xx_device_fix_descriptor (s->dev);
      gt68xx_scanner_stop_scan (s);
      sanei_usb_set_timeout (LONG_TIMEOUT);

      if (s->dev->model->flags & GT68XX_FLAG_SHEET_FED)
        {
          gt68xx_device_paperfeed (s->dev);
        } 
      else
        {
          sanei_usb_set_timeout (SHORT_TIMEOUT);
          gt68xx_scanner_wait_for_positioning (s);
          sanei_usb_set_timeout (LONG_TIMEOUT);
          gt68xx_device_carriage_home (s->dev);
        }
      if (s->gamma_table)
        free (s->gamma_table);
      s->gamma_table = 0;
    }
  else
    {
      DBG (4, "sane_cancel: scan has not been initiated yet, "
           "or it is already aborted\n");
    }

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

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  GT68xx_Scanner *s = handle;

  DBG (5, "sane_set_io_mode: handle = %p, non_blocking = %s\n",
       handle, non_blocking == SANE_TRUE ? "true" : "false");

  if (!s->scanning)
    {
      DBG (1, "sane_set_io_mode: not scanning\n");
      return SANE_STATUS_INVAL;
    }
  if (non_blocking)
    return SANE_STATUS_UNSUPPORTED;
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  GT68xx_Scanner *s = handle;

  DBG (5, "sane_get_select_fd: handle = %p, fd = %p\n", handle, (void *) fd);

  if (!s->scanning)
    {
      DBG (1, "sane_get_select_fd: not scanning\n");
      return SANE_STATUS_INVAL;
    }
  return SANE_STATUS_UNSUPPORTED;
}

/* vim: set sw=2 cino=>2se-1sn-1s{s^-1st0(0u0 smarttab expandtab: */
