/* sane - Scanner Access Now Easy.

   Copyright (C) 2002 Sergey Vlasov <vsu@altlinux.ru>
   Copyright (C) 2002 - 2005 Henning Meier-Geinitz <henning@meier-geinitz.de>

   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 71
#define MAX_DEBUG
#define WARMUP_TIME 60
#define CALIBRATION_HEIGHT 2.5

/* 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/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;
  u_int8_t *firstbyte = (u_int8_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;

  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);

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

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

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

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

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

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  SANE_Char line[PATH_MAX];
  SANE_Char *word;
  SANE_String_Const cp;
  SANE_Int linenumber;
  FILE *fp;

  DBG_INIT ();
  DBG (2, "SANE GT68xx backend version %d.%d build %d from %s\n", V_MAJOR,
       V_MINOR, BUILD, PACKAGE_STRING);

  if (version_code)
    *version_code = SANE_VERSION_CODE (V_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;

#ifdef DBG_LEVEL
  if (DBG_LEVEL > 0)
    {
      DBG (5, "sane_init: debug options are enabled, handle with care\n");
      debug_options = SANE_TRUE;
    }
#endif

  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);
	    }
	  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);
	    }
	  else
	    {
	      DBG (3, "sane_init: option `override' needs a parameter\n");
	    }
	}
      else if (strcmp (word, "afe") == 0)
	{
	  GT68xx_AFE_Parameters afe;
	  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);
  DBG (5, "sane_init: exit\n");

  return SANE_STATUS_GOOD;
}

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

  DBG (5, "sane_exit: start\n");
  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");

  if (devlist)
    free (devlist);

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

  dev_num = 0;
  for (dev = first_dev; dev_num < num_devices; dev = dev->next)
    {
      SANE_Device *sane_device;

      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;
    }
  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])
    {
      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
    {
      /* 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, "         henning@meier-geinitz.de. 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, "         henning@meier-geinitz.de. 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;
  RIE (init_options (s));
  dev->gray_mode_color = 0x02;

  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;
  gt68xx_scanner_free (s);
  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_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;
	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;
	    }
	  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;

	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 SANE_STATUS_GOOD;
}

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];

  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
    }

  s->calib = s->val[OPT_QUALITY_CAL].w;
  if (!(s->dev->model->flags & GT68XX_FLAG_NO_STOP))
    RIE (gt68xx_device_stop_scan (s->dev));

  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;

  RIE (gt68xx_scanner_calibrate (s, &scan_request));
  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 (0, "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

	}
      gt68xx_scanner_stop_scan (s);
      gt68xx_scanner_wait_for_positioning (s);
      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 allready 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: */
