/* sane - Scanner Access Now Easy.
   (C) 2003 Henning Meier-Geinitz <henning@meier-geinitz.de>.

   Based on the mustek (SCSI) backend.

   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

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

   This file implements a SANE backend for scanners based on the Mustek
   MA-1509 chipset. Currently the Mustek BearPaw 1200F is known to work.
*/


/**************************************************************************/
/* ma1509 backend version                                                 */
#define BUILD 3
/**************************************************************************/

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

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

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

#define BACKEND_NAME	ma1509
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_config.h"

#include "ma1509.h"

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

/* Debug level from sanei_init_debug */
static SANE_Int debug_level;

static SANE_Int num_devices;
static Ma1509_Device *first_dev;
static Ma1509_Scanner *first_handle;
static const SANE_Device **devlist = 0;

static int warmup_time = MA1509_WARMUP_TIME;

/* Array of newly attached devices */
static Ma1509_Device **new_dev;

/* Length of new_dev array */
static SANE_Int new_dev_len;

/* Number of entries alloced for new_dev */
static SANE_Int new_dev_alloced;

static SANE_String_Const mode_list[] = {
  SANE_I18N ("Lineart"), SANE_I18N ("Gray"), SANE_I18N ("Color"),
  0
};

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

static SANE_Word resolution_list[] = {
  9,
  50, 100, 150, 200, 300, 400, 450, 500, 600
};

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


/* "SCSI" command buffers used by the backend */
static const SANE_Byte scsi_inquiry[] = {
  0x12, 0x01, 0x00, 0x00, 0x00, 0x00, INQ_LEN, 0x00
};
static const SANE_Byte scsi_test_unit_ready[] = {
  0x03, 0x01, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00
};
static const SANE_Byte scsi_set_window[] = {
  0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00
};


static void
print_data_buffer (const SANE_Byte * buffer, size_t len)
{
  SANE_Byte buffer_byte_list[50];
  SANE_Byte buffer_byte[5];
  const SANE_Byte *pp;

  buffer_byte_list[0] = '\0';
  for (pp = buffer; pp < (buffer + len); pp++)
    {
      sprintf ((SANE_String) buffer_byte, " %02x", *pp);
      strcat ((SANE_String) buffer_byte_list, (SANE_String) buffer_byte);
      if (((pp - buffer) % 0x10 == 0x0f) || (pp >= (buffer + len - 1)))
	{
	  DBG (5, "buffer: %s\n", buffer_byte_list);
	  buffer_byte_list[0] = '\0';
	}
    }
}

static SANE_Status
ma1509_cmd (Ma1509_Scanner * s, const SANE_Byte * cmd, SANE_Byte * data,
	    size_t * data_size)
{
  SANE_Status status;
  size_t size;
#define MA1509_WRITE_LIMIT (1024 * 64)
#define MA1509_READ_LIMIT (1024 * 256)

  DBG (5, "ma1509_cmd: fd=%d, cmd=%p, data=%p, data_size=%ld\n",
       s->fd, cmd, data, (long int) (data_size ? *data_size : 0));
  DBG (5, "ma1509_cmd: cmd = %02x %02x %02x %02x %02x %02x %02x %02x \n",
       cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6], cmd[7]);


  size = MA1509_COMMAND_LENGTH;
  status = sanei_usb_write_bulk (s->fd, cmd, &size);
  if (status != SANE_STATUS_GOOD || size != MA1509_COMMAND_LENGTH)
    {
      DBG (5,
	   "ma1509_cmd: sanei_usb_write_bulk returned %s (size = %ld, expected %d)\n",
	   sane_strstatus (status), (long int) size, MA1509_COMMAND_LENGTH);
      return status;
    }

  if (cmd[1] == 1)
    {
      /* receive data */
      if (data && data_size && *data_size)
	{
	  size_t bytes_left = *data_size;
	  DBG (5, "ma1509_cmd: trying to receive %ld bytes of data\n",
	       (long int) *data_size);

	  while (status == SANE_STATUS_GOOD && bytes_left > 0)
	    {
	      size = bytes_left;
	      if (size > MA1509_READ_LIMIT)
		size = MA1509_READ_LIMIT;

	      status =
		sanei_usb_read_bulk (s->fd, data + *data_size - bytes_left,
				     &size);

	      if (status != SANE_STATUS_GOOD)
		{
		  DBG (1, "ma1509_cmd: sanei_usb_read_bulk returned %s\n",
		       sane_strstatus (status));
		  return status;
		}
	      bytes_left -= size;
	      DBG (5, "ma1509_cmd: read %ld bytes, %ld bytes to go\n",
		   (long int) size, (long int) bytes_left);
	    }
	  if (debug_level >= 5)
	    print_data_buffer (data, *data_size);
	}
    }
  else
    {
      /* send data */
      if (data && data_size && *data_size)
	{
	  size_t bytes_left = *data_size;

	  DBG (5, "ma1509_cmd: sending %ld bytes of data\n",
	       (long int) *data_size);
	  if (debug_level >= 5)
	    print_data_buffer (data, *data_size);

	  while (status == SANE_STATUS_GOOD && bytes_left > 0)
	    {
	      size = bytes_left;
	      if (size > MA1509_WRITE_LIMIT)
		size = MA1509_WRITE_LIMIT;
	      status =
		sanei_usb_write_bulk (s->fd, data + *data_size - bytes_left,
				      &size);
	      if (status != SANE_STATUS_GOOD)
		{
		  DBG (1, "ma1509_cmd: sanei_usb_write_bulk returned %s\n",
		       sane_strstatus (status));
		  return status;
		}
	      bytes_left -= size;
	      DBG (5, "ma1509_cmd: wrote %ld bytes, %ld bytes to go\n",
		   (long int) size, (long int) bytes_left);
	    }

	}
    }

  DBG (5, "ma1509_cmd: finished: data_size=%ld, status=%s\n",
       (long int) (data_size ? *data_size : 0), sane_strstatus (status));
  return status;
}

static SANE_Status
test_unit_ready (Ma1509_Scanner * s)
{
  SANE_Status status;
  SANE_Byte buffer[0x04];
  size_t size = sizeof (buffer);

  status = ma1509_cmd (s, scsi_test_unit_ready, buffer, &size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "test_unit_ready: ma1509_cmd failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  if (buffer[1] == 0x14)
    s->hw->has_adf = SANE_TRUE;
  else
    s->hw->has_adf = SANE_FALSE;

  return status;
}

static SANE_Status
attach (SANE_String_Const devname, Ma1509_Device ** devp)
{
  SANE_Int fw_revision;
  SANE_Byte result[INQ_LEN];
  SANE_Byte inquiry_byte_list[50], inquiry_text_list[17];
  SANE_Byte inquiry_byte[5], inquiry_text[5];
  SANE_Byte *model_name = result + 44;
  Ma1509_Scanner s;
  Ma1509_Device *dev, new_dev;
  SANE_Status status;
  size_t size;
  SANE_Byte *pp;
  SANE_Word vendor, product;

  if (devp)
    *devp = 0;

  for (dev = first_dev; dev; dev = dev->next)
    if (strcmp (dev->sane.name, devname) == 0)
      {
	if (devp)
	  *devp = dev;
	return SANE_STATUS_GOOD;
      }

  memset (&new_dev, 0, sizeof (new_dev));
  memset (&s, 0, sizeof (s));
  s.hw = &new_dev;

  DBG (3, "attach: trying device %s\n", devname);

  status = sanei_usb_open (devname, &s.fd);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: sanei_usb_open failed: %s\n", sane_strstatus (status));
      return status;
    }

  status = sanei_usb_get_vendor_product (s.fd, &vendor, &product);
  if (status != SANE_STATUS_GOOD && status != SANE_STATUS_UNSUPPORTED)
    {
      DBG (1, "attach: sanei_usb_get_vendor_product failed: %s\n",
	   sane_strstatus (status));
      sanei_usb_close (s.fd);
      return status;
    }
  if (status == SANE_STATUS_UNSUPPORTED)
    {
      DBG (3, "attach: can't detect vendor/product, trying anyway\n");
    }
  else if (vendor != 0x055f || product != 0x0010)
    {
      DBG (1, "attach: unknown vendor/product (0x%x/0x%x)\n", vendor,
	   product);
      sanei_usb_close (s.fd);
      return SANE_STATUS_UNSUPPORTED;
    }

  DBG (4, "attach: sending TEST_UNIT_READY\n");
  status = test_unit_ready (&s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: test_unit_ready device %s failed (%s)\n", devname,
	   sane_strstatus (status));
      sanei_usb_close (s.fd);
      return status;
    }

  DBG (4, "attach: sending INQUIRY\n");
  size = sizeof (result);
  memset (result, 0, sizeof (result));
  status = ma1509_cmd (&s, scsi_inquiry, result, &size);
  if (status != SANE_STATUS_GOOD || size != INQ_LEN)
    {
      DBG (1, "attach: inquiry for device %s failed (%s)\n", devname,
	   sane_strstatus (status));
      sanei_usb_close (s.fd);
      return status;
    }

  sanei_usb_close (s.fd);

  if ((result[0] & 0x1f) != 0x06)
    {
      DBG (1, "attach: device %s doesn't look like a scanner at all (%d)\n",
	   devname, result[0] & 0x1f);
      return SANE_STATUS_INVAL;
    }

  if (debug_level >= 5)
    {
      /* print out inquiry */
      DBG (5, "attach: inquiry output:\n");
      inquiry_byte_list[0] = '\0';
      inquiry_text_list[0] = '\0';
      for (pp = result; pp < (result + INQ_LEN); pp++)
	{
	  sprintf ((SANE_String) inquiry_text, "%c",
		   (*pp < 127) && (*pp > 31) ? *pp : '.');
	  strcat ((SANE_String) inquiry_text_list,
		  (SANE_String) inquiry_text);
	  sprintf ((SANE_String) inquiry_byte, " %02x", *pp);
	  strcat ((SANE_String) inquiry_byte_list,
		  (SANE_String) inquiry_byte);
	  if ((pp - result) % 0x10 == 0x0f)
	    {
	      DBG (5, "%s  %s\n", inquiry_byte_list, inquiry_text_list);
	      inquiry_byte_list[0] = '\0';
	      inquiry_text_list[0] = '\0';
	    }
	}
    }

  /* get firmware revision as BCD number:             */
  fw_revision = (result[32] - '0') << 8 | (result[34] - '0') << 4
    | (result[35] - '0');
  DBG (4, "attach: firmware revision %d.%02x\n", fw_revision >> 8,
       fw_revision & 0xff);

  dev = malloc (sizeof (*dev));
  if (!dev)
    return SANE_STATUS_NO_MEM;

  memcpy (dev, &new_dev, sizeof (*dev));

  dev->name = strdup (devname);
  if (!dev->name)
    return SANE_STATUS_NO_MEM;
  dev->sane.name = (SANE_String_Const) dev->name;
  dev->sane.vendor = "Mustek";
  dev->sane.type = "flatbed scanner";

  dev->x_range.min = 0;
  dev->y_range.min = 0;
  dev->x_range.quant = SANE_FIX (0.1);
  dev->y_range.quant = SANE_FIX (0.1);
  dev->x_trans_range.min = 0;
  dev->y_trans_range.min = 0;
  /* default to something really small to be on the safe side: */
  dev->x_trans_range.max = SANE_FIX (8.0 * MM_PER_INCH);
  dev->y_trans_range.max = SANE_FIX (5.0 * MM_PER_INCH);
  dev->x_trans_range.quant = SANE_FIX (0.1);
  dev->y_trans_range.quant = SANE_FIX (0.1);

  DBG (3, "attach: scanner id: %.11s\n", model_name);

  /* BearPaw 1200F (SCSI-over-USB) */
  if (strncmp ((SANE_String) model_name, " B06", 4) == 0)
    {
      dev->x_range.max = SANE_FIX (211.3);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (296.7);

      dev->x_trans_range.min = SANE_FIX (0);
      dev->y_trans_range.min = SANE_FIX (0);
      dev->x_trans_range.max = SANE_FIX (150.0);
      dev->y_trans_range.max = SANE_FIX (175.0);

      dev->sane.model = "BearPaw 1200F";
    }
  else
    {
      DBG (0, "attach: this scanner (ID: %s) is not supported yet\n",
	   model_name);
      DBG (0, "attach: please set the debug level to 5 and send a debug "
	   "report\n");
      DBG (0, "attach: to henning@meier-geinitz.de (export "
	   "SANE_DEBUG_MA1509=5\n");
      DBG (0, "attach: scanimage -L 2>debug.txt). Thank you.\n");
      free (dev);
      return SANE_STATUS_INVAL;
    }

  DBG (2, "attach: found Mustek %s %s %s%s\n",
       dev->sane.model, dev->sane.type, dev->has_ta ? "(TA)" : "",
       dev->has_adf ? "(ADF)" : "");

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

  if (devp)
    *devp = dev;
  return SANE_STATUS_GOOD;
}


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
init_options (Ma1509_Scanner * s)
{
  SANE_Int i;

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

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

  s->opt[OPT_NUM_OPTS].name = "";
  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].cap = 0;
  s->opt[OPT_MODE_GROUP].size = 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 (mode_list[1]);
  if (!s->val[OPT_MODE].s)
    return SANE_STATUS_NO_MEM;

  /* resolution */
  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 = resolution_list;
  s->val[OPT_RESOLUTION].w = 50;

  /* 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].size = max_string_size (ta_source_list);
  s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_SOURCE].constraint.string_list = ta_source_list;
  s->val[OPT_SOURCE].s = strdup (ta_source_list[0]);
  if (!s->val[OPT_SOURCE].s)
    return SANE_STATUS_NO_MEM;
  s->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;

  /* 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].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  s->val[OPT_PREVIEW].w = 0;

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

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

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

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

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

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

  /* 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->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_THRESHOLD].w = 128;

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

  /* red gamma vector */
  s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
  s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_R].size = MA1509_GAMMA_SIZE * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_R].wa = &s->red_gamma_table[0];
  s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;
  s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
  for (i = 0; i < MA1509_GAMMA_SIZE; i++)
    s->red_gamma_table[i] = i * MA1509_GAMMA_SIZE / 256;

  /* green gamma vector */
  s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
  s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_G].size = MA1509_GAMMA_SIZE * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_G].wa = &s->green_gamma_table[0];
  s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;
  s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
  for (i = 0; i < MA1509_GAMMA_SIZE; i++)
    s->green_gamma_table[i] = i * MA1509_GAMMA_SIZE / 256;

  /* blue gamma vector */
  s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
  s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_B].size = MA1509_GAMMA_SIZE * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_B].wa = &s->blue_gamma_table[0];
  s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;
  s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
  for (i = 0; i < MA1509_GAMMA_SIZE; i++)
    s->blue_gamma_table[i] = i * MA1509_GAMMA_SIZE / 256;

  return SANE_STATUS_GOOD;
}

static SANE_Status
attach_one_device (SANE_String_Const devname)
{
  Ma1509_Device *dev;

  attach (devname, &dev);
  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;
}

static SANE_Status
set_window (Ma1509_Scanner * s)
{
  SANE_Byte buffer[0x30], *cp;
  double pixels_per_mm;
  size_t size = sizeof (buffer);
  SANE_Status status;
  SANE_Int tlx, tly, width, height;
  SANE_Int offset = 0;
  struct timeval now;
  long remaining_time;

  /* check if lamp is warmed up */
  gettimeofday (&now, 0);
  remaining_time = warmup_time - (now.tv_sec - s->lamp_time);
  if (remaining_time > 0)
    {
      DBG (0, "Warm-up in progress: please wait %2ld seconds\n",
	   remaining_time);
      sleep (remaining_time);
    }

  memset (buffer, 0, size);
  cp = buffer;

  STORE16B (cp, 0);		/* window identifier            */
  STORE16B (cp, s->val[OPT_RESOLUTION].w);
  STORE16B (cp, 0);		/* not used acc. to specs       */

  pixels_per_mm = s->val[OPT_RESOLUTION].w / MM_PER_INCH;

  tlx = SANE_UNFIX (s->val[OPT_TL_X].w) * pixels_per_mm + 0.5;
  tly = SANE_UNFIX (s->val[OPT_TL_Y].w) * pixels_per_mm + 0.5;

  width = (SANE_UNFIX (s->val[OPT_BR_X].w) - SANE_UNFIX (s->val[OPT_TL_X].w))
    * pixels_per_mm + 0.5;
  height = (SANE_UNFIX (s->val[OPT_BR_Y].w) - SANE_UNFIX (s->val[OPT_TL_Y].w))
    * pixels_per_mm + 0.5 + offset;

  if (strcmp (s->val[OPT_MODE].s, "Lineart") == 0)
    {
      width /= 64;
      width *= 64;
      if (!width)
	width = 64;
    }
  else
    {
      width /= 8;
      width *= 8;
      if (!width)
	width = 8;
    }


  DBG (4, "set_window: tlx=%d (%d mm); tly=%d (%d mm); width=%d (%d mm); "
       "height=%d (%d mm)\n", tlx, (int) (tlx / pixels_per_mm), tly,
       (int) (tly / pixels_per_mm), width, (int) (width / pixels_per_mm),
       height, (int) (height / pixels_per_mm));


  STORE16B (cp, 0);
  STORE16B (cp, tlx);
  STORE16B (cp, 0);
  STORE16B (cp, tly);
  *cp++ = 0x14;
  *cp++ = 0xc0;
  STORE16B (cp, width);
  *cp++ = 0x28;
  *cp++ = 0x20;
  STORE16B (cp, height);

  s->hw->ppl = width;
  s->hw->bpl = s->hw->ppl;

  s->hw->lines = height;

  *cp++ = 0x00;			/* brightness, not impl.        */
  /* threshold */
  if (strcmp (s->val[OPT_MODE].s, "Lineart") == 0)
    *cp++ = (SANE_Byte) s->val[OPT_THRESHOLD].w;
  else
    *cp++ = 0x80;
  *cp++ = 0x00;			/* contrast, not impl.          */
  *cp++ = 0x00;			/* ???               .          */

  /* Note that 'image composition' has no meaning for the SE series     */
  /* Mode selection is accomplished solely by bits/pixel (1, 8, 24)     */
  if (strcmp (s->val[OPT_MODE].s, "Color") == 0)
    {
      *cp++ = 24;		/* 24 bits/pixel in color mode  */
      s->hw->bpl *= 3;
    }
  else if (strcmp (s->val[OPT_MODE].s, "Gray") == 0)
    *cp++ = 8;			/* 8 bits/pixel in gray mode    */
  else
    {
      *cp++ = 1;		/* 1 bit/pixel in lineart mode  */
      s->hw->bpl /= 8;
    }

  cp += 13;			/* skip reserved bytes          */
  *cp++ = 0x00;			/* lamp mode  */
  if (strcmp (s->val[OPT_MODE].s, "Lineart") != 0)
    *cp++ = 0x02;		/* ???  */

  status = ma1509_cmd (s, scsi_set_window, buffer, &size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "set_window: ma1509_cmd failed: %s\n", sane_strstatus (status));
      return status;
    }
  return status;
}

static SANE_Status
calibration (Ma1509_Scanner * s)
{
  SANE_Byte cmd[0x08], *buffer, *calibration_buffer;
  SANE_Status status;
  SANE_Int ppl = 5312;
  SANE_Int lines = 40;
  size_t total_size = lines * ppl;
  SANE_Int color, column, line;

  buffer = malloc (total_size * 3);
  if (!buffer)
    {
      DBG (1,
	   "calibration: couldn't malloc %lu bytes for calibration buffer\n",
	   (u_long) (total_size * 3));
      return SANE_STATUS_NO_MEM;
    }
  memset (buffer, 0x00, total_size);

  memset (cmd, 0, 8);
  cmd[0] = 0x28;		/* read data */
  cmd[1] = 0x01;		/* read */
  cmd[2] = 0x01;		/* calibration */
  cmd[4] = (total_size >> 16) & 0xff;
  cmd[5] = (total_size >> 8) & 0xff;
  cmd[6] = total_size & 0xff;
  total_size *= 3;
  status = ma1509_cmd (s, cmd, buffer, &total_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "calibration: ma1509_cmd read data failed: %s\n",
	   sane_strstatus (status));
      free (buffer);
      return status;
    }

  calibration_buffer = malloc (ppl);
  if (!calibration_buffer)
    {
      DBG (1,
	   "calibration: couldn't malloc %d bytes for calibration buffer\n",
	   ppl);
      return SANE_STATUS_NO_MEM;
    }
  memset (calibration_buffer, 0x00, ppl);

  memset (cmd, 0, 8);
  cmd[0] = 0x2a;		/* send data */
  cmd[1] = 0x00;		/* write */
  cmd[2] = 0x01;		/* calibration */
  cmd[5] = (ppl >> 8) & 0xff;
  cmd[6] = ppl & 0xff;

  for (color = 1; color < 4; color++)
    {
      cmd[4] = color;

      for (column = 0; column < ppl; column++)
	{
	  SANE_Int average = 0;

	  for (line = 0; line < lines; line++)
	    average += buffer[line * ppl * 3 + column * 3 + (color - 1)];
	  average /= lines;
	  if (average < 1)
	    average = 1;
	  if (average > 255)
	    average = 255;

	  average = (256 * 256) / average - 256;
	  if (average < 0)
	    average = 0;
	  if (average > 255)
	    average = 255;
	  calibration_buffer[column] = average;
	}

      total_size = ppl;
      status = ma1509_cmd (s, cmd, calibration_buffer, &total_size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "calibration: ma1509_cmd send data failed: %s\n",
	       sane_strstatus (status));
	  free (buffer);
	  free (calibration_buffer);
	  return status;
	}
    }
  free (buffer);
  free (calibration_buffer);
  DBG (4, "calibration: done\n");
  return status;
}


static SANE_Status
send_gamma (Ma1509_Scanner * s)
{
  SANE_Byte cmd[0x08], *buffer;
  SANE_Status status;
  size_t total_size = MA1509_GAMMA_SIZE;
  SANE_Int color;

  buffer = malloc (total_size);
  if (!buffer)
    {
      DBG (1, "send_gamma: couldn't malloc %lu bytes for gamma  buffer\n",
	   (u_long) total_size);
      return SANE_STATUS_NO_MEM;
    }

  memset (cmd, 0, 8);
  cmd[0] = 0x2a;		/* send data */
  cmd[1] = 0x00;		/* write */
  cmd[2] = 0x03;		/* gamma */
  cmd[5] = (total_size >> 8) & 0xff;
  cmd[6] = total_size & 0xff;
  for (color = 1; color < 4; color++)
    {
      unsigned int i;

      if (s->val[OPT_CUSTOM_GAMMA].w)
	{
	  SANE_Int *int_buffer;

	  if (color == 1)
	    int_buffer = s->red_gamma_table;
	  else if (color == 2)
	    int_buffer = s->green_gamma_table;
	  else
	    int_buffer = s->blue_gamma_table;
	  for (i = 0; i < total_size; i++)
	    buffer[i] = int_buffer[i];
	}
      else
	{
	  /* linear tables */
	  for (i = 0; i < total_size; i++)
	    buffer[i] = i * 256 / total_size;
	}

      cmd[4] = color;
      status = ma1509_cmd (s, cmd, buffer, &total_size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "send_gamma: ma1509_cmd send data failed: %s\n",
	       sane_strstatus (status));
	  free (buffer);
	  return status;
	}
    }
  if (!s->val[OPT_CUSTOM_GAMMA].w)
    free (buffer);
  DBG (4, "send_gamma: done\n");
  return status;
}


static SANE_Status
start_scan (Ma1509_Scanner * s)
{
  SANE_Byte cmd[8];
  SANE_Status status;

  DBG (4, "start_scan\n");
  memset (cmd, 0, 8);

  cmd[0] = 0x1b;
  cmd[1] = 0x01;
  cmd[2] = 0x01;

  status = ma1509_cmd (s, cmd, NULL, NULL);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "start_scan: ma1509_cmd failed: %s\n", sane_strstatus (status));
      return status;
    }
  return status;
}

static SANE_Status
turn_lamp (Ma1509_Scanner * s, SANE_Bool is_on)
{
  SANE_Status status;
  SANE_Byte buffer[0x30];
  size_t size = sizeof (buffer);
  struct timeval lamp_time;

  DBG (4, "turn_lamp %s\n", is_on ? "on" : "off");
  memset (buffer, 0, size);
  if (is_on)
    buffer[0x28] = 0x01;
  else
    buffer[0x28] = 0x02;

  status = ma1509_cmd (s, scsi_set_window, buffer, &size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "turn_lamp: ma1509_cmd set_window failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  gettimeofday (&lamp_time, 0);
  s->lamp_time = lamp_time.tv_sec;
  return status;
}

static SANE_Status
stop_scan (Ma1509_Scanner * s)
{
  SANE_Byte cmd[8];
  SANE_Status status;

  DBG (4, "stop_scan\n");
  memset (cmd, 0, 8);

  cmd[0] = 0x1b;
  cmd[1] = 0x01;
  cmd[2] = 0x00;

  status = ma1509_cmd (s, cmd, NULL, NULL);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "stop_scan: ma1509_cmd failed: %s\n", sane_strstatus (status));
      return status;
    }

  DBG (4, "stop_scan: scan stopped\n");
  return status;
}


static SANE_Status
start_read_data (Ma1509_Scanner * s)
{
  SANE_Byte cmd[8];
  SANE_Status status;
  SANE_Int total_size = s->hw->ppl * s->hw->lines;

  if (strcmp (s->val[OPT_MODE].s, "Lineart") == 0)
    total_size /= 8;

  memset (cmd, 0, 8);

  cmd[0] = 0x28;		/* read data */
  cmd[1] = 0x01;		/* read */
  cmd[2] = 0x00;		/* scan data */
  cmd[3] = (total_size >> 24) & 0xff;
  cmd[4] = (total_size >> 16) & 0xff;
  cmd[5] = (total_size >> 8) & 0xff;
  cmd[6] = total_size & 0xff;
  status = ma1509_cmd (s, cmd, NULL, NULL);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "stop_scan: ma1509_cmd failed: %s\n", sane_strstatus (status));
      return status;
    }
  return status;
}

static SANE_Status
read_data (Ma1509_Scanner * s, SANE_Byte * buffer, SANE_Int * size)
{
  size_t local_size = *size;
  SANE_Status status;

  status = sanei_usb_read_bulk (s->fd, buffer, &local_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "read_data: sanei_usb_read_bulk failed: %s\n",
	   sane_strstatus (status));
      return status;
    }
  *size = local_size;
  return status;
}




/**************************************************************************/
/*                            SANE API calls                              */
/**************************************************************************/

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

  DBG_INIT ();

#ifdef DBG_LEVEL
  debug_level = DBG_LEVEL;
#else
  debug_level = 0;
#endif

  DBG (2, "SANE ma1509 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 (4, "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;

  fp = sanei_config_open (MA1509_CONFIG_FILE);
  if (!fp)
    {
      /* default to /dev/usb/scanner0 instead of insisting on config file */
      DBG (3, "sane_init: couldn't find config file (%s), trying "
	   "/dev/usb/scanner0 directly\n", MA1509_CONFIG_FILE);
      attach ("/dev/usb/scanner0", 0);
      return SANE_STATUS_GOOD;
    }
  linenumber = 0;
  DBG (4, "sane_init: reading config file `%s'\n", MA1509_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 (5, "sane_init: config file line %d: ignoring empty line\n",
	       linenumber);
	  if (word)
	    free (word);
	  continue;
	}
      if (word[0] == '#')
	{
	  DBG (5, "sane_init: config file line %d: ignoring comment line\n",
	       linenumber);
	  free (word);
	  continue;
	}
      if (strcmp (word, "option") == 0)
	{
	  free (word);
	  word = 0;
	  cp = sanei_config_get_string (cp, &word);

	  if (!word)
	    {
	      DBG (1, "sane_init: config file line %d: missing quotation mark?\n",
		   linenumber);
	      continue;
	    }

	  if (strcmp (word, "warmup-time") == 0)
	    {
	      long local_warmup_time;

	      free (word);
	      word = 0;
	      cp = sanei_config_get_string (cp, &word);

	      if (!word)
		{
		  DBG (1, "sane_init: config file line %d: missing quotation mark?\n",
		       linenumber);
		  continue;
		}

	      errno = 0;
	      local_warmup_time = strtol (word, &end, 0);

	      if (end == word)
		{
		  DBG (3, "sane-init: config file line %d: warmup-time must "
		       "have a parameter; using default (%d)\n",
		       linenumber, warmup_time);
		}
	      else if (errno)
		{
		  DBG (3, "sane-init: config file line %d: warmup-time `%s' "
		       "is invalid (%s); using default (%d)\n", linenumber,
		       word, strerror (errno), warmup_time);
		}
	      else
		{
		  warmup_time = local_warmup_time;
		  DBG (4,
		       "sane_init: config file line %d: warmup-time set "
		       "to %d seconds\n", linenumber, warmup_time);

		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else
	    {
	      DBG (3, "sane_init: config file line %d: ignoring unknown "
		   "option `%s'\n", linenumber, word);
	      if (word)
		free (word);
	      word = 0;
	    }
	}
      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;
}

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

  DBG (4, "sane_exit\n");
  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free (dev->name);
      free (dev);
    }
  if (devlist)
    free (devlist);
  devlist = 0;
  first_dev = 0;
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  Ma1509_Device *dev;
  SANE_Int i;

  DBG (4, "sane_get_devices: %d devices %s\n", num_devices,
       local_only ? "(local only)" : "");
  if (devlist)
    free (devlist);

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

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

  *device_list = devlist;
  DBG (5, "sane_get_devices: end\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Ma1509_Device *dev;
  SANE_Status status;
  Ma1509_Scanner *s;

  if (!devicename)
    {
      DBG (1, "sane_open: devicename is null!\n");
      return SANE_STATUS_INVAL;
    }
  if (!handle)
    {
      DBG (1, "sane_open: handle is null!\n");
      return SANE_STATUS_INVAL;
    }
  DBG (4, "sane_open: devicename=%s\n", devicename);

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

      if (!dev)
	{
	  status = attach (devicename, &dev);
	  if (status != SANE_STATUS_GOOD)
	    return status;
	}
    }
  else
    /* empty devicname -> use first device */
    dev = first_dev;

  if (!dev)
    {
      DBG (1, "sane_open: %s doesn't seem to exist\n", devicename);
      return SANE_STATUS_INVAL;
    }

  s = malloc (sizeof (*s));
  if (!s)
    return SANE_STATUS_NO_MEM;
  memset (s, 0, sizeof (*s));
  s->fd = -1;
  s->hw = dev;
  init_options (s);

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

  status = sanei_usb_open (s->hw->sane.name, &s->fd);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_open: couldn't open %s: %s\n", s->hw->sane.name,
	   sane_strstatus (status));
      return status;
    }

  status = turn_lamp (s, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_open: couldn't turn on lamp: %s\n",
	   sane_strstatus (status));
      return status;
    }

  status = turn_lamp (s, SANE_TRUE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_open: couldn't turn on lamp: %s\n",
	   sane_strstatus (status));
      return status;
    }

  *handle = s;
  DBG (5, "sane_open: finished (handle=%p)\n", (void *) s);
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  Ma1509_Scanner *prev, *s;
  SANE_Status status;

  DBG (4, "sane_close: handle=%p\n", handle);

  /* 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 (1, "sane_close: invalid handle %p\n", handle);
      return;			/* oops, not a handle we know about */
    }

  if (s->val[OPT_MODE].s)
    free (s->val[OPT_MODE].s);
  if (s->val[OPT_SOURCE].s)
    free (s->val[OPT_SOURCE].s);

  status = turn_lamp (s, SANE_FALSE);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_close: couldn't turn off lamp: %s\n",
	   sane_strstatus (status));
      return;
    }
  sanei_usb_close (s->fd);

  if (prev)
    prev->next = s->next;
  else
    first_handle = s->next;
  free (handle);
  handle = 0;
}

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

  if (((unsigned) option >= NUM_OPTIONS) || (option < 0))
    {
      DBG (3, "sane_get_option_descriptor: option %d >= NUM_OPTIONS or < 0\n",
	   option);
      return 0;
    }
  if (!s)
    {
      DBG (1, "sane_get_option_descriptor: handle is null!\n");
      return 0;
    }
  if (s->opt[option].name && s->opt[option].name[0] != 0)
    DBG (4, "sane_get_option_descriptor for option %s (%sactive%s)\n",
	 s->opt[option].name,
	 s->opt[option].cap & SANE_CAP_INACTIVE ? "in" : "",
	 s->opt[option].cap & SANE_CAP_ADVANCED ? ", advanced" : "");
  else
    DBG (4, "sane_get_option_descriptor for option \"%s\" (%sactive%s)\n",
	 s->opt[option].title,
	 s->opt[option].cap & SANE_CAP_INACTIVE ? "in" : "",
	 s->opt[option].cap & SANE_CAP_ADVANCED ? ", advanced" : "");
  return s->opt + option;
}

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

  if (((unsigned) option >= NUM_OPTIONS) || (option < 0))
    {
      DBG (3, "sane_control_option: option %d < 0 or >= NUM_OPTIONS\n",
	   option);
      return SANE_STATUS_INVAL;
    }
  if (!s)
    {
      DBG (1, "sane_control_option: handle is null!\n");
      return SANE_STATUS_INVAL;
    }
  if (!val && s->opt[option].type != SANE_TYPE_BUTTON)
    {
      DBG (1, "sane_control_option: val is null!\n");
      return SANE_STATUS_INVAL;
    }

  if (s->opt[option].name && s->opt[option].name[0] != 0)
    DBG (4, "sane_control_option (%s option %s)\n",
	 action == SANE_ACTION_GET_VALUE ? "get" :
	 (action == SANE_ACTION_SET_VALUE ? "set" : "unknown action with"),
	 s->opt[option].name);
  else
    DBG (4, "sane_control_option (%s option \"%s\")\n",
	 action == SANE_ACTION_GET_VALUE ? "get" :
	 (action == SANE_ACTION_SET_VALUE ? "set" : "unknown action with"),
	 s->opt[option].title);

  if (info)
    *info = 0;

  if (s->scanning)
    {
      DBG (3, "sane_control_option: don't use while scanning (option %s)\n",
	   s->opt[option].name);
      return SANE_STATUS_DEVICE_BUSY;
    }

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

  if (!SANE_OPTION_IS_ACTIVE (cap))
    {
      DBG (3, "sane_control_option: option %s is inactive\n",
	   s->opt[option].name);
      return SANE_STATUS_INVAL;
    }

  if (action == SANE_ACTION_GET_VALUE)
    {
      switch (option)
	{
	  /* word options: */
	case OPT_PREVIEW:
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_THRESHOLD:
	case OPT_CUSTOM_GAMMA:
	case OPT_NUM_OPTS:
	  *(SANE_Word *) val = s->val[option].w;
	  return SANE_STATUS_GOOD;

	  /* word-array options: */
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (val, s->val[option].wa, s->opt[option].size);
	  return SANE_STATUS_GOOD;

	  /* string options: */
	case OPT_SOURCE:
	case OPT_MODE:
	  strcpy (val, s->val[option].s);
	  return SANE_STATUS_GOOD;
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      if (!SANE_OPTION_IS_SETTABLE (cap))
	{
	  DBG (3, "sane_control_option: option %s is not setable\n",
	       s->opt[option].name);
	  return SANE_STATUS_INVAL;
	}

      status = sanei_constrain_value (s->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (4, "sane_control_option: constrain_value error (option %s)\n",
	       s->opt[option].name);
	  return status;
	}

      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_BR_X:
	case OPT_TL_Y:
	case OPT_BR_Y:
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  /* fall through */
	case OPT_THRESHOLD:
	case OPT_PREVIEW:
	  s->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* side-effect-free word-array options: */
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (s->val[option].wa, val, s->opt[option].size);
	  return SANE_STATUS_GOOD;

	case OPT_MODE:
	  {
	    SANE_Char *old_val = s->val[option].s;

	    if (old_val)
	      {
		if (strcmp (old_val, val) == 0)
		  return SANE_STATUS_GOOD;	/* no change */
		free (old_val);
	      }
	    if (info)
	      *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;

	    s->val[option].s = strdup (val);
	    if (!s->val[option].s)
	      return SANE_STATUS_NO_MEM;

	    s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;

	    if (strcmp (s->val[option].s, "Lineart") == 0)
	      {
		s->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	      }
	    else
	      {
		s->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
		if (s->val[OPT_CUSTOM_GAMMA].w)
		  {
		    s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
		  }
	      }
	    return SANE_STATUS_GOOD;
	  }

	case OPT_SOURCE:
	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
	  if (!s->val[option].s)
	    return SANE_STATUS_NO_MEM;

	  if (strcmp (val, "Transparency Adapter") == 0)
	    {
	      s->opt[OPT_TL_X].constraint.range = &s->hw->x_trans_range;
	      s->opt[OPT_TL_Y].constraint.range = &s->hw->y_trans_range;
	      s->opt[OPT_BR_X].constraint.range = &s->hw->x_trans_range;
	      s->opt[OPT_BR_Y].constraint.range = &s->hw->y_trans_range;
	    }
	  else
	    {
	      s->opt[OPT_TL_X].constraint.range = &s->hw->x_range;
	      s->opt[OPT_TL_Y].constraint.range = &s->hw->y_range;
	      s->opt[OPT_BR_X].constraint.range = &s->hw->x_range;
	      s->opt[OPT_BR_Y].constraint.range = &s->hw->y_range;
	    }
	  return SANE_STATUS_GOOD;

	  /* options with side-effects: */
	case OPT_CUSTOM_GAMMA:
	  w = *(SANE_Word *) val;

	  if (w == s->val[OPT_CUSTOM_GAMMA].w)
	    return SANE_STATUS_GOOD;	/* no change */

	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;

	  s->val[OPT_CUSTOM_GAMMA].w = w;

	  s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	  s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	  s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;

	  if (w && strcmp (s->val[OPT_MODE].s, "Lineart") != 0)
	    {
	      s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
	    }
	  return SANE_STATUS_GOOD;
	}

    }
  DBG (4, "sane_control_option: unknown action for option %s\n",
       s->opt[option].name);
  return SANE_STATUS_INVAL;
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Ma1509_Scanner *s = handle;
  SANE_String_Const mode;

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

  if (!s->scanning)
    {
      double width, height, dpi;

      memset (&s->params, 0, sizeof (s->params));

      width = SANE_UNFIX (s->val[OPT_BR_X].w - s->val[OPT_TL_X].w);
      height = SANE_UNFIX (s->val[OPT_BR_Y].w - s->val[OPT_TL_Y].w);
      dpi = s->val[OPT_RESOLUTION].w;

      /* make best-effort guess at what parameters will look like once
         scanning starts.  */
      if (dpi > 0.0 && width > 0.0 && height > 0.0)
	{
	  double dots_per_mm = dpi / MM_PER_INCH;

	  s->params.pixels_per_line = width * dots_per_mm;
	  s->params.lines = height * dots_per_mm;
	}
      mode = s->val[OPT_MODE].s;
      if (strcmp (mode, "Lineart") == 0)
	{
	  s->params.format = SANE_FRAME_GRAY;
	  s->params.bytes_per_line = (s->params.pixels_per_line + 7) / 8;
	  s->params.depth = 1;
	}
      else if (strcmp (mode, "Gray") == 0)
	{
	  s->params.format = SANE_FRAME_GRAY;
	  s->params.bytes_per_line = s->params.pixels_per_line;
	  s->params.depth = 8;
	}
      else
	{
	  /* it's one of the color modes... */

	  s->params.bytes_per_line = 3 * s->params.pixels_per_line;
	  s->params.depth = 8;
	  s->params.format = SANE_FRAME_RGB;
	}
    }
  s->params.last_frame = SANE_TRUE;
  if (params)
    *params = s->params;
  DBG (4, "sane_get_parameters: frame = %d; last_frame = %s; depth = %d\n",
       s->params.format, s->params.last_frame ? "true" : "false",
       s->params.depth);
  DBG (4, "sane_get_parameters: lines = %d; ppl = %d; bpl = %d\n",
       s->params.lines, s->params.pixels_per_line, s->params.bytes_per_line);

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Ma1509_Scanner *s = handle;
  SANE_Status status;
  SANE_String_Const mode;
  struct timeval start;

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

  DBG (4, "sane_start\n");

  status = sane_get_parameters (s, 0);
  if (status != SANE_STATUS_GOOD)
    return status;

  /* Check for inconsistencies */

  if (s->val[OPT_TL_X].w > s->val[OPT_BR_X].w)
    {
      DBG (0, "sane_start: %s (%.1f mm) is bigger than %s (%.1f mm) "
	   "-- aborting\n",
	   s->opt[OPT_TL_X].title, SANE_UNFIX (s->val[OPT_TL_X].w),
	   s->opt[OPT_BR_X].title, SANE_UNFIX (s->val[OPT_BR_X].w));
      return SANE_STATUS_INVAL;
    }
  if (s->val[OPT_TL_Y].w > s->val[OPT_BR_Y].w)
    {
      DBG (0, "sane_start: %s (%.1f mm) is bigger than %s (%.1f mm) "
	   "-- aborting\n",
	   s->opt[OPT_TL_Y].title, SANE_UNFIX (s->val[OPT_TL_Y].w),
	   s->opt[OPT_BR_Y].title, SANE_UNFIX (s->val[OPT_BR_Y].w));
      return SANE_STATUS_INVAL;
    }

  s->total_bytes = 0;
  s->read_bytes = 0;

  /* save start time */
  gettimeofday (&start, 0);
  s->start_time = start.tv_sec;
  /* translate options into s->mode for convenient access: */
  mode = s->val[OPT_MODE].s;

  status = set_window (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_start: set window command failed: %s\n",
	   sane_strstatus (status));
      goto stop_scanner_and_return;
    }

  status = test_unit_ready (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_start: test_unit_ready failed: %s\n",
	   sane_strstatus (status));
      goto stop_scanner_and_return;
    }

  if (strcmp (s->val[OPT_MODE].s, "Lineart") != 0)
    {
      status = calibration (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: calibration failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}

      status = send_gamma (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: send_gamma failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}
    }

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

  status = start_scan (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_start: start_scan command failed: %s\n",
	   sane_strstatus (status));
      goto stop_scanner_and_return;
    }

  status = start_read_data (s);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "sane_start: start_read_data command failed: %s\n",
	   sane_strstatus (status));
      goto stop_scanner_and_return;
    }

  s->params.bytes_per_line = s->hw->bpl;
  s->params.pixels_per_line = s->params.bytes_per_line;
  if (strcmp (s->val[OPT_MODE].s, "Color") == 0)
    s->params.pixels_per_line /= 3;
  else if (strcmp (s->val[OPT_MODE].s, "Lineart") == 0)
    s->params.pixels_per_line *= 8;

  s->params.lines = s->hw->lines;

  s->buffer = (SANE_Byte *) malloc (MA1509_BUFFER_SIZE);
  if (!s->buffer)
    return SANE_STATUS_NO_MEM;
  s->buffer_bytes = 0;

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

stop_scanner_and_return:
  sanei_usb_close (s->fd);
  s->scanning = SANE_FALSE;
  return status;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  Ma1509_Scanner *s = handle;
  SANE_Status status;
  SANE_Int total_size = s->hw->lines * s->hw->bpl;
  SANE_Int i;

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

  DBG (5, "sane_read\n");
  *len = 0;

  if (s->cancelled)
    {
      DBG (4, "sane_read: scan was cancelled\n");
      return SANE_STATUS_CANCELLED;
    }

  if (!s->scanning)
    {
      DBG (1, "sane_read: must call sane_start before sane_read\n");
      return SANE_STATUS_INVAL;
    }

  if (total_size - s->read_bytes <= 0)
    {
      DBG (4, "sane_read: EOF\n");
      stop_scan (s);
      s->scanning = SANE_FALSE;
      return SANE_STATUS_EOF;
    }

  if (s->buffer_bytes == 0)
    {
      SANE_Int size = MA1509_BUFFER_SIZE;
      if (size > (total_size - s->total_bytes))
	size = total_size - s->total_bytes;
      DBG (4, "sane_read: trying to read %d bytes\n", size);
      status = read_data (s, s->buffer, &size);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_read: read_data failed: %s\n",
	       sane_strstatus (status));
	  *len = 0;
	  return status;
	}
      s->total_bytes += size;
      s->buffer_start = s->buffer;
      s->buffer_bytes = size;
    }

  *len = max_len;
  if (*len > s->buffer_bytes)
    *len = s->buffer_bytes;

  memcpy (buf, s->buffer_start, *len);
  s->buffer_start += (*len);
  s->buffer_bytes -= (*len);
  s->read_bytes += (*len);

  /* invert for lineart mode */
  if (strcmp (s->val[OPT_MODE].s, "Lineart") == 0)
    {
      for (i = 0; i < *len; i++)
	buf[i] = ~buf[i];
    }

  DBG (4, "sane_read: read %d/%d bytes (%d bytes to go, %d total)\n", *len,
       max_len, total_size - s->read_bytes, total_size);

  return SANE_STATUS_GOOD;
}

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

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

  DBG (4, "sane_cancel\n");
  if (s->scanning)
    {
      s->cancelled = SANE_TRUE;
      stop_scan (s);
      free (s->buffer);
    }
  s->scanning = SANE_FALSE;
  DBG (4, "sane_cancel finished\n");
}

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

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

  DBG (4, "sane_set_io_mode: %s\n",
       non_blocking ? "non-blocking" : "blocking");

  if (!s->scanning)
    {
      DBG (1, "sane_set_io_mode: call sane_start before sane_set_io_mode");
      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)
{
  Ma1509_Scanner *s = handle;

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

  DBG (4, "sane_get_select_fd\n");
  if (!s->scanning)
    return SANE_STATUS_INVAL;

  return SANE_STATUS_UNSUPPORTED;
}
