/* sane - Scanner Access Now Easy.

   ScanMaker 3840 Backend
   Copyright (C) 2005 Earle F. Philhower, III
   earle@ziplabel.com - http://www.ziplabel.com

   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.

*/




#include "../include/sane/config.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>

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

#define BACKENDNAME sm3840
#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/sanei_config.h"

#include "sm3840.h"

#include "sm3840_scan.c"
#include "sm3840_lib.c"

static double sm3840_unit_convert (SANE_Int val);

static int num_devices;
static SM3840_Device *first_dev;
static SM3840_Scan *first_handle;
static const SANE_Device **devlist = 0;

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

static const SANE_Word resolution_list[] = {
  4, 1200, 600, 300, 150
};

static const SANE_Word bpp_list[] = {
  2, 8, 16
};

static const SANE_Range x_range = {
  SANE_FIX (0),
  SANE_FIX (215.91),		/* 8.5 inches */
  SANE_FIX (0)
};

static const SANE_Range y_range = {
  SANE_FIX (0),
  SANE_FIX (297.19),		/* 11.7 inches */
  SANE_FIX (0)
};

static const SANE_Range brightness_range = {
  1,
  4096,
  1.0
};

static const SANE_Range contrast_range = {
  SANE_FIX (0.1),
  SANE_FIX (9.9),
  SANE_FIX (0.1)
};

static const SANE_Range lamp_range = {
  1,
  15,
  1
};


/*--------------------------------------------------------------------------*/
static int
min (int a, int b)
{
  if (a < b)
    return a;
  else
    return b;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  SM3840_Scan *s = handle;

  DBG (2, "+sane-read:%p %p %d %p\n", (unsigned char *) s, buf, max_len, 
       (unsigned char *) len);
  DBG (2,
       "+sane-read:remain:%d offset:%d linesleft:%d linebuff:%p linesread:%d\n",
       s->remaining, s->offset, s->linesleft, s->line_buffer, s->linesread);

  if (!s->scanning)
    return SANE_STATUS_INVAL;

  if (!s->remaining)
    {
      if (!s->linesleft)
	{
	  *len = 0;
	  s->scanning = 0;
	  /* Move to home position */
	  reset_scanner ((usb_dev_handle *) s->udev);
	  /* Send lamp timeout */
	  set_lamp_timer ((usb_dev_handle *) s->udev, s->sm3840_params.lamp);

	  /* Free memory */
	  if (s->save_scan_line)
	    free (s->save_scan_line);
	  s->save_scan_line = NULL;
	  if (s->save_dpi1200_remap)
	    free (s->save_dpi1200_remap);
	  s->save_dpi1200_remap = NULL;
	  if (s->save_color_remap)
	    free (s->save_color_remap);
	  s->save_color_remap = NULL;

	  return SANE_STATUS_EOF;
	}

      record_line ((s->linesread == 0) ? 1 : 0,
		   (usb_dev_handle *) s->udev,
		   s->line_buffer,
		   s->sm3840_params.dpi,
		   s->sm3840_params.scanpix,
		   s->sm3840_params.gray,
		   (s->sm3840_params.bpp == 16) ? 1 : 0,
		   &s->save_i,
		   &s->save_scan_line,
		   &s->save_dpi1200_remap, &s->save_color_remap);
      s->remaining = s->sm3840_params.linelen;
      s->offset = 0;
      s->linesread++;
      s->linesleft--;
    }

  memcpy (buf, s->offset + s->line_buffer, min (max_len, s->remaining));

  *len = min (max_len, s->remaining);
  s->offset += min (max_len, s->remaining);
  s->remaining -= min (max_len, s->remaining);

  DBG (2, "-sane_read\n");

  return SANE_STATUS_GOOD;
}

/*--------------------------------------------------------------------------*/
void
sane_cancel (SANE_Handle h)
{
  SM3840_Scan *s = h;

  DBG (2, "trying to cancel...\n");
  if (s->scanning)
    {
      if (!s->cancelled)
	{
	  /* Move to home position */
	  reset_scanner ((usb_dev_handle *) s->udev);
	  /* Send lamp timeout */
	  set_lamp_timer ((usb_dev_handle *) s->udev, s->sm3840_params.lamp);

	  /* Free memory */
	  if (s->save_scan_line)
	    free (s->save_scan_line);
	  s->save_scan_line = NULL;
	  if (s->save_dpi1200_remap)
	    free (s->save_dpi1200_remap);
	  s->save_dpi1200_remap = NULL;
	  if (s->save_color_remap)
	    free (s->save_color_remap);
	  s->save_color_remap = NULL;

	  s->scanning = 0;
	  s->cancelled = SANE_TRUE;
	}
    }
}

/*--------------------------------------------------------------------------*/
SANE_Status
sane_start (SANE_Handle handle)
{
  SM3840_Scan *s = handle;
  SANE_Status status;

  /* First make sure we have a current parameter set.  Some of the
   * parameters will be overwritten below, but that's OK.  */
  DBG (2, "sane_start\n");
  status = sane_get_parameters (s, 0);
  if (status != SANE_STATUS_GOOD)
    return status;
  DBG (1, "Got params again...\n");

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

  s->line_buffer = malloc (s->sm3840_params.linelen);
  s->remaining = 0;
  s->offset = 0;
  s->linesleft = s->sm3840_params.scanlines;
  s->linesread = 0;

  s->save_i = 0;
  s->save_scan_line = NULL;
  s->save_dpi1200_remap = NULL;
  s->save_color_remap = NULL;

  setup_scan ((usb_dev_handle *) s->udev, &(s->sm3840_params));

  return (SANE_STATUS_GOOD);
}

static double
sm3840_unit_convert (SANE_Int val)
{
  double d;
  d = SANE_UNFIX (val);
  d /= MM_PER_INCH;
  return d;
}

/*--------------------------------------------------------------------------*/
SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  SM3840_Scan *s = handle;

  DBG (2, "sane_get_parameters\n");
  if (!s->scanning)
    {
      memset (&s->sane_params, 0, sizeof (s->sane_params));
      /* Copy from options to sm3840_params */
      s->sm3840_params.gray =
	(!strcasecmp (s->value[OPT_MODE].s, SANE_I18N ("Gray"))) ? 1 : 0;
      s->sm3840_params.dpi = s->value[OPT_RESOLUTION].w;
      s->sm3840_params.bpp = s->value[OPT_BIT_DEPTH].w;
      s->sm3840_params.gain = SANE_UNFIX (s->value[OPT_CONTRAST].w);
      s->sm3840_params.offset = s->value[OPT_BRIGHTNESS].w;
      s->sm3840_params.lamp = s->value[OPT_LAMP_TIMEOUT].w;

      s->sm3840_params.top = sm3840_unit_convert (s->value[OPT_TL_Y].w);
      s->sm3840_params.left = sm3840_unit_convert (s->value[OPT_TL_X].w);
      s->sm3840_params.width =
	sm3840_unit_convert (s->value[OPT_BR_X].w) - s->sm3840_params.left;
      s->sm3840_params.height =
	sm3840_unit_convert (s->value[OPT_BR_Y].w) - s->sm3840_params.top;

      /* Legalize and calculate pixel sizes */
      prepare_params (&(s->sm3840_params));

      /* Copy into sane_params */
      s->sane_params.pixels_per_line = s->sm3840_params.scanpix;
      s->sane_params.lines = s->sm3840_params.scanlines;
      s->sane_params.format =
	s->sm3840_params.gray ? SANE_FRAME_GRAY : SANE_FRAME_RGB;
      s->sane_params.bytes_per_line = s->sm3840_params.linelen;
      s->sane_params.depth = s->sm3840_params.bpp;
      s->sane_params.last_frame = SANE_TRUE;
    } /*!scanning */

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

  return (SANE_STATUS_GOOD);
}

/*--------------------------------------------------------------------------*/
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  SM3840_Scan *s = handle;
  SANE_Status status = 0;
  SANE_Word cap;
  DBG (2, "sane_control_option\n");
  if (info)
    *info = 0;
  if (s->scanning)
    return SANE_STATUS_DEVICE_BUSY;
  if (option >= NUM_OPTIONS)
    return SANE_STATUS_INVAL;
  cap = s->options_list[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    return SANE_STATUS_INVAL;
  if (action == SANE_ACTION_GET_VALUE)
    {
      DBG (1, "sane_control_option %d, get value\n", option);
      switch (option)
	{
	  /* word options: */
	case OPT_RESOLUTION:
	case OPT_BIT_DEPTH:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_NUM_OPTS:
	case OPT_CONTRAST:
	case OPT_BRIGHTNESS:
	case OPT_LAMP_TIMEOUT:
	  *(SANE_Word *) val = s->value[option].w;
	  return (SANE_STATUS_GOOD);
	  /* string options: */
	case OPT_MODE:
	  strcpy (val, s->value[option].s);
	  return (SANE_STATUS_GOOD);
	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      DBG (1, "sane_control_option %d, set value\n", option);
      if (!SANE_OPTION_IS_SETTABLE (cap))
	return (SANE_STATUS_INVAL);
      if (status != SANE_STATUS_GOOD)
	return (status);
      status = sanei_constrain_value (s->options_list + option, val, info);
      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_RESOLUTION:
	case OPT_BIT_DEPTH:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_TL_X:
	case OPT_TL_Y:
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  /* fall through */
	case OPT_NUM_OPTS:
	case OPT_CONTRAST:
	case OPT_BRIGHTNESS:
	case OPT_LAMP_TIMEOUT:
	  s->value[option].w = *(SANE_Word *) val;
	  DBG (1, "set brightness to\n");
	  return (SANE_STATUS_GOOD);
	case OPT_MODE:
	  if (s->value[option].s)
	    free (s->value[option].s);
	  s->value[option].s = strdup (val);
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	  return (SANE_STATUS_GOOD);
	}
    }
  return (SANE_STATUS_INVAL);
}

/*--------------------------------------------------------------------------*/
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  SM3840_Scan *s = handle;
  DBG (2, "sane_get_option_descriptor\n");
  if ((unsigned) option >= NUM_OPTIONS)
    return (0);
  return (&s->options_list[option]);
}

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

void
sane_close (SANE_Handle handle)
{
  SM3840_Scan *prev, *s;

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

  if (s->scanning)
    {
      sane_cancel (handle);
    }

  sanei_usb_close (s->udev);

  if (s->line_buffer)
    free (s->line_buffer);
  if (s->save_scan_line)
    free (s->save_scan_line);
  if (s->save_dpi1200_remap)
    free (s->save_dpi1200_remap);
  if (s->save_color_remap)
    free (s->save_color_remap);

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

/*--------------------------------------------------------------------------*/
void
sane_exit (void)
{
  SM3840_Device *next;
  DBG (2, "sane_exit\n");
  while (first_dev != NULL)
    {
      next = first_dev->next;
      free (first_dev);
      first_dev = next;
    }
  if (devlist)
    free (devlist);
}



/*--------------------------------------------------------------------------*/
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  DBG_INIT ();
  if (version_code)
    *version_code = SANE_VERSION_CODE (V_MAJOR, V_MINOR, 0);
  if (authorize)
    DBG(2, "Unused authorize\n");

  sanei_usb_init ();

  return (SANE_STATUS_GOOD);
}

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


static SANE_Status
add_sm_device (SANE_String_Const devname, SANE_String_Const modname)
{
  SM3840_Device *dev;

  dev = calloc (sizeof (*dev), 1);
  if (!dev)
    return (SANE_STATUS_NO_MEM);

  memset (dev, 0, sizeof (*dev));
  dev->sane.name = strdup (devname);
  dev->sane.model = modname;
  dev->sane.vendor = "Microtek";
  dev->sane.type = "flatbed scanner";
  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;

  return (SANE_STATUS_GOOD);
}

static SANE_Status
add_sm3840_device (SANE_String_Const devname)
{
  return add_sm_device (devname, "ScanMaker 3840");
}

static SANE_Status
add_sm4800_device (SANE_String_Const devname)
{
  return add_sm_device (devname, "ScanMaker 4800");
}


/*--------------------------------------------------------------------------*/
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  static const SANE_Device **devlist = 0;
  SM3840_Device *dev;
  int i;

  DBG (3, "sane_get_devices (local_only = %d)\n", local_only);

  while (first_dev)
    {
      dev = first_dev->next;
      free (first_dev);
      first_dev = dev;
    }
  first_dev = NULL;
  num_devices = 0;

  /* If we get enough scanners should use an array, but for now */
  /* do it one-by-one... */
  sanei_usb_find_devices (0x05da, 0x30d4, add_sm3840_device);
  sanei_usb_find_devices (0x05da, 0x30cf, add_sm4800_device);

  if (devlist)
    free (devlist);
  devlist = calloc ((num_devices + 1) * sizeof (devlist[0]), 1);
  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;
  if (device_list)
    *device_list = devlist;
  return (SANE_STATUS_GOOD);
}


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

static size_t
max_string_size (const SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;
  for (i = 0; strings[i]; ++i)
    {
      size = strlen (strings[i]) + 1;
      if (size > max_size)
	max_size = size;
    }

  return (max_size);
}

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

static void
initialize_options_list (SM3840_Scan * s)
{

  SANE_Int option;
  DBG (2, "initialize_options_list\n");
  for (option = 0; option < NUM_OPTIONS; ++option)
    {
      s->options_list[option].size = sizeof (SANE_Word);
      s->options_list[option].cap =
	SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }

  s->options_list[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
  s->options_list[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  s->options_list[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  s->options_list[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  s->options_list[OPT_NUM_OPTS].unit = SANE_UNIT_NONE;
  s->options_list[OPT_NUM_OPTS].size = sizeof (SANE_Word);
  s->options_list[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  s->options_list[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE;
  s->value[OPT_NUM_OPTS].w = NUM_OPTIONS;

  s->options_list[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  s->options_list[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  s->options_list[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  s->options_list[OPT_MODE].type = SANE_TYPE_STRING;
  s->options_list[OPT_MODE].size = max_string_size (mode_list);
  s->options_list[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->options_list[OPT_MODE].constraint.string_list = mode_list;
  s->value[OPT_MODE].s = strdup (mode_list[1]);

  s->options_list[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  s->options_list[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  s->options_list[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  s->options_list[OPT_RESOLUTION].type = SANE_TYPE_INT;
  s->options_list[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  s->options_list[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->options_list[OPT_RESOLUTION].constraint.word_list = resolution_list;
  s->value[OPT_RESOLUTION].w = 300;

  s->options_list[OPT_BIT_DEPTH].name = SANE_NAME_BIT_DEPTH;
  s->options_list[OPT_BIT_DEPTH].title = SANE_TITLE_BIT_DEPTH;
  s->options_list[OPT_BIT_DEPTH].desc = SANE_DESC_BIT_DEPTH;
  s->options_list[OPT_BIT_DEPTH].type = SANE_TYPE_INT;
  s->options_list[OPT_BIT_DEPTH].unit = SANE_UNIT_NONE;
  s->options_list[OPT_BIT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
  s->options_list[OPT_BIT_DEPTH].constraint.word_list = bpp_list;
  s->value[OPT_BIT_DEPTH].w = 8;

  s->options_list[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  s->options_list[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  s->options_list[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  s->options_list[OPT_TL_X].type = SANE_TYPE_FIXED;
  s->options_list[OPT_TL_X].unit = SANE_UNIT_MM;
  s->options_list[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_TL_X].constraint.range = &x_range;
  s->value[OPT_TL_X].w = s->options_list[OPT_TL_X].constraint.range->min;
  s->options_list[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  s->options_list[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  s->options_list[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  s->options_list[OPT_TL_Y].type = SANE_TYPE_FIXED;
  s->options_list[OPT_TL_Y].unit = SANE_UNIT_MM;
  s->options_list[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_TL_Y].constraint.range = &y_range;
  s->value[OPT_TL_Y].w = s->options_list[OPT_TL_Y].constraint.range->min;
  s->options_list[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
  s->options_list[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  s->options_list[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
  s->options_list[OPT_BR_X].type = SANE_TYPE_FIXED;
  s->options_list[OPT_BR_X].unit = SANE_UNIT_MM;
  s->options_list[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_BR_X].constraint.range = &x_range;
  s->value[OPT_BR_X].w = s->options_list[OPT_BR_X].constraint.range->max;
  s->options_list[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
  s->options_list[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  s->options_list[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
  s->options_list[OPT_BR_Y].type = SANE_TYPE_FIXED;
  s->options_list[OPT_BR_Y].unit = SANE_UNIT_MM;
  s->options_list[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_BR_Y].constraint.range = &y_range;
  s->value[OPT_BR_Y].w = s->options_list[OPT_BR_Y].constraint.range->max;

  s->options_list[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  s->options_list[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  s->options_list[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  s->options_list[OPT_CONTRAST].type = SANE_TYPE_FIXED;
  s->options_list[OPT_CONTRAST].unit = SANE_UNIT_NONE;
  s->options_list[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_CONTRAST].constraint.range = &contrast_range;
  s->value[OPT_CONTRAST].w = SANE_FIX (3.5);

  s->options_list[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  s->options_list[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  s->options_list[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  s->options_list[OPT_BRIGHTNESS].type = SANE_TYPE_INT;
  s->options_list[OPT_BRIGHTNESS].unit = SANE_UNIT_NONE;
  s->options_list[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_BRIGHTNESS].constraint.range = &brightness_range;
  s->value[OPT_BRIGHTNESS].w = 1800;

  s->options_list[OPT_LAMP_TIMEOUT].name = "lamp-timeout";
  s->options_list[OPT_LAMP_TIMEOUT].title = SANE_I18N ("Lamp timeout");
  s->options_list[OPT_LAMP_TIMEOUT].desc =
    SANE_I18N ("Minutes until lamp is turned off after scan");
  s->options_list[OPT_LAMP_TIMEOUT].type = SANE_TYPE_INT;
  s->options_list[OPT_LAMP_TIMEOUT].unit = SANE_UNIT_NONE;
  s->options_list[OPT_LAMP_TIMEOUT].constraint_type = SANE_CONSTRAINT_RANGE;
  s->options_list[OPT_LAMP_TIMEOUT].constraint.range = &lamp_range;
  s->value[OPT_LAMP_TIMEOUT].w = 15;

}

/*--------------------------------------------------------------------------*/
SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  SANE_Status status;
  SM3840_Device *dev;
  SM3840_Scan *s;
  DBG (2, "sane_open\n");

  /* Make sure we have first_dev */
  sane_get_devices (NULL, 0);
  if (devicename[0])
    {
      for (dev = first_dev; dev; dev = dev->next)
	if (strcmp (dev->sane.name, devicename) == 0)
	  break;
    }
  else
    {
      /* empty devicename -> use first device */
      dev = first_dev;
    }
  DBG (2, "using device: %s %p\n", dev->sane.name, (unsigned char *)dev);
  if (!dev)
    return SANE_STATUS_INVAL;
  s = calloc (sizeof (*s), 1);
  if (!s)
    return SANE_STATUS_NO_MEM;

  status = sanei_usb_open (dev->sane.name, &(s->udev));

  initialize_options_list (s);
  s->scanning = 0;
  /* insert newly opened handle into list of open handles: */
  s->next = first_handle;
  first_handle = s;
  *handle = s;
  return (SANE_STATUS_GOOD);
}

/*--------------------------------------------------------------------------*/
SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  SM3840_Scan *s = handle;
  DBG (2, "sane_set_io_mode( %p, %d )\n", handle, non_blocking);
  if (s->scanning)
    {
      if (non_blocking == SANE_FALSE)
	return SANE_STATUS_GOOD;
      else
	return (SANE_STATUS_UNSUPPORTED);
    }
  else
    return SANE_STATUS_INVAL;
}

/*---------------------------------------------------------------------------*/
SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG (2, "sane_get_select_fd( %p, %p )\n", (void *) handle, (void *) fd);
  return SANE_STATUS_UNSUPPORTED;
}
