/* sane - Scanner Access Now Easy.
   Copyright (C) 1996, 1997 David Mosberger-Tang and Andreas Czechanowski,
   1998 Andreas Bolsch for extension to ScanExpress models version 0.6,
   2000-2005 Henning Meier-Geinitz,
   2003 James Perry (600 EP).

   This file is part of the SANE package.

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

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

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.

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

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

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

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

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

   This file implements a SANE backend for Mustek and some Trust flatbed
   scanners with SCSI, parallel port (600 EP)  or proprietary interface.  */


/**************************************************************************/
/* Mustek backend version                                                 */
#define BUILD 138
/**************************************************************************/

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

#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 <sys/types.h>

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

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_ab306.h"
#include "../include/sane/sanei_thread.h"

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

#include "mustek.h"
#include "mustek_scsi_pp.h"

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

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

/* Maximum # of inches to scan in one swoop.  0 means "unlimited."
   This is here to be nice on the SCSI bus---Mustek scanners don't
   disconnect while scanning is in progress, which confuses some
   drivers that expect no reasonable SCSI request would take more than
   10 seconds.  That's not really true for Mustek scanners operating
   in certain modes, hence this limit. Usually you don't need to set it. */
static double strip_height;

/* Should we wait for the scan slider to return after each scan? */
static SANE_Bool force_wait;

/* Should we disable double buffering when reading data from the scanner? */
static SANE_Bool disable_double_buffering;

static SANE_Int num_devices;
static Mustek_Device *first_dev;
static Mustek_Scanner *first_handle;
static const SANE_Device **devlist = 0;

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

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

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

static SANE_Int lamp_off_time = 60;

/* Used for line-distance correction: */
static const SANE_Int color_seq[] = {
  1, 2, 0			/* green, blue, red */
};

/* Which modes are supported? */
static SANE_String_Const mode_list_paragon[] = {
  SANE_VALUE_SCAN_MODE_LINEART,
  SANE_VALUE_SCAN_MODE_HALFTONE,
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,
  0
};
static SANE_String_Const mode_list_se[] = {
  SANE_VALUE_SCAN_MODE_LINEART,
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,
  0
};

static SANE_String_Const bit_depth_list_pro[] = {
  "8", "12",
  0
};

/* Some scanners support setting speed manually */
static SANE_String_Const speed_list[] = {
  SANE_I18N ("Slowest"), SANE_I18N ("Slower"), SANE_I18N ("Normal"),
  SANE_I18N ("Faster"), SANE_I18N ("Fastest"),
  0
};

/* Which scan-sources are supported? */
static const SANE_String_Const source_list[] = {
  SANE_I18N ("Flatbed"),
  0
};
static SANE_String_Const adf_source_list[] = {
  SANE_I18N ("Flatbed"), SANE_I18N ("Automatic Document Feeder"),
  0
};
static SANE_String_Const ta_source_list[] = {
  SANE_I18N ("Flatbed"), SANE_I18N ("Transparency Adapter"),
  0
};

/* Range used for gamma and halftone pattern */
static const SANE_Range u8_range = {
  0,				/* minimum */
  255,				/* maximum */
  0				/* quantization */
};

/* Which kind of halftone patterns are available? */
static SANE_String_Const halftone_list[] = {
  SANE_I18N ("8x8 coarse"), SANE_I18N ("8x8 normal"), SANE_I18N ("8x8 fine"),
  SANE_I18N ("8x8 very fine"), SANE_I18N ("6x6 normal"),
  SANE_I18N ("5x5 coarse"), SANE_I18N ("5x5 fine"), SANE_I18N ("4x4 coarse"),
  SANE_I18N ("4x4 normal"), SANE_I18N ("4x4 fine"), SANE_I18N ("3x3 normal"),
  SANE_I18N ("2x2 normal"), SANE_I18N ("8x8 custom"),
  SANE_I18N ("6x6 custom"),
  SANE_I18N ("5x5 custom"), SANE_I18N ("4x4 custom"),
  SANE_I18N ("3x3 custom"),
  SANE_I18N ("2x2 custom"),
  0
};

/* Range used for brightness and contrast */
static const SANE_Range percentage_range = {
  SANE_FIX(-100),	/* minimum */
  SANE_FIX(100),	/* maximum */
  SANE_FIX(1)           /* quantization */
};

/* SCSI command buffers used by the backend */
static const SANE_Byte scsi_inquiry[] = {
  MUSTEK_SCSI_INQUIRY, 0x00, 0x00, 0x00, INQ_LEN, 0x00
};
static const SANE_Byte scsi_test_unit_ready[] = {
  MUSTEK_SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* Remove #ifdef and this comment when this SCSI command is used for
   something.  Keeping this definition around so we don't loose info
   about the protocol.
 */
#ifdef ENABLE_MUSTEK_SCSI_AREA_AND_WINDOWS
static const SANE_Byte scsi_area_and_windows[] = {
  MUSTEK_SCSI_AREA_AND_WINDOWS, 0x00, 0x00, 0x00, 0x09, 0x00
};
#endif
static const SANE_Byte scsi_request_sense[] = {
  MUSTEK_SCSI_REQUEST_SENSE, 0x00, 0x00, 0x00, 0x04, 0x00
};
static const SANE_Byte scsi_start_stop[] = {
  MUSTEK_SCSI_START_STOP, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const SANE_Byte scsi_ccd_distance[] = {
  MUSTEK_SCSI_CCD_DISTANCE, 0x00, 0x00, 0x00, 0x05, 0x00
};
static const SANE_Byte scsi_get_image_status[] = {
  MUSTEK_SCSI_GET_IMAGE_STATUS, 0x00, 0x00, 0x00, 0x06, 0x00
};
static const SANE_Byte scsi_set_window[] = {
  MUSTEK_SCSI_SET_WINDOW, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00
};
static const SANE_Byte scsi_get_window[] = {
  MUSTEK_SCSI_GET_WINDOW, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00
};
static const SANE_Byte scsi_read_data[] = {
  MUSTEK_SCSI_READ_DATA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const SANE_Byte scsi_send_data[] = {
  MUSTEK_SCSI_SEND_DATA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* Remove #ifdef and this comment when this SCSI command is used for
   something.  Keeping this definition around so we don't loose info
   about the protocol.
 */
#ifdef ENABLE_MUSTEK_SCSI_LOOKUP_TABLE
static const SANE_Byte scsi_lookup_table[] = {
  MUSTEK_SCSI_LOOKUP_TABLE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00
};
#endif

/* prototypes */
static SANE_Status area_and_windows (Mustek_Scanner * s);
static SANE_Status inquiry (Mustek_Scanner * s);

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

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

/* Used for Pro series. First value seems to be always 85, second one varies.
   First bit of second value clear == device ready (?) */
static SANE_Status
scsi_sense_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;
  size_t len;
  SANE_Byte sense_buffer[4];
  SANE_Byte bytetxt[300], dbgtxt[300], *pp;

  gettimeofday (&start, 0);

  while (1)
    {
      len = sizeof (sense_buffer);

      DBG (5, "scsi_sense_wait_ready: command size = %ld, sense size = %ld\n",
	   (long int) sizeof (scsi_request_sense), (long int) len);
      status = sanei_scsi_cmd (s->fd, scsi_request_sense,
			       sizeof (scsi_request_sense), sense_buffer,
			       &len);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "scsi_sense_wait_ready: failed: %s\n",
	       sane_strstatus (status));
	  return status;
	}

      dbgtxt[0] = '\0';
      for (pp = sense_buffer; pp < (sense_buffer + 4); pp++)
	{
	  sprintf ((SANE_String) bytetxt, " %02x", *pp);
	  strcat ((SANE_String) dbgtxt, (SANE_String) bytetxt);
	}
      DBG (5, "scsi_sense_wait_ready: sensebuffer: %s\n", dbgtxt);

      if (!(sense_buffer[1] & 0x01))
	{
	  DBG (4, "scsi_sense_wait_ready: ok\n");
	  return SANE_STATUS_GOOD;
	}
      else
	{
	  gettimeofday (&now, 0);
	  if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	    {
	      DBG (1, "scsi_sense_wait_ready: timed out after %lu seconds\n",
		   (u_long) (now.tv_sec - start.tv_sec));
	      return SANE_STATUS_INVAL;
	    }
	  usleep (100000);	/* retry after 100ms */
	}
    }
  return SANE_STATUS_INVAL;
}

/* Used for 3pass series */
static SANE_Status
scsi_area_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;

  gettimeofday (&start, 0);

  DBG (5, "scsi_area_wait_ready\n");
  while (1)
    {
      status = area_and_windows (s);
      switch (status)
	{
	default:
	  /* Ignore errors while waiting for scanner to become ready.
	     Some SCSI drivers return EIO while the scanner is
	     returning to the home position.  */
	  DBG (3, "scsi_area_wait_ready: failed (%s)\n",
	       sane_strstatus (status));
	  /* fall through */
	case SANE_STATUS_DEVICE_BUSY:
	  gettimeofday (&now, 0);
	  if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	    {
	      DBG (1, "scsi_area_wait_ready: timed out after %lu seconds\n",
		   (u_long) (now.tv_sec - start.tv_sec));
	      return SANE_STATUS_INVAL;
	    }
	  usleep (100000);	/* retry after 100ms */
	  break;

	case SANE_STATUS_GOOD:
	  return status;
	}
    }
  return SANE_STATUS_INVAL;
}

static SANE_Status
scsi_unit_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;

  gettimeofday (&start, 0);

  while (1)
    {
      DBG (5, "scsi_unit_wait_ready: sending TEST_UNIT_READY\n");
      status = sanei_scsi_cmd (s->fd, scsi_test_unit_ready,
			       sizeof (scsi_test_unit_ready), 0, 0);
      DBG (5, "scsi_unit_wait_ready: TEST_UNIT_READY finished\n");
      switch (status)
	{
	default:
	  /* Ignore errors while waiting for scanner to become ready.
	     Some SCSI drivers return EIO while the scanner is
	     returning to the home position.  */
	  DBG (3, "scsi_unit_wait_ready: test unit ready failed (%s)\n",
	       sane_strstatus (status));
	  /* fall through */
	case SANE_STATUS_DEVICE_BUSY:
	  gettimeofday (&now, 0);
	  if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	    {
	      DBG (1, "scsi_unit_wait_ready: timed out after %lu seconds\n",
		   (u_long) (now.tv_sec - start.tv_sec));
	      return SANE_STATUS_INVAL;
	    }
	  usleep (100000);	/* retry after 100ms */
	  break;

	case SANE_STATUS_GOOD:
	  return status;
	}
    }
  return SANE_STATUS_INVAL;
}

static SANE_Status
scsi_inquiry_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;

  gettimeofday (&start, 0);

  while (1)
    {
      DBG (5, "scsi_inquiry_wait_ready: sending INQUIRY\n");
      status = inquiry (s);
      DBG (5, "scsi_inquiry_wait_ready: INQUIRY finished\n");
      switch (status)
	{
	default:
	  /* Ignore errors while waiting for scanner to become ready.
	     Some SCSI drivers return EIO while the scanner is
	     returning to the home position.  */
	  DBG (3, "scsi_unit_wait_ready: inquiry failed (%s)\n",
	       sane_strstatus (status));
	  /* fall through */
	case SANE_STATUS_DEVICE_BUSY:
	  gettimeofday (&now, 0);
	  if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	    {
	      DBG (1, "scsi_unit_wait_ready: timed out after %lu seconds\n",
		   (u_long) (now.tv_sec - start.tv_sec));
	      return SANE_STATUS_INVAL;
	    }
	  usleep (500000);	/* retry after 500ms */
	  break;

	case SANE_STATUS_GOOD:
	  return status;
	}
    }
  return SANE_STATUS_INVAL;
}

static SANE_Status
n_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;

  gettimeofday (&start, 0);

  DBG (5, "n_wait_ready\n");
  while (1)
    {
      status = sanei_ab306_test_ready (s->fd);
      if (status == SANE_STATUS_GOOD)
	return SANE_STATUS_GOOD;

      gettimeofday (&now, 0);
      if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	{
	  DBG (1, "n_wait_ready: timed out after %lu seconds\n",
	       (u_long) (now.tv_sec - start.tv_sec));
	  return SANE_STATUS_INVAL;
	}
      usleep (100000);		/* retry after 100ms */
    }
}

static SANE_Status
scsi_pp_wait_ready (Mustek_Scanner * s)
{
  struct timeval now, start;
  SANE_Status status;

  gettimeofday (&start, 0);

  DBG (5, "scsi_pp_wait_ready\n");
  while (1)
    {
      status = mustek_scsi_pp_test_ready (s->fd);
      if (status == SANE_STATUS_GOOD)
	return SANE_STATUS_GOOD;

      gettimeofday (&now, 0);
      if (now.tv_sec - start.tv_sec >= MAX_WAITING_TIME)
	{
	  DBG (1, "scsi_pp_wait_ready: timed out after %lu seconds\n",
	       (u_long) (now.tv_sec - start.tv_sec));
	  return SANE_STATUS_INVAL;
	}
      usleep (100000);		/* retry after 100ms */
    }
}

static SANE_Status
dev_wait_ready (Mustek_Scanner * s)
{
  if (s->hw->flags & MUSTEK_FLAG_N)
    return n_wait_ready (s);
  else if (s->hw->flags & MUSTEK_FLAG_SCSI_PP)
    return scsi_pp_wait_ready (s);
  else if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
    {
      SANE_Status status;

      /* some 3-pass scanners seem to need the inquiry wait, too */
      status = scsi_area_wait_ready (s);
      if (status != SANE_STATUS_GOOD)
	return status;
      return scsi_inquiry_wait_ready (s);
    }
  else if ((s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	   || (s->hw->flags & MUSTEK_FLAG_PARAGON_2))
    return scsi_inquiry_wait_ready (s);
  else if (s->hw->flags & MUSTEK_FLAG_PRO)
    return scsi_sense_wait_ready (s);
  else
    return scsi_unit_wait_ready (s);
}

static SANE_Status
dev_open (SANE_String_Const devname, Mustek_Scanner * s,
	  SANEI_SCSI_Sense_Handler handler)
{
  SANE_Status status;

  DBG (5, "dev_open %s\n", devname);

#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
  s->hw->buffer_size = s->hw->max_buffer_size;
  status = sanei_scsi_open_extended (devname, &s->fd, handler, 0,
				     &s->hw->buffer_size);
#else
  s->hw->buffer_size = MIN (sanei_scsi_max_request_size,
			    s->hw->max_buffer_size);
  status = sanei_scsi_open (devname, &s->fd, handler, 0);
#endif

  if (status == SANE_STATUS_GOOD)
    {
      DBG (3, "dev_open: %s is a SCSI device\n", devname);
      DBG (4, "dev_open: wanted %d kbytes, got %d kbytes buffer\n",
	   s->hw->max_buffer_size / 1024, s->hw->buffer_size / 1024);
      if (s->hw->buffer_size < 4096)
	{
	  DBG (1, "dev_open: sanei_scsi_open buffer too small\n");
	  sanei_scsi_close (s->fd);
	  return SANE_STATUS_NO_MEM;
	}
    }
  else
    {
      DBG (3, "dev_open: %s: can't open %s as a SCSI device\n",
	   sane_strstatus (status), devname);

      status = sanei_ab306_open (devname, &s->fd);
      if (status == SANE_STATUS_GOOD)
	{
	  s->hw->flags |= MUSTEK_FLAG_N;
	  DBG (3, "dev_open: %s is an AB306N device\n", devname);
	}
      else
	{
	  DBG (3, "dev_open: %s: can't open %s as an AB306N device\n",
	       sane_strstatus (status), devname);

	  status = mustek_scsi_pp_open (devname, &s->fd);
	  if (status == SANE_STATUS_GOOD)
	    {
	      s->hw->flags |= MUSTEK_FLAG_SCSI_PP;
	      DBG (3, "dev_open: %s is a SCSI-over-parallel device\n",
		   devname);
	    }
	  else
	    {
	      DBG (3,
		   "dev_open: %s: can't open %s as a SCSI-over-parallel device\n",
		   sane_strstatus (status), devname);
	      DBG (1, "dev_open: can't open %s\n", devname);
	      return SANE_STATUS_INVAL;
	    }
	}
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
dev_cmd (Mustek_Scanner * s, const void *src, size_t src_size,
	 void *dst, size_t * dst_size)
{
  SANE_Status status;
  SANE_Byte cmd_byte_list[50];
  SANE_Byte cmd_byte[5];
  const SANE_Byte *pp;

  DBG (5, "dev_cmd: fd=%d, src=%p, src_size=%ld, dst=%p, dst_size=%ld\n",
       s->fd, src, (long int) src_size, dst,
       (long int) (dst_size ? *dst_size : 0));

  if (src && (debug_level >= 5))	/* output data sent to SCSI device */
    {
      cmd_byte_list[0] = '\0';
      for (pp = (const SANE_Byte *) src;
	   pp < (((const SANE_Byte *) src) + src_size); pp++)
	{
	  sprintf ((SANE_String) cmd_byte, " %02x", *pp);
	  strcat ((SANE_String) cmd_byte_list, (SANE_String) cmd_byte);
	  if (((pp - (const SANE_Byte *) src) % 0x10 == 0x0f)
	      || (pp >= (((const SANE_Byte *) src) + src_size - 1)))
	    {
	      DBG (5, "dev_cmd: sending: %s\n", cmd_byte_list);
	      cmd_byte_list[0] = '\0';
	    }
	}
    }
  if (s->hw->flags & MUSTEK_FLAG_N)
    status = sanei_ab306_cmd (s->fd, src, src_size, dst, dst_size);
  else if (s->hw->flags & MUSTEK_FLAG_SCSI_PP)
    status = mustek_scsi_pp_cmd (s->fd, src, src_size, dst, dst_size);
  else
    status = sanei_scsi_cmd (s->fd, src, src_size, dst, dst_size);

  if (dst && dst_size && (debug_level >= 5))
    /* output data received from SCSI device */
    {
      cmd_byte_list[0] = '\0';
      for (pp = (const SANE_Byte *) dst;
	   pp < (((const SANE_Byte *) dst) + *dst_size); pp++)
	{
	  sprintf ((SANE_String) cmd_byte, " %02x", *pp);
	  strcat ((SANE_String) cmd_byte_list, (SANE_String) cmd_byte);
	  if (((pp - (const SANE_Byte *) dst) % 0x10 == 0x0f)
	      || (pp >= (((const SANE_Byte *) dst) + *dst_size - 1)))
	    {
	      DBG (5, "dev_cmd: receiving: %s\n", cmd_byte_list);
	      cmd_byte_list[0] = '\0';
	    }
	}
    }

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

static SANE_Status
dev_req_wait (void *id)
{
  if (!id)
    return SANE_STATUS_GOOD;
  else
    return sanei_scsi_req_wait (id);
}

static SANE_Status
dev_block_read_start (Mustek_Scanner * s, SANE_Int lines)
{
  DBG (4, "dev_block_read_start: entering block for %d lines\n", lines);
  if (s->hw->flags & MUSTEK_FLAG_N)
    {
      SANE_Byte readlines[6];

      memset (readlines, 0, sizeof (readlines));
      readlines[0] = MUSTEK_SCSI_READ_SCANNED_DATA;
      readlines[2] = (lines >> 16) & 0xff;
      readlines[3] = (lines >> 8) & 0xff;
      readlines[4] = (lines >> 0) & 0xff;
      return sanei_ab306_cmd (s->fd, readlines, sizeof (readlines), 0, 0);
    }
  else if (s->hw->flags & MUSTEK_FLAG_SCSI_PP)
    {
      SANE_Byte readlines[6];

      memset (readlines, 0, sizeof (readlines));
      readlines[0] = MUSTEK_SCSI_READ_SCANNED_DATA;
      readlines[2] = (lines >> 16) & 0xff;
      readlines[3] = (lines >> 8) & 0xff;
      readlines[4] = (lines >> 0) & 0xff;
      return mustek_scsi_pp_cmd (s->fd, readlines, sizeof (readlines), 0, 0);
    }
  else if (s->hw->flags & MUSTEK_FLAG_PARAGON_2)
    {
      SANE_Byte buffer[6];
      size_t len;
      SANE_Int color;
      SANE_Status status;

      /* reset line-distance values */
      if (s->mode & MUSTEK_MODE_COLOR)
	{
	  for (color = 0; color < 3; ++color)
	    {
	      s->ld.index[color] = -s->ld.dist[color];
	    }
	  s->ld.lmod3 = -1;
	  s->ld.ld_line = 0;
	}

      /* Get image status (necessary to start new block) */
      len = sizeof (buffer);
      status = dev_cmd (s, scsi_get_image_status,
			sizeof (scsi_get_image_status), buffer, &len);
      if (status != SANE_STATUS_GOOD)
	return status;

      /* tell scanner how many lines to scan in one block */
      memset (buffer, 0, sizeof (buffer));
      buffer[0] = MUSTEK_SCSI_READ_SCANNED_DATA;
      buffer[2] = (lines >> 16) & 0xff;
      buffer[3] = (lines >> 8) & 0xff;
      buffer[4] = (lines >> 0) & 0xff;
      buffer[5] = 0x04;
      return sanei_scsi_cmd (s->fd, buffer, sizeof (buffer), 0, 0);
    }
  else
    return SANE_STATUS_GOOD;
}

static SANE_Status
dev_read_req_enter (Mustek_Scanner * s, SANE_Byte * buf, SANE_Int lines,
		    SANE_Int bpl, size_t * lenp, void **idp, SANE_Int bank,
		    SANE_Byte * command)
{
  *lenp = lines * bpl;

  if (s->hw->flags & MUSTEK_FLAG_N)
    {
      SANE_Int planes;

      *idp = 0;
      planes = (s->mode & MUSTEK_MODE_COLOR) ? 3 : 1;

      return sanei_ab306_rdata (s->fd, planes, buf, lines, bpl);
    }
  else if (s->hw->flags & MUSTEK_FLAG_SCSI_PP)
    {
      SANE_Int planes;

      *idp = 0;
      planes = (s->mode & MUSTEK_MODE_COLOR) ? 3 : 1;

      return mustek_scsi_pp_rdata (s->fd, planes, buf, lines, bpl);
    }
  else
    {
      if (s->hw->flags & MUSTEK_FLAG_SE)
	{
	  if (s->mode & MUSTEK_MODE_COLOR)
	    lines *= 3;

	  memset (command, 0, 10);
	  command[0] = MUSTEK_SCSI_READ_DATA;
	  command[6] = bank;	/* buffer bank not used ??? */
	  command[7] = (lines >> 8) & 0xff;
	  command[8] = (lines >> 0) & 0xff;
	  return sanei_scsi_req_enter (s->fd, command, 10, buf, lenp, idp);
	}
      else if (s->hw->flags & MUSTEK_FLAG_PRO)
	{
	  memset (command, 0, 6);
	  command[0] = MUSTEK_SCSI_READ_SCANNED_DATA;
	  command[2] = ((lines * bpl) >> 16) & 0xff;
	  command[3] = ((lines * bpl) >> 8) & 0xff;
	  command[4] = ((lines * bpl) >> 0) & 0xff;

	  return sanei_scsi_req_enter (s->fd, command, 6, buf, lenp, idp);
	}
      else			/* Paragon series */
	{
	  memset (command, 0, 6);
	  command[0] = MUSTEK_SCSI_READ_SCANNED_DATA;
	  command[2] = (lines >> 16) & 0xff;
	  command[3] = (lines >> 8) & 0xff;
	  command[4] = (lines >> 0) & 0xff;
	  return sanei_scsi_req_enter (s->fd, command, 6, buf, lenp, idp);
	}
    }
}

static void
dev_close (Mustek_Scanner * s)
{
  if (s->hw->flags & MUSTEK_FLAG_N)
    sanei_ab306_close (s->fd);
  else if (s->hw->flags & MUSTEK_FLAG_SCSI_PP)
    mustek_scsi_pp_close (s->fd);
  else
    sanei_scsi_close (s->fd);
}

static SANE_Status
sense_handler (SANE_Int scsi_fd, SANE_Byte * result, void *arg)
{
  if (!result)
    {
      DBG (5, "sense_handler: no sense buffer\n");
      return SANE_STATUS_IO_ERROR;
    }
  if (!arg)
    DBG (5, "sense_handler: got sense code %02x for fd %d (arg = null)\n",
	 result[0], scsi_fd);
  else
    DBG (5, "sense_handler: got sense code %02x for fd %d (arg = %uc)\n",
	 result[0], scsi_fd, *(SANE_Byte *) arg);
  switch (result[0])
    {
    case 0x00:
      break;

    case 0x82:
      if (result[1] & 0x80)
	{
	  DBG (3, "sense_handler: ADF is jammed\n");
	  return SANE_STATUS_JAMMED;	/* ADF is jammed */
	}
      break;

    case 0x83:
      if (result[2] & 0x02)
	{
	  DBG (3, "sense_handler: ADF is out of documents\n");
	  return SANE_STATUS_NO_DOCS;	/* ADF out of documents */
	}
      break;

    case 0x84:
      if (result[1] & 0x10)
	{
	  DBG (3, "sense_handler: transparency adapter cover open\n");
	  return SANE_STATUS_COVER_OPEN;	/* open transparency adapter cover */
	}
      break;

    default:
      DBG (1, "sense_handler: got unknown sense code %02x for fd %d\n",
	   result[0], scsi_fd);
      return SANE_STATUS_IO_ERROR;
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
inquiry (Mustek_Scanner * s)
{
  SANE_Byte result[INQ_LEN];
  size_t size;
  SANE_Status status;

  DBG (5, "inquiry: sending INQUIRY\n");
  size = sizeof (result);

  memset (result, 0, size);

  status = dev_cmd (s, scsi_inquiry, sizeof (scsi_inquiry), result, &size);
  if (status != SANE_STATUS_GOOD)
    return status;

  /* checking ADF status */
  if (s->hw->flags & MUSTEK_FLAG_ADF)
    {
      if (result[63] & (1 << 3))
	{
	  s->hw->flags |= MUSTEK_FLAG_ADF_READY;
	  DBG (4, "inquiry: ADF ready\n");
	}
      else
	{
	  s->hw->flags &= ~MUSTEK_FLAG_ADF_READY;
	  DBG (4, "inquiry: ADF not ready (out of paper)\n");
	}
    }
  if (!result[0])
    return SANE_STATUS_DEVICE_BUSY;
  return SANE_STATUS_GOOD;
}

static SANE_Status
paragon_2_get_adf_status (Mustek_Scanner * s)
{
  SANE_Status status;
  size_t len;
  SANE_Byte sense_buffer[4];

  len = sizeof (sense_buffer);

  status = sanei_scsi_cmd (s->fd, scsi_request_sense,
			   sizeof (scsi_request_sense), sense_buffer, &len);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "paragon_2_get_adf_status: %s\n", sane_strstatus (status));
      return status;
    }
  DBG (5, "paragon_2_get_adf_status: sense_buffer: %x %x %x %x\n",
       sense_buffer[0], sense_buffer[1], sense_buffer[3], sense_buffer[3]);

  if (sense_buffer[0] == 0x00 && sense_buffer[1] == 0x00)
    return SANE_STATUS_GOOD;

  return SANE_STATUS_NO_DOCS;
}

static SANE_Bool
ta_available_pro (Mustek_Scanner * s)
{
  SANE_Status status;
  size_t len;
  SANE_Byte sense_buffer[4];

  len = sizeof (sense_buffer);

  status = sanei_scsi_cmd (s->fd, scsi_request_sense,
			   sizeof (scsi_request_sense), sense_buffer, &len);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "ta_available_pro: failed: %s\n", sane_strstatus (status));
      return status;
    }
  DBG (5, "ta_available_pro: sense_buffer[2] = %x\n", sense_buffer[2]);

  scsi_unit_wait_ready (s);
  if (sense_buffer[2] == 0x40)
    return SANE_TRUE;

  return SANE_FALSE;
}

static SANE_Status
attach (SANE_String_Const devname, Mustek_Device ** devp, SANE_Bool may_wait)
{
  SANE_Int mustek_scanner, 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;
  Mustek_Scanner s;
  Mustek_Device *dev, new_dev;
  SANE_Status status;
  size_t size;
  SANE_String scsi_device_type[] = {
    "Direct-Access", "Sequential-Access", "Printer", "Processor",
    "Write-Once", "CD-ROM", "Scanner", "Optical Memory", "Medium Changer",
    "Communications"
  };
  SANE_Byte scsi_vendor[9];
  SANE_Byte scsi_product[17];
  SANE_Byte scsi_revision[5];
  SANE_Byte *pp;
  SANE_Bool warning = SANE_FALSE;
  SANE_Int firmware_format = 0;
  SANE_Int firmware_revision_system = 0;

  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;
  s.hw->max_buffer_size = 8 * 1024;

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

  status = dev_open (devname, &s, sense_handler);
  if (status != SANE_STATUS_GOOD)
    return status;

  if (may_wait || force_wait)
    dev_wait_ready (&s);

  DBG (5, "attach: sending INQUIRY\n");
  size = sizeof (result);
  memset (result, 0, sizeof (result));
  status = dev_cmd (&s, scsi_inquiry, sizeof (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));
      dev_close (&s);
      return status;
    }

  status = dev_wait_ready (&s);
  dev_close (&s);

  if (status != SANE_STATUS_GOOD)
    return status;

  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 >= 3)
    {
      /* clear spaces and special chars */
      strncpy ((SANE_String) scsi_vendor, (SANE_String) result + 8, 8);
      scsi_vendor[8] = '\0';
      pp = scsi_vendor + 7;
      while (pp >= scsi_vendor && (*pp == ' ' || *pp >= 127))
	*pp-- = '\0';
      strncpy ((SANE_String) scsi_product, (SANE_String) result + 16, 16);
      scsi_product[16] = '\0';
      pp = scsi_product + 15;
      while (pp >= scsi_product && (*pp == ' ' || *pp >= 127))
	*pp-- = '\0';
      strncpy ((SANE_String) scsi_revision, (SANE_String) result + 32, 4);
      scsi_revision[4] = '\0';
      pp = scsi_revision + 3;
      while (pp >= scsi_revision && (*pp == ' ' || *pp >= 127))
	*pp-- = '\0';
      DBG (3, "attach: SCSI Vendor: `%-8s' Model: `%-16s' Rev.: `%-4s'\n",
	   scsi_vendor, scsi_product, scsi_revision);
      DBG (3, "attach: SCSI Type: %s; ANSI rev.: %d\n",
	   ((result[0] & 0x1f) < 0x10) ?
	   scsi_device_type[result[0] & 0x1f] : "Unknown", result[2] & 0x03);
      DBG (3, "attach: SCSI flags: %s%s%s%s%s%s%s\n",
	   (result[7] & 0x80) ? "RelAdr " : "",
	   (result[7] & 0x40) ? "WBus32 " : "",
	   (result[7] & 0x20) ? "WBus16 " : "",
	   (result[7] & 0x10) ? "Sync " : "",
	   (result[7] & 0x08) ? "Linked " : "",
	   (result[7] & 0x02) ? "CmdQue " : "",
	   (result[7] & 0x01) ? "SftRe " : "");
    }

  if (debug_level >= 4)
    {
      /* print out inquiry */
      DBG (4, "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 (4, "%s  %s\n", inquiry_byte_list, inquiry_text_list);
	      inquiry_byte_list[0] = '\0';
	      inquiry_text_list[0] = '\0';
	    }
	}
    }

  /* first check for new firmware format: */
  mustek_scanner = (strncmp ((SANE_String) result + 36, "MUSTEK", 6) == 0);
  if (mustek_scanner)
    {
      if (result[43] == 'M')
	{
	  DBG (3, "attach: found Mustek scanner (pro series firmware "
	       "format)\n");
	  firmware_format = 2;
	  model_name = result + 43;
	}
      else
	{
	  DBG (3, "attach: found Mustek scanner (new firmware format)\n");
	  firmware_format = 1;
	}
    }
  else
    {
      /* check for old format: */
      mustek_scanner = (strncmp ((SANE_String) result + 8, "MUSTEK", 6) == 0);
      if (mustek_scanner)
	{
	  model_name = result + 16;
	  DBG (3, "attach: found Mustek scanner (old firmware format)\n");
	  firmware_format = 0;
	}
      else
	{
	  /* Check for some non-Mustek scanners an print warning */
	  if (strncmp ((SANE_String) result + 8, "Trust", 5) == 0)
	    DBG (1, "attach: this is a real Trust scanner. It is not "
		 " supported by this backend.\n");
	  if (strncmp ((SANE_String) result + 8, "Aashima", 7) == 0)
	    DBG (1, "attach: this is an Aashima/Teco scanner. It is not "
		 " supported by this backend.\n");
	  if (strncmp ((SANE_String) result + 16, "Flatbed Scanner", 15) == 0
	      && strncmp ((SANE_String) result + 42, "TECO", 4) == 0)
	    DBG (1, "attach: this is a Relysis/Teco scanner. It is not "
		 " supported by this backend.\n");
	  DBG (1, "attach: device %s doesn't look like a Mustek scanner\n",
	       devname);
	  return SANE_STATUS_INVAL;
	}
    }

  /* get firmware revision as BCD number:             */
  /* General format: x.yz                             */
  /* Newer ScanExpress scanners (ID XC06): Vxyz       */
  if (result[33] == '.')
    {
      fw_revision =
	(result[32] - '0') << 8 | (result[34] - '0') << 4 | (result[35] -
							     '0');
      firmware_revision_system = 0;
      DBG (4, "attach: old firmware revision system\n");
    }
  else
    {
      fw_revision =
	(result[33] - '0') << 8 | (result[34] - '0') << 4 | (result[35] -
							     '0');
      firmware_revision_system = 1;
      DBG (4, "attach: new firmware revision system\n");
    }
  DBG (3, "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 = 0;
  dev->y_range.quant = 0;
  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 = 0;
  dev->y_trans_range.quant = 0;
  dev->dpi_range.min = SANE_FIX (72);	/* some scanners don't like low dpi */
  dev->dpi_range.quant = SANE_FIX (1);
  /* default to 128 kB */
  dev->max_buffer_size = 128 * 1024;	/* SCSI buffer -> use 64 k per buffer */
  dev->max_block_buffer_size = 1024 * 1024 * 1024;
  dev->firmware_format = firmware_format;
  dev->firmware_revision_system = firmware_revision_system;

  DBG (3, "attach: scanner id: %.11s\n", model_name);
  if (strncmp ((SANE_String) model_name + 10, "PRO", 3) == 0)
    DBG (3, "attach: this is probably a Paragon Pro series scanner\n");
  else if (strncmp ((SANE_String) model_name, "MFC", 3) == 0)
    DBG (3, "attach: this is probably a Paragon series II scanner\n");
  else if (strncmp ((SANE_String) model_name, "M", 1) == 0)
    DBG (3,
	 "attach: this is probably a Paragon series I or 3-pass scanner\n");
  else if (strncmp ((SANE_String) model_name, " C", 2) == 0)
    DBG (3, "attach: this is probably a ScanExpress series A4 scanner\n");
  else if (strncmp ((SANE_String) model_name, " L", 2) == 0)
    DBG (3, "attach: this is probably a ScanExpress series A3 scanner\n");
  else if (strncmp ((SANE_String) model_name, "XC", 2) == 0)
    DBG (3,
	 "attach: this is probably a ScanExpress Plus series A4 scanner\n");
  else
    DBG (3, "attach: I am not sure what type of scanner this is\n");

  /* Paragon 3-pass series */
  if (strncmp ((SANE_String) model_name, "MFS-12000CX", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon MFS-12000CX v4.00 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (14.00 * MM_PER_INCH);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);
      dev->dpi_range.max = SANE_FIX (1200);
      dev->sane.model = "MFS-12000CX";
    }
  /* There are two different versions of the MFS-6000CX, one has the model
     name "MFS-06000CX", the other one is "MSF-06000CZ"   */
  else if (strncmp ((SANE_String) model_name, "MFS-06000CX", 11) == 0)
    {
      /* These values were measured and tested with a Paragon MFS-6000CX
         v4.06 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (13.86 * MM_PER_INCH);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (2.0);
      dev->x_trans_range.max = SANE_FIX (203.0);
      dev->y_trans_range.max = SANE_FIX (255.0);

      dev->dpi_range.max = SANE_FIX (600);
      dev->sane.model = "MFS-6000CX";
    }
  else if (strncmp ((SANE_String) model_name, "MSF-06000CZ", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon MFS-6000CX v4.00 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (13.85 * MM_PER_INCH);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (2.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);

      dev->dpi_range.max = SANE_FIX (600);
      dev->sane.model = "MFS-6000CX";
    }

  /* Paragon 1-pass 14" series I */

  /* I haven't seen a single report for this, but it is mentioned in
     the old man page. All reported Paragon 1200SP had a model name
     "MFS-12000SP" */
  else if (strncmp ((SANE_String) model_name, "MSF-12000SP", 11) == 0)
    {
      /* These values are not tested and mostly guessed. */
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.max = SANE_FIX (13.85 * MM_PER_INCH);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (200.0);
      dev->y_trans_range.max = SANE_FIX (250.0);
      dev->dpi_range.max = SANE_FIX (1200);
      dev->flags |= MUSTEK_FLAG_LD_NONE;
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->flags |= MUSTEK_FLAG_USE_BLOCK;
      dev->sane.model = "MFS-12000SP";
      warning = SANE_TRUE;
    }
  /* MFS-8000 SP v 1.x */
  else if (strncmp ((SANE_String) model_name, "MSF-08000SP", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon MFS-8000SP v1.20 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (355.6);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);

      dev->dpi_range.max = SANE_FIX (800);
      /* At least scanners with firmware 1.20 need a gamma table upload
         in color mode, otherwise the image is red */
      if (fw_revision == 0x120)
	dev->flags |= MUSTEK_FLAG_FORCE_GAMMA;
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->sane.model = "MFS-8000SP";
    }
  /* This model name exists */
  else if (strncmp ((SANE_String) model_name, "MSF-06000SP", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon MFS-6000SP v3.12 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (355.6);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);
      dev->dpi_range.max = SANE_FIX (600);
      /* Looks like at least some versions of this scanner produce black images
         in gray and color mode if MUSTEK_FORCE_GAMMA is not set. At least
         versions 2.01, 2.02 and 2.10 are reported as having this bug. 3.12
         doesn't need this workaround but it doesn't harm either. */
      dev->flags |= MUSTEK_FLAG_FORCE_GAMMA;
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->sane.model = "MFS-6000SP";
    }

  /* This one was reported multiple times */
  else if (strncmp ((SANE_String) model_name, "MFS-12000SP", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon MFS-12000SP v1.02 and v1.00 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (217.0);
      dev->y_range.min = SANE_FIX (2.0);
      dev->y_range.max = SANE_FIX (352.0);
      dev->x_trans_range.min = SANE_FIX (0.0);
      dev->y_trans_range.min = SANE_FIX (0.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (250.0);

      dev->dpi_range.max = SANE_FIX (1200);
      /* Earlier versions of this source code used MUSTEK_FLAG_LD_MFS
         for firmware versions < 1.02 and LD_NONE for the rest. This
         didn't work for my scanners.  1.00 doesn't need any LD
         correction, 1.02, 1.07 and 1.11 do need normal LD
         corrections.  Maybe all != 1.00 need normal LD */
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->flags |= MUSTEK_FLAG_LD_BLOCK;
      dev->flags |= MUSTEK_FLAG_USE_BLOCK;
      dev->sane.model = "MFS-12000SP";
    }
  /* MFS-8000 SP v2.x */
  else if (strncmp ((SANE_String) model_name, "MFS-08000SP", 11) == 0)
    {
      /* These values are tested with a MFS-08000SP v 2.04 */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (355.6);
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);

      dev->dpi_range.max = SANE_FIX (800);
      /* At least scanners with firmware 1.20 need a gamma table upload
         in color mode, otherwise the image is red */
      if (fw_revision == 0x120)
	dev->flags |= MUSTEK_FLAG_FORCE_GAMMA;
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->sane.model = "MFS-8000SP";
    }
  /* I have never seen one of those */
  else if (strncmp ((SANE_String) model_name, "MFS-06000SP", 11) == 0)
    {
      /* These values are not tested. */
      dev->x_range.max = SANE_FIX (8.5 * MM_PER_INCH);
      dev->y_range.max = SANE_FIX (13.84 * MM_PER_INCH);
      /* copied from MSF-06000SP */
      dev->x_trans_range.min = SANE_FIX (1.0);
      dev->y_trans_range.min = SANE_FIX (1.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (255.0);
      dev->dpi_range.max = SANE_FIX (600);
      dev->flags |= MUSTEK_FLAG_PARAGON_1;
      dev->sane.model = "MFS-6000SP";
      warning = SANE_TRUE;
    }

  /* Paragon 1-pass A4 series II */
  else if (strncmp ((SANE_String) model_name, "MFC-08000CZ", 11) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon 800 II SP v1.06. */
      dev->x_range.min = SANE_FIX (1.5);
      dev->x_range.max = SANE_FIX (218.0);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (293.0);
      dev->x_trans_range.min = SANE_FIX (0.0);
      dev->y_trans_range.min = SANE_FIX (0.0);
      dev->x_trans_range.max = SANE_FIX (205.0);
      dev->y_trans_range.max = SANE_FIX (254.0);

      dev->dpi_range.max = SANE_FIX (800);
      dev->max_block_buffer_size = 2 * 1024 * 1024;

      dev->flags |= MUSTEK_FLAG_PARAGON_2;
      dev->flags |= MUSTEK_FLAG_LD_BLOCK;
      dev->flags |= MUSTEK_FLAG_USE_BLOCK;
      dev->sane.model = "800S/800 II SP";
    }
  else if (strncmp ((SANE_String) model_name, "MFC-06000CZ", 11) == 0)
    {
      /* These values were measured and compared to those from the
         Windows driver. Tested with a Paragon 600 II CD, a Paragon
         MFC-600S and a Paragon 600 II N. */
      dev->x_range.min = SANE_FIX (0.0);
      dev->x_range.max = SANE_FIX (218.0);
      dev->y_range.min = SANE_FIX (0.0);
      dev->y_range.max = SANE_FIX (293.0);
      dev->x_trans_range.min = SANE_FIX (0.0);
      dev->y_trans_range.min = SANE_FIX (0.0);
      dev->x_trans_range.max = SANE_FIX (201.0);
      dev->y_trans_range.max = SANE_FIX (257.0);

      dev->dpi_range.max = SANE_FIX (600);
      /* This model comes in a non-scsi version, too. It is supplied
         with its own parallel-port like adapter, an AB306N. Two
         firmware revisions are known: 1.01 and 2.00. Each needs its
         own line-distance correction code. */
      if (dev->flags & MUSTEK_FLAG_N)
	{
	  if (fw_revision < 0x200)
	    dev->flags |= MUSTEK_FLAG_LD_N1;
	  else
	    dev->flags |= MUSTEK_FLAG_LD_N2;
	  dev->x_trans_range.min = SANE_FIX (33.0);
	  dev->y_trans_range.min = SANE_FIX (62.0);
	  dev->x_trans_range.max = SANE_FIX (183.0);
	  dev->y_trans_range.max = SANE_FIX (238.0);
	  dev->max_block_buffer_size = 1024 * 1024 * 1024;
	  dev->sane.model = "600 II N";
	}
      else if (dev->flags & MUSTEK_FLAG_SCSI_PP)
	{
	  /* FIXME; experiment with different line distance codes later */
	  dev->dpi_range.min = SANE_FIX (75.0);
	  dev->flags |= MUSTEK_FLAG_LD_NONE;
	  dev->max_block_buffer_size = 2 * 1024 * 1024;
	  dev->sane.model = "600 II EP";
	}
      else
	{
	  dev->sane.model = "600S/600 II CD";
	  dev->flags |= MUSTEK_FLAG_PARAGON_2;
	  dev->flags |= MUSTEK_FLAG_LD_BLOCK;
	  dev->flags |= MUSTEK_FLAG_USE_BLOCK;
	  dev->max_block_buffer_size = 2 * 1024 * 1024;
	}
    }

  /* ScanExpress and ScanMagic series */
  else if (strncmp ((SANE_String) model_name, " C03", 4) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a ScannExpress 6000SP 1.00 */
      dev->x_range.max = SANE_FIX (215);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (293);

      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->dpi_range.max = SANE_FIX (600);
      dev->dpi_range.min = SANE_FIX (60);
      dev->flags |= MUSTEK_FLAG_SE;
      /* At least the SE 6000SP with firmware 1.00 limits its
         x-resolution to 300 dpi and does *no* interpolation at higher
         resolutions. So this has to be done in software. */
      dev->flags |= MUSTEK_FLAG_ENLARGE_X;
      dev->sane.model = "ScanExpress 6000SP";
    }
  /* There are two different versions of the ScanExpress 12000SP, one
     has the model name " C06", the other one is "XC06". The latter
     seems to be used in the newer "Plus" models.
     Also there is the Mustek ScanExpress 1200 FS, which looks similar to the
     ScanExpress 12000 SP but has an "F" instead of the "V" in the
     firmware version.
  */
  else if (strncmp ((SANE_String) model_name, " C06", 4) == 0)
    {
      if (result[32] == 'F')
	{
	  /* Mustek ScanExpress 1200 FS. Completely untested. */
	  dev->x_range.min = SANE_FIX (0);
	  dev->y_range.min = SANE_FIX (0);
	  dev->x_range.max = SANE_FIX (215.9);
	  dev->y_range.max = SANE_FIX (291.2);

	  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->dpi_range.max = SANE_FIX (1200);
	  dev->dpi_range.min = SANE_FIX (60);
	  dev->flags |= MUSTEK_FLAG_SE;
	  /* The ScanExpress models limit their x-resolution to 600 dpi
	     and do *no* interpolation at higher resolutions. So this has
	     to be done in software. */
	  dev->flags |= MUSTEK_FLAG_ENLARGE_X;
	  dev->flags |= MUSTEK_FLAG_COVER_SENSOR;
	  dev->sane.model = "ScanExpress 12000 FS (untested)";
	}
      else
	{
	  /* These values were measured and compared to those from the Windows
	     driver. Tested with a ScaneExpress 12000SP 2.02 and a ScanMagic
	     9636S v 1.01 */
	  dev->x_range.min = SANE_FIX (0);
	  dev->y_range.min = SANE_FIX (0);
	  dev->x_range.max = SANE_FIX (215.9);
	  dev->y_range.max = SANE_FIX (291.2);

	  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->dpi_range.max = SANE_FIX (1200);
	  dev->dpi_range.min = SANE_FIX (60);
	  dev->flags |= MUSTEK_FLAG_SE;
	  /* The ScanExpress models limit their x-resolution to 600 dpi
	     and do *no* interpolation at higher resolutions. So this has
	     to be done in software. */
	  dev->flags |= MUSTEK_FLAG_ENLARGE_X;
	  dev->flags |= MUSTEK_FLAG_COVER_SENSOR;
	  dev->sane.model = "ScanExpress 12000SP";
	}
    }
  else if (strncmp ((SANE_String) model_name, "XC06", 4) == 0)
    {
      /* These values are tested with a SE 12000 SP Plus v 1.01 */
      dev->x_range.max = SANE_FIX (216);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (294.5);

      dev->x_trans_range.min = SANE_FIX (0);
      dev->y_trans_range.min = SANE_FIX (0);
      dev->x_trans_range.max = SANE_FIX (152.0);
      dev->y_trans_range.max = SANE_FIX (177.0);

      dev->dpi_range.max = SANE_FIX (1200);
      dev->dpi_range.min = SANE_FIX (60);

      dev->flags |= MUSTEK_FLAG_SE;
      dev->flags |= MUSTEK_FLAG_SE_PLUS;
      /* The ScanExpress models limit their x-resolution to 600 dpi
         and do *no* interpolation at higher resolutions. So this has
         to be done in software. */
      dev->flags |= MUSTEK_FLAG_ENLARGE_X;
      dev->flags |= MUSTEK_FLAG_COVER_SENSOR;
      dev->sane.model = "ScanExpress 12000SP Plus";
    }
  /* ScanExpress A3 SP */
  else if (strncmp ((SANE_String) model_name, " L03", 4) == 0)
    {
      /* These values were measured with a ScannExpress A3 SP 2.00 */
      dev->x_range.max = SANE_FIX (297);
      dev->y_range.min = SANE_FIX (0);
      dev->y_range.max = SANE_FIX (430);

      /* TA couldn't be tested due to lack of equipment. So At least
         the TA IV (A4 size) is supported */
      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->dpi_range.max = SANE_FIX (600);
      dev->dpi_range.min = SANE_FIX (60);
      dev->flags |= MUSTEK_FLAG_SE;
      /* The ScanExpress models limit their x-resolution to 300 dpi
         and do *no* interpolation at higher resolutions. So this has
         to be done in software. */
      dev->flags |= MUSTEK_FLAG_ENLARGE_X;
      dev->flags |= MUSTEK_FLAG_COVER_SENSOR;
      dev->sane.model = "ScanExpress A3 SP";
    }
  /* Paragon 1200 SP Pro */
  else if (strncmp ((SANE_String) model_name, "MFS-1200SPPRO", 13) == 0)
    {
      /* These values were measured with a Paragon 1200 SP Pro v2.01 */
      dev->x_range.max = SANE_FIX (8.6 * MM_PER_INCH);
      dev->y_range.max = SANE_FIX (13.70 * MM_PER_INCH);
      dev->dpi_range.max = SANE_FIX (1200);
      dev->sane.model = "1200 SP PRO";
      dev->flags |= MUSTEK_FLAG_LD_NONE;
      dev->flags |= MUSTEK_FLAG_ENLARGE_X;
    }
  /* No documentation, but it works: Paragon 1200 A3 PRO  */
  else if (strncmp ((SANE_String) model_name, "MFS-1200A3PRO", 13) == 0)
    {
      /* These values were measured and compared to those from the Windows
         driver. Tested with a Paragon 1200 A3 Pro v1.10 */
      dev->x_range.max = SANE_FIX (11.7 * MM_PER_INCH);
      dev->y_range.max = SANE_FIX (424);
      dev->dpi_range.max = SANE_FIX (1200);
      dev->sane.model = "1200 A3 PRO";
      dev->flags |= MUSTEK_FLAG_LD_NONE;
      dev->flags |= MUSTEK_FLAG_ENLARGE_X;
    }
  else
    {
      DBG (0, "attach: this Mustek 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_MUSTEK=5\n");
      DBG (0, "attach: scanimage -L 2>debug.txt). Thank you.\n");
      free (dev);
      return SANE_STATUS_INVAL;
    }

  if (dev->flags & MUSTEK_FLAG_SE)
    {
      DBG (3, "attach: this is a single-pass scanner\n");
      if (result[63] & (1 << 6))
	{
	  dev->flags |= MUSTEK_FLAG_TA;
	  DBG (3, "attach: scanner supports transparency adapter (TA)\n");
	}
    }
  else
    {
      if (result[57] & (1 << 6))
	{
	  DBG (3, "attach: this is a single-pass scanner\n");
	  if (dev->flags & MUSTEK_FLAG_LD_NONE)
	    DBG (4,
		 "attach: scanner doesn't need line-distance correction\n");
	  else if (dev->flags & MUSTEK_FLAG_LD_N1)
	    DBG (4, "attach: scanner has N1 line-distance correction\n");
	  else if (dev->flags & MUSTEK_FLAG_LD_N2)
	    DBG (4, "attach: scanner has N2 line-distance correction\n");
	  else if (dev->flags & MUSTEK_FLAG_LD_BLOCK)
	    DBG (4, "attach: scanner has block line-distance correction\n");
	  else
	    DBG (4, "attach: scanner has normal line-distance correction\n");
	}
      else
	{
	  dev->flags |= MUSTEK_FLAG_THREE_PASS;
	  /* three-pass scanners quantize to 0.5% of the maximum resolution: */
	  dev->dpi_range.quant = dev->dpi_range.max / 200;
	  dev->dpi_range.min = dev->dpi_range.quant;
	  DBG (3, "attach: this is a three-pass scanner\n");
	}
      if (result[57] & (1 << 5))
	{
	  DBG (3, "attach: this is a professional series scanner\n");
	  dev->flags |= MUSTEK_FLAG_PRO;
	  status = dev_open (devname, &s, sense_handler);
	  if (status == SANE_STATUS_GOOD)
	    {
	      if (ta_available_pro (&s))
		{
		  dev->flags |= MUSTEK_FLAG_TA;
		  DBG (3, "attach: found transparency adapter (TA)\n");
		}
	      dev_close (&s);
	    }
	  else
	    {
	      DBG (1, "attach: couldn't open device: %s\n",
		   sane_strstatus (status));
	      return status;
	    }
	}
      if (result[63] & (1 << 2))
	{
	  dev->flags |= MUSTEK_FLAG_ADF;
	  DBG (3, "attach: found automatic document feeder (ADF)\n");
	  if (result[63] & (1 << 3))
	    {
	      dev->flags |= MUSTEK_FLAG_ADF_READY;
	      DBG (4, "attach: automatic document feeder is ready\n");
	    }
	  else
	    {
	      DBG (4, "attach: automatic document feeder is out of "
		   "documents\n");
	    }
	}

      if (result[63] & (1 << 6))
	{
	  dev->flags |= MUSTEK_FLAG_TA;
	  DBG (3, "attach: found transparency adapter (TA)\n");
	}
    }

  if (dev->flags & MUSTEK_FLAG_COVER_SENSOR)
    {
      if (result[62] & (1 << 0))
	DBG (4, "attach: scanner cover is closed\n");
      else
	DBG (4, "attach: scanner cover is open\n");
    }

  if (warning == SANE_TRUE)
    {
      DBG (0,
	   "WARNING: Your scanner was detected by the SANE Mustek backend, "
	   "but\n  it is not fully tested. It may or may not work. Be "
	   "careful and read\n  the PROBLEMS file in the sane directory. "
	   "Please set the debug level of this\n  backend to maximum "
	   "(export SANE_DEBUG_MUSTEK=255) and send the output of\n  "
	   "scanimage -L to the SANE mailing list sane-devel@alioth-lists.debian.net. "
	   "Please include\n  the exact model name of your scanner and to "
	   "which extend it works.\n");
    }

  DBG (2, "attach: found Mustek %s %s, %s%s%s%s\n",
       dev->sane.model, dev->sane.type,
       (dev->flags & MUSTEK_FLAG_THREE_PASS) ? "3-pass" : "1-pass",
       (dev->flags & MUSTEK_FLAG_ADF) ? ", ADF" : "",
       (dev->flags & MUSTEK_FLAG_TA) ? ", TA" : "",
       (dev->flags & MUSTEK_FLAG_SE) ? ", SE" : "");

  ++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
constrain_value (Mustek_Scanner * s, SANE_Int option, void *value,
		 SANE_Int * info)
{
  SANE_Fixed w, dpi;
  SANE_Status status;

  if (value)
    w = *(SANE_Fixed *) value;
  else
    w = 0;

  if (option == OPT_RESOLUTION)
    {
      if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	{
	  /* The three pass scanners use a 0.5% of the maximum resolution
	     increment for resolutions less than or equal to half of the
	     maximum resolution. The MFS-06000CX uses a 5% of the maximum
	     resolution increment for larger resolutions.  The models
	     MFS-12000CX and MSF-06000CZ use 1% of the maximum resolution.
	     We can't represent this easily in SANE, so the constraint is
	     simply for 0.5% and then we round to the 5% or 1% increments
	     if necessary.  */
	  SANE_Fixed max_dpi, quant, half_res;

	  /*w = *(SANE_Word *) value; */
	  max_dpi = s->hw->dpi_range.max;
	  half_res = max_dpi / 2;

	  if (w > half_res)
	    {
	      /* quantizize to 1% step */
	      quant = max_dpi / 100;

	      dpi = (w + quant / 2) / quant;
	      dpi *= quant;
	      if (dpi != w)
		{
		  *(SANE_Word *) value = dpi;
		  if (info)
		    *info |= SANE_INFO_INEXACT;
		}
	    }

	}
    }

  status = sanei_constrain_value (s->opt + option, value, info);
  if (s->opt[option].type == SANE_TYPE_FIXED)
    DBG (5, "constrain_value: %s = %.2f (was %.2f)\n", s->opt[option].name,
	 SANE_UNFIX (*(SANE_Word *) value), SANE_UNFIX (w));
  return status;
}

/* Quantize s->val[OPT_RESOLUTION].w and return the resolution code for the
   quantized resolution.  Quantization depends on scanner type (single
   pass vs. three-pass) and resolution */
static SANE_Int
encode_resolution (Mustek_Scanner * s)
{
  SANE_Fixed max_dpi, dpi;
  SANE_Int code, mode = 0;

  dpi = s->val[OPT_RESOLUTION].w;

  if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    {
      code = dpi >> SANE_FIXED_SCALE_SHIFT;
    }
  else
    {
      SANE_Fixed quant, half_res;

      max_dpi = s->hw->dpi_range.max;
      half_res = max_dpi / 2;

      if (dpi <= half_res)
	{
	  /* quantizize to 0.5% step */
	  quant = max_dpi / 200;
	}
      else
	{
	  /* quantizize to 1% step */
	  quant = max_dpi / 100;
	  mode = 0x100;		/* indicate 5% or 1% quantization */
	}

      code = (dpi + quant / 2) / quant;
      if (code < 1)
	code = 1;

    }
  DBG (5, "encode_resolution: code = 0x%x (%d); mode = %x\n", code, code,
       mode);
  return code | mode;
}

static SANE_Int
encode_percentage (Mustek_Scanner * s, double value)
{
  SANE_Int max, code, sign = 0;

  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
    {
      code = (int) ((value / 100.0 * 12) + 12.5);
      max = 0x18;
    }
  else
    {
      if (value < 0.0)
	{
	  value = -value;
	  sign = 0x80;
	}
      code = (int) (value / 100.0 * 127 + 0.5);
      code |= sign;
      max = 0xff;
    }
  if (code > max)
    code = max;
  if (code < 0)
    code = 0x00;
  return code;
}

/* encode halftone pattern type and size */
static SANE_Status
encode_halftone (Mustek_Scanner * s)
{
  SANE_String selection = s->val[OPT_HALFTONE_DIMENSION].s;
  SANE_Int i = 0;

  while ((halftone_list[i] != 0) && (strcmp (selection, halftone_list[i]) != 0))
    {
      i++;
    }
  if (halftone_list[i] == 0)
    return SANE_STATUS_INVAL;

  if (i < 0x0c)			/* standard pattern */
    {
      s->custom_halftone_pattern = SANE_FALSE;
      s->halftone_pattern_type = i;
    }
  else				/* custom pattern */
    {
      s->custom_halftone_pattern = SANE_TRUE;
      i -= 0x0c;
      i = 8 - i;
      if (i < 8)
	i--;
      i = i + (i << 4);
      s->halftone_pattern_type = i;
    }

  DBG (5, "encode_halftone: %s pattern type %x\n",
       s->custom_halftone_pattern ? "custom" : "standard",
       s->halftone_pattern_type);
  return SANE_STATUS_GOOD;
}

/* Paragon series */
static SANE_Status
area_and_windows (Mustek_Scanner * s)
{
  SANE_Byte cmd[117], *cp;
  SANE_Int i, offset;

  /* setup SCSI command (except length): */
  memset (cmd, 0, sizeof (cmd));
  cmd[0] = MUSTEK_SCSI_AREA_AND_WINDOWS;

  cp = cmd + 6;

  /* Some scanners need a larger scanarea for line-distance correction */
  offset = 0;
  if (((s->hw->flags & MUSTEK_FLAG_LD_N1)
       || ((s->hw->flags & MUSTEK_FLAG_LD_BLOCK)
	   && (s->hw->flags & MUSTEK_FLAG_PARAGON_1)))
      && (s->mode & MUSTEK_MODE_COLOR))
    offset = MAX_LINE_DIST;

  /* fill in frame header: */

  if (s->hw->flags & MUSTEK_FLAG_USE_EIGHTS)
    {
      double eights_per_mm = 8 / MM_PER_INCH;
      SANE_Int tlx, tly, brx, bry;
      /*
       * The MSF-06000CZ seems to lock-up if the pixel-unit is used.
       * Using 1/8" works.
       * This doesn't seem to be true with the current scheme.
       * This code isn't used at the moment.  <henning@meier-geinitz.de>
       */
      *cp++ = ((s->mode & MUSTEK_MODE_LINEART) ? 0x00 : 0x01);

      tlx = SANE_UNFIX (s->val[OPT_TL_X].w) * eights_per_mm + 0.5;
      tly = SANE_UNFIX (s->val[OPT_TL_Y].w) * eights_per_mm + 0.5;
      brx = SANE_UNFIX (s->val[OPT_BR_X].w) * eights_per_mm + 0.5;
      bry = SANE_UNFIX (s->val[OPT_BR_Y].w) * eights_per_mm + 0.5;
      STORE16L (cp, tlx);
      STORE16L (cp, tly);
      STORE16L (cp, brx);
      STORE16L (cp, bry);
      DBG (5, "area_and_windows: tlx=%d (%d mm); tly=%d (%d mm); "
	   "brx=%d (%d mm); bry=%d (%d mm)\n", tlx,
	   (int) (tlx / eights_per_mm), tly, (int) (tly / eights_per_mm), brx,
	   (int) (brx / eights_per_mm), bry, (int) (bry / eights_per_mm));
    }
  else
    {
      double pixels_per_mm = SANE_UNFIX (s->hw->dpi_range.max) / MM_PER_INCH;
      SANE_Int tlx, tly, brx, bry;

      if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	/* 3pass scanners use 1/2 of the max resolution as base */
	pixels_per_mm /= 2;

      /* pixel unit and halftoning: */
      *cp++ = 0x8 | ((s->mode & MUSTEK_MODE_LINEART) ? 0x00 : 0x01);

      /* fill in scanning area: */
      if (strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0)
	{
	  /* must mirror the x coordinates */
	  brx = SANE_UNFIX (s->hw->x_range.max - s->val[OPT_TL_X].w)
	    * pixels_per_mm + 0.5;
	  tlx = SANE_UNFIX (s->hw->x_range.max - s->val[OPT_BR_X].w)
	    * pixels_per_mm + 0.5;
	}
      else
	{
	  tlx = SANE_UNFIX (s->val[OPT_TL_X].w) * pixels_per_mm + 0.5;
	  brx = SANE_UNFIX (s->val[OPT_BR_X].w) * pixels_per_mm + 0.5;

	}
      tly = SANE_UNFIX (s->val[OPT_TL_Y].w) * pixels_per_mm + 0.5;
      bry = SANE_UNFIX (s->val[OPT_BR_Y].w) * pixels_per_mm + 0.5 + offset;
      STORE16L (cp, tlx);
      STORE16L (cp, tly);
      STORE16L (cp, brx);
      STORE16L (cp, bry);
      DBG (5, "area_and_windows: tlx=%d (%d mm); tly=%d (%d mm); "
	   "brx=%d (%d mm); bry=%d (%d mm)\n", tlx,
	   (int) (tlx / pixels_per_mm), tly, (int) (tly / pixels_per_mm), brx,
	   (int) (brx / pixels_per_mm), bry, (int) (bry / pixels_per_mm));
    }

  if (s->custom_halftone_pattern)
    {
      *cp++ = 0x40;		/* mark presence of user pattern */
      *cp++ = s->halftone_pattern_type;	/* set pattern length */
      for (i = 0; i < (s->halftone_pattern_type & 0x0f) *
	   ((s->halftone_pattern_type >> 4) & 0x0f); ++i)
	*cp++ = s->val[OPT_HALFTONE_PATTERN].wa[i];
    }

  cmd[4] = (cp - cmd) - 6;

  return dev_cmd (s, cmd, (cp - cmd), 0, 0);
}

/* ScanExpress */
static SANE_Status
set_window_se (Mustek_Scanner * s, SANE_Int lamp)
{
  SANE_Byte cmd[58], *cp;
  double pixels_per_mm;
  SANE_Int offset;
  SANE_Int tlx, tly, width, height;

  /* setup SCSI command (except length): */
  memset (cmd, 0, sizeof (cmd));
  cmd[0] = MUSTEK_SCSI_SET_WINDOW;
  cp = cmd + sizeof (scsi_set_window);	/* skip command block           */

  if (s->mode & MUSTEK_MODE_COLOR)
    {
      /* We have to increase the specified resolution to the next       */
      /* "standard" resolution due to a firmware bug(?) in color mode   */
      /* It's possible to scan in 36, 75, 100, 150, 200, 250, 300,      */
      /* 400, 500, 600, 900, 1200 dpi but the speed is only different   */
      /* with 36, 150, 300, 600, 1200 dpi.                              */
      /* Additionally we must increase the window length slightly to    */
      /* compensate for different line counts for r/g/b                 */
      const SANE_Int resolution_list[] = { 36, 150, 300, 600, 1200, 0 };
      SANE_Int entry = 0;

      while (resolution_list[entry] < s->resolution_code)
	entry++;
      s->ld.peak_res = resolution_list[entry];

      offset = MAX_LINE_DIST;	/* distance r/b lines           */
    }
  else
    {
      /* In gray and lineart modes all resolutions are possible         */
      s->ld.peak_res = s->resolution_code;
      offset = 0;
    }
  DBG (5, "set_window_se: hardware resolution is %d dpi; offset is %d\n",
       s->ld.peak_res, offset);

  STORE16B (cp, 0);		/* window identifier            */
  STORE16B (cp, s->ld.peak_res);
  /* x and y resolution           */
  STORE16B (cp, 0);		/* not used acc. to specs       */

  pixels_per_mm = SANE_UNFIX (s->hw->dpi_range.max) / MM_PER_INCH;

  /* fill in scanning area, begin and length(!) */
  if ((strcmp (s->val[OPT_SOURCE].s, "Transparency Adapter") == 0) &&
      !(s->hw->flags & MUSTEK_FLAG_TA))
    {
      /* need to add the start values of the transparency adapter */
      tlx = (SANE_UNFIX (s->val[OPT_TL_X].w) + 33.0) * pixels_per_mm + 0.5;
      tly = (SANE_UNFIX (s->val[OPT_TL_Y].w) + 60.0) * pixels_per_mm + 0.5;
      DBG (5, "set_window_se: added offset for transparency adapter\n");
    }
  else
    {
      /* no transparency adapter selected or calculation done in firmware */
      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;

  DBG (5, "set_window_se: 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));


  STORE32B (cp, tlx);
  STORE32B (cp, tly);
  STORE32B (cp, width);
  STORE32B (cp, height);

  *cp++ = 0x00;			/* brightness, not impl.        */
  *cp++ = 0x80;			/* threshold, not impl.         */
  *cp++ = 0x00;			/* contrast, not impl.          */

  /* Note that 'image composition' has no meaning for the SE series     */
  /* Mode selection is accomplished solely by bits/pixel (1, 8, 24)     */
  if (s->mode & MUSTEK_MODE_COLOR)
    {
      *cp++ = 0x05;		/* actually not used!           */
      *cp++ = 24;		/* 24 bits/pixel in color mode  */
    }
  else if (s->mode & MUSTEK_MODE_GRAY)
    {
      *cp++ = 0x02;		/* actually not used!           */
      *cp++ = 8;		/* 8 bits/pixel in gray mode    */
    }
  else
    {
      *cp++ = 0x00;		/* actually not used!           */
      *cp++ = 1;		/* 1 bit/pixel in lineart mode  */
    }

  cp += 14;			/* skip reserved bytes          */
  *cp++ = lamp;			/* 0 = normal, 1 = on, 2 = off  */

  if ((s->hw->flags & MUSTEK_FLAG_TA)
      && (strcmp (s->val[OPT_SOURCE].s, "Transparency Adapter") == 0))
    *cp++ = 1;
  else
    *cp++ = 0;
  cp += 5;			/* skip reserved bytes          */

  cmd[8] = cp - cmd - sizeof (scsi_set_window);
  return dev_cmd (s, cmd, (cp - cmd), 0, 0);
}

/* Pro series */
static SANE_Status
set_window_pro (Mustek_Scanner * s)
{
  SANE_Byte cmd[20], *cp;
  double pixels_per_mm;

  memset (cmd, 0, sizeof (cmd));
  cmd[0] = MUSTEK_SCSI_SET_WINDOW;
  if (strcmp (s->hw->sane.model, "1200 SP PRO") == 0)
    cmd[8] = 0x09;
  else
    cmd[8] = 0x0a;

  cp = cmd + sizeof (scsi_set_window);	/* skip command block           */

  *cp++ = 0;			/* what's this? */
  pixels_per_mm = SANE_UNFIX (s->hw->dpi_range.max) / MM_PER_INCH;

  /* The next for 16 bit values are x0, y0, x1, y1 in pixels at max res */
  STORE16L (cp, SANE_UNFIX (s->val[OPT_TL_X].w) * pixels_per_mm + 0.5);
  STORE16L (cp, SANE_UNFIX (s->val[OPT_TL_Y].w) * pixels_per_mm + 0.5);
  STORE16L (cp, SANE_UNFIX (s->val[OPT_BR_X].w) * pixels_per_mm + 0.5);
  STORE16L (cp, SANE_UNFIX (s->val[OPT_BR_Y].w) * pixels_per_mm + 0.5);

  if (strcmp (s->hw->sane.model, "1200 SP PRO") != 0)
    *cp++ = lamp_off_time;		/* Only needed for A3 Pro, default: 60 minutes until lamp-off */
  DBG (5, "set_window_pro\n");

  return dev_cmd (s, cmd, (cp - cmd), 0, 0);
}

/* Pro series calibration */
static SANE_Status
get_calibration_size_pro (Mustek_Scanner * s)
{
  SANE_Status status;
  SANE_Byte cmd[6];
  SANE_Byte result[6];
  size_t len;

  memset (cmd, 0, sizeof (cmd));
  memset (result, 0, sizeof (result));
  cmd[0] = MUSTEK_SCSI_GET_IMAGE_STATUS;
  cmd[4] = 0x06;		/* size of result */
  cmd[5] = 0x80;		/* get back buffer size and number of buffers */
  len = sizeof (result);
  status = dev_cmd (s, cmd, sizeof (cmd), result, &len);
  if (status != SANE_STATUS_GOOD)
    return status;

  s->hw->cal.bytes = result[1] | (result[2] << 8);
  s->hw->cal.lines = result[3] | (result[4] << 8);

  DBG (4, "get_calibration_size_pro: bytes=%d, lines=%d\n", s->hw->cal.bytes,
       s->hw->cal.lines);
  return SANE_STATUS_GOOD;
}

static SANE_Status
get_calibration_lines_pro (Mustek_Scanner * s)
{
  SANE_Status status;
  SANE_Byte cmd[10];
  size_t len;
  SANE_Int line;

  DBG (2, "get_calibration_lines_pro: please wait for warmup\n");
  memset (cmd, 0, sizeof (cmd));
  cmd[0] = MUSTEK_SCSI_READ_DATA;
  len = s->hw->cal.bytes;
  cmd[6] = (len >> 16) & 0xff;
  cmd[7] = (len >> 8) & 0xff;
  cmd[8] = (len >> 0) & 0xff;

  for (line = 0; line < s->hw->cal.lines; line++)
    {
      status = dev_cmd (s, cmd, sizeof (scsi_read_data),
			s->hw->cal.buffer + line * len, &len);

      if ((status != SANE_STATUS_GOOD)
	  || (len != (unsigned int) s->hw->cal.bytes))
	{
	  DBG (1, "get_calibration_lines_pro: read failed\n");
	  return status;
	}
    }
  DBG (5, "get_calibration_lines_pro finished. Assuming 12 bits per color\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
send_calibration_lines_pro (Mustek_Scanner * s)
{
  SANE_Status status;
  SANE_Byte *cmd1, *cmd2;
  size_t buf_size;
  SANE_Word column, line, color;

  DBG (5, "send_calibration_lines_pro\n");

  buf_size = s->hw->cal.bytes / 2;
  cmd1 = (SANE_Byte *) malloc (buf_size + sizeof (scsi_send_data));
  cmd2 = (SANE_Byte *) malloc (buf_size + sizeof (scsi_send_data));
  if (!cmd1 || !cmd2)
    {
      DBG (1, "send_calibration_lines_pro: failed to malloc %ld bytes for "
	   "sending lines\n",
	   (long int) (buf_size + sizeof (scsi_send_data)));
      return SANE_STATUS_NO_MEM;
    }
  memset (cmd1, 0, sizeof (scsi_send_data));
  memset (cmd2, 0, sizeof (scsi_send_data));

  cmd1[0] = cmd2[0] = MUSTEK_SCSI_SEND_DATA;
  cmd1[6] = cmd2[6] = (buf_size >> 16) & 0xff;
  cmd1[7] = cmd2[7] = (buf_size >> 8) & 0xff;
  cmd1[8] = cmd2[8] = (buf_size >> 0) & 0xff;
  cmd1[9] = 0;			/* Least significant 8 bits */
  cmd2[9] = 0x80;		/* Most significant 2 bits */

  for (color = 0; color < 3; color++)
    {
      for (column = 0; column < s->hw->cal.bytes / 6; column++)
	{
	  SANE_Word calibration_word = 0;
	  for (line = 0; line < s->hw->cal.lines; line++)
	    {
	      calibration_word +=
		*(s->hw->cal.buffer + column * 6 + color_seq[color] * 2 + 0)
		+
		(*(s->hw->cal.buffer + column * 6 + color_seq[color] * 2 + 1)
		 << 8);
	    }
	  if (!calibration_word)
	    calibration_word = 1;
	  calibration_word = (1024 * 65536 / calibration_word) - 1024;
	  if (calibration_word > 1023)
	    calibration_word = 1023;
	  *(cmd1 + sizeof (scsi_send_data) + (buf_size / 3) * color + column)
	    = calibration_word & 0xff;
	  *(cmd2 + sizeof (scsi_send_data) + (buf_size / 3) * color + column)
	    = (calibration_word >> 8) & 0xff;
	}
    }

  status = dev_cmd (s, cmd1, buf_size + sizeof (scsi_send_data), 0, 0);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "send_calibration_lines_pro: send failed\n");
      return status;
    }

  status = dev_cmd (s, cmd2, buf_size + sizeof (scsi_send_data), 0, 0);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "send_calibration_lines_pro: send failed\n");
      return status;
    }
  free (cmd1);
  free (cmd2);
  return SANE_STATUS_GOOD;
}

static SANE_Status
calibration_pro (Mustek_Scanner * s)
{
  SANE_Status status;

  if (s->val[OPT_QUALITY_CAL].w)
    DBG (4, "calibration_pro: doing calibration\n");
  else
    {
      DBG (4, "calibration_pro: calibration not necessary\n");
      return SANE_STATUS_GOOD;
    }

  status = get_calibration_size_pro (s);
  if (status != SANE_STATUS_GOOD)
    return status;

  s->hw->cal.buffer = (SANE_Byte *) malloc (s->hw->cal.bytes *
					    s->hw->cal.lines);
  if (!s->hw->cal.buffer)
    {
      DBG (1, "calibration_pro: failed to malloc %d bytes for buffer\n",
	   s->hw->cal.bytes * s->hw->cal.lines);
      return SANE_STATUS_NO_MEM;
    }

  status = get_calibration_lines_pro (s);
  if (status != SANE_STATUS_GOOD)
    return status;

  status = send_calibration_lines_pro (s);
  if (status != SANE_STATUS_GOOD)
    return status;

  free (s->hw->cal.buffer);
  return SANE_STATUS_GOOD;
}


/* ScanExpress series calibration */
static SANE_Status
get_calibration_lines_se (Mustek_Scanner * s)
{
  SANE_Status status;
  SANE_Byte cmd[10];
  size_t len;
  SANE_Word lines, bytes_per_color;

  if (s->mode == MUSTEK_MODE_COLOR)
    {
      lines = s->hw->cal.lines * 3;
      bytes_per_color = s->hw->cal.bytes / 3;
    }
  else
    {
      lines = s->hw->cal.lines;
      bytes_per_color = s->hw->cal.bytes;
    }

  DBG (4, "get_calibration_lines_se: reading %d lines (%d bytes per color)\n",
       lines, bytes_per_color);
  memset (cmd, 0, sizeof (cmd));
  cmd[0] = MUSTEK_SCSI_READ_DATA;
  cmd[2] = 1;
  cmd[7] = (lines >> 8) & 0xff;
  cmd[8] = (lines >> 0) & 0xff;
  len = lines * bytes_per_color;
  status = dev_cmd (s, cmd, sizeof (scsi_read_data), s->hw->cal.buffer, &len);
  if ((status != SANE_STATUS_GOOD)
      || (len != (unsigned int) (lines * bytes_per_color)))
    {
      DBG (1, "get_calibration_lines_se: read failed\n");
      return status;
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
send_calibration_lines_se (Mustek_Scanner * s, SANE_Word color)
{
  SANE_Status status;
  SANE_Byte *cmd;
  size_t buf_size;
  SANE_Word column;
  SANE_Word bytes_per_color;

  if (s->mode == MUSTEK_MODE_COLOR)
    {
      bytes_per_color = s->hw->cal.bytes / 3;
    }
  else
    {
      bytes_per_color = s->hw->cal.bytes;
    }

  buf_size = bytes_per_color;

  DBG (5, "send_calibration_lines_se: %d bytes, color: %d\n",
       bytes_per_color, color + 1);

  cmd = (SANE_Byte *) malloc (buf_size + sizeof (scsi_send_data));
  if (!cmd)
    {
      DBG (1, "send_calibration_lines_se: failed to malloc %ld bytes for "
	   "sending lines\n",
	   (long int) (buf_size + sizeof (scsi_send_data)));
      return SANE_STATUS_NO_MEM;
    }
  memset (cmd, 0, sizeof (scsi_send_data));

  for (column = 0; column < bytes_per_color; column++)
    {
      SANE_Word line;
      SANE_Word cali_word = 0;
      SANE_Int color_seq[] = { 2, 0, 1 };

      for (line = 0; line < s->hw->cal.lines; line++)
	cali_word += *(s->hw->cal.buffer
		       + line * bytes_per_color
		       + bytes_per_color * color_seq[color] + column);
      if (!cali_word)
	cali_word = 1;
      cali_word = 256 * s->hw->cal.lines * 255 / cali_word - 256;
      if (cali_word > 255)
	cali_word = 255;
      *(cmd + sizeof (scsi_send_data) + column) = cali_word;
    }

  cmd[0] = MUSTEK_SCSI_SEND_DATA;
  cmd[2] = 1;
  cmd[6] = color + 1;
  cmd[7] = (buf_size >> 8) & 0xff;
  cmd[8] = (buf_size >> 0) & 0xff;

  status = dev_cmd (s, cmd, buf_size + sizeof (scsi_send_data), 0, 0);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "send_calibration_lines_se: send failed\n");
      return status;
    }
  free (cmd);
  return SANE_STATUS_GOOD;
}

static SANE_Status
calibration_se (Mustek_Scanner * s)
{
  SANE_Status status;

  if (!s->val[OPT_QUALITY_CAL].w || s->val[OPT_PREVIEW].w
      || s->mode == MUSTEK_MODE_LINEART)
    return SANE_STATUS_GOOD;

  DBG (4, "calibration_se: doing calibration\n");

  s->hw->cal.lines = MIN (s->hw->cal.lines,
			  s->hw->buffer_size / s->hw->cal.bytes);

  s->hw->cal.buffer = (SANE_Byte *) malloc (s->hw->cal.bytes
					    * s->hw->cal.lines);
  if (!s->hw->cal.buffer)
    {
      DBG (1, "calibration_se: failed to malloc %d bytes for buffer\n",
	   s->hw->cal.bytes * s->hw->cal.lines);
      return SANE_STATUS_NO_MEM;
    }

  status = get_calibration_lines_se (s);
  if (status != SANE_STATUS_GOOD)
    return status;

  if (s->mode == MUSTEK_MODE_GRAY)
    status = send_calibration_lines_se (s, 0);
  else
    {
      status = send_calibration_lines_se (s, 0);
      status = send_calibration_lines_se (s, 1);
      status = send_calibration_lines_se (s, 2);
    }
  if (status != SANE_STATUS_GOOD)
    return status;

  free (s->hw->cal.buffer);
  return SANE_STATUS_GOOD;
}

/* ScanExpress series */
static SANE_Status
send_gamma_table_se (Mustek_Scanner * s)
{
  SANE_Status status;
  SANE_Byte gamma[10 + 4096], *cp;
  SANE_Int color, factor, val_a, val_b;
  SANE_Int i, j;
# define CLIP(x)	((x) < 0 ? 0 : ((x) > 255 ? 255 : (x)))

  memset (gamma, 0, sizeof (scsi_send_data));

  gamma[0] = MUSTEK_SCSI_SEND_DATA;
  gamma[2] = 0x03;		/* indicates gamma table */

  if ((s->mode & MUSTEK_MODE_GRAY) || (s->mode & MUSTEK_MODE_COLOR))
    {
      if (s->hw->gamma_length + sizeof (scsi_send_data) > sizeof (gamma))
	return SANE_STATUS_NO_MEM;
      gamma[7] = (s->hw->gamma_length >> 8) & 0xff;
      gamma[8] = (s->hw->gamma_length >> 0) & 0xff;

      factor = s->hw->gamma_length / 256;
      color = (s->mode & MUSTEK_MODE_COLOR) ? 1 : 0;

      do
	{
	  gamma[6] = color;

	  if (color == 0)
	    {
	      val_a = s->gamma_table[0][1];
	      val_b = s->gamma_table[0][0];
	    }
	  else
	    {
	      /* compose intensity gamma and color channel gamma: */
	      val_a = s->gamma_table[0][s->gamma_table[color][1]];
	      val_b = s->gamma_table[0][s->gamma_table[color][0]];
	    }
	  /* Now val_a is extrapolated from [0] and [1]      */
	  val_a = MAX (2 * val_b - val_a, 0);

	  /* Interpolate first entries from 256 entry table  */
	  cp = gamma + sizeof (scsi_send_data);
	  for (j = 0; j < factor; j++)
	    *cp++ = CLIP (((factor - j) * val_a + j * val_b
			   + factor / 2) / factor);

	  for (i = 1; i < 256; i++)
	    {
	      if (color == 0)
		{
		  val_a = s->gamma_table[0][i - 1];
		  val_b = s->gamma_table[0][i];
		}
	      else
		{
		  /* compose intensity gamma and color channel gamma: */
		  val_a = s->gamma_table[0][s->gamma_table[color][i - 1]];
		  val_b = s->gamma_table[0][s->gamma_table[color][i]];
		}

	      /* Interpolate next entries from the 256 entry table */
	      for (j = 0; j < factor; j++)
		*cp++ = CLIP (((factor - j) * val_a + j * val_b
			       + factor / 2) / factor);
	    }

	  DBG (5, "send_gamma_table_se: sending table for color %d\n",
	       gamma[6]);
	  status = dev_cmd (s, gamma, sizeof (scsi_send_data)
			    + s->hw->gamma_length, 0, 0);
	  ++color;
	}
      while ((color != 1) & (color < 4) & (status == SANE_STATUS_GOOD));

      return status;
    }
  else
    {
      /* In lineart mode the threshold is encoded in byte 8 as follows */
      /* brightest -> 00 01 02 ... 7F 80 81 82 ... FF <- darkest image */
      gamma[6] = 0x04;
      gamma[8] = 128 - 127 * SANE_UNFIX (s->val[OPT_BRIGHTNESS].w) / 100.0;

      DBG (5, "send_gamma_table_se: sending lineart threshold %2X\n",
	   gamma[8]);
      return dev_cmd (s, gamma, sizeof (scsi_send_data), 0, 0);
    }
}

/* Paragon series */
static SANE_Status
mode_select_paragon (Mustek_Scanner * s, SANE_Int color_code)
{
  SANE_Int speed_code;
  SANE_Byte mode[19], *cp;

  /* calculate funky speed code: */
  for (speed_code = 0; speed_list[speed_code]; ++speed_code)
    {
      if (strcmp (speed_list[speed_code], s->val[OPT_SPEED].s) == 0)
	break;
    }
  if (speed_code > 4)
    speed_code = 4;
  else if (speed_code < 0)
    speed_code = 0;
  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
    {
      speed_code = 5 - speed_code;	/* 1 is fast, 5 is slow */
    }
  else
    {
      speed_code = 4 - speed_code;	/* 0 is fast, 4 is slow */
    }
  memset (mode, 0, sizeof (mode));
  mode[0] = MUSTEK_SCSI_MODE_SELECT;

  /* set command length and resolution code: */
  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
    {
      mode[4] = 0x0b;
      mode[7] = s->resolution_code;
    }
  else
    {
      mode[4] = 0x0d;
      cp = mode + 17;
      STORE16L (cp, s->resolution_code);
    }
  /* set mode byte: */
  mode[6] = 0x83 | (color_code << 5);
  if (!(s->hw->flags & MUSTEK_FLAG_USE_EIGHTS))
    mode[6] |= 0x08;
  if (s->custom_halftone_pattern)
    mode[6] |= 0x10;
  if (s->hw->flags & MUSTEK_FLAG_PARAGON_1)
    {
      if ((s->mode == MUSTEK_MODE_LINEART)
	  || (s->mode == MUSTEK_MODE_HALFTONE))
	{
	  mode[8] =
	    encode_percentage (s, SANE_UNFIX (s->val[OPT_BRIGHTNESS].w));
	  mode[9] =
	    encode_percentage (s, SANE_UNFIX (s->val[OPT_CONTRAST].w));
	}
      else
	{
	  mode[8] = 0x0c;
	  mode[9] = 0x0c;
	}
      mode[10] = 2;		/* grain */
      if (s->val[OPT_PREVIEW].w && s->val[OPT_FAST_PREVIEW].w)
	mode[11] = 0x01;
      else if ((s->mode == MUSTEK_MODE_COLOR)
	       || (s->mode == MUSTEK_MODE_HALFTONE))
	mode[11] = 0x00;	/* speed */
      else
	mode[11] = 0x02;	/* speed */
      mode[12] = 0x00;		/* shadow param not used by Mustek */
      mode[13] = 0xff;		/* highlight only used by some scanners */
      mode[14] = 0x70;		/* paper- */
      mode[15] = 0x00;		/* length */
      mode[16] = 0x53;		/* midtone param not used by Mustek */
    }
  else if (s->hw->flags & MUSTEK_FLAG_PARAGON_2)
    {
      mode[8] = encode_percentage (s, SANE_UNFIX (s->val[OPT_BRIGHTNESS].w));
      mode[9] = encode_percentage (s, SANE_UNFIX (s->val[OPT_CONTRAST].w));
      mode[10] = 2;		/* grain */
      if ((s->mode == MUSTEK_MODE_COLOR) || (s->mode == MUSTEK_MODE_HALFTONE))
	mode[11] = 0x00;	/* speed */
      else
	mode[11] = 0x02;	/* speed */
      mode[12] = 0x00;		/* shadow param not used by Mustek */
      mode[13] = 0x00;		/* highlight param not used by Mustek */
      mode[14] = 0x5c;		/* paper- */
      mode[15] = 0x00;		/* length */
      mode[16] = 0x41;		/* midtone param not used by Mustek */
    }
  else if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
    {
      if (s->mode & MUSTEK_MODE_COLOR)
	{
	  mode[8] = encode_percentage
	    (s, SANE_UNFIX (s->val[OPT_BRIGHTNESS + s->pass + 1].w - 1));
	  mode[9] = encode_percentage
	    (s, SANE_UNFIX (s->val[OPT_CONTRAST + s->pass + 1].w - 1));
	}
      else
	{
	  mode[8] = encode_percentage
	    (s, SANE_UNFIX (s->val[OPT_BRIGHTNESS].w - 1));
	  mode[9] = encode_percentage
	    (s, SANE_UNFIX (s->val[OPT_CONTRAST].w - 1));
	}
      mode[10] = s->halftone_pattern_type;
      mode[11] = speed_code;	/* lamp setting not supported yet */
      mode[12] = 0;		/* shadow param not used by Mustek */
      mode[13] = 0;		/* highlight param not used by Mustek */
      mode[14] = mode[15] = 0;	/* paperlength not used by Mustek */
      mode[16] = 0;		/* midtone param not used by Mustek */
    }
  else
    {
      mode[8] = encode_percentage (s, SANE_UNFIX (s->val[OPT_BRIGHTNESS].w));
      mode[9] = encode_percentage (s, SANE_UNFIX (s->val[OPT_CONTRAST].w));
      mode[10] = s->halftone_pattern_type;
      mode[11] = speed_code;	/* lamp setting not supported yet */
      mode[12] = 0;		/* shadow param not used by Mustek */
      mode[13] = 0;		/* highlight param not used by Mustek */
      mode[14] = mode[15] = 0;	/* paperlength not used by Mustek */
      mode[16] = 0;		/* midtone param not used by Mustek */
    }

  DBG (5, "mode_select: resolution_code=%d (0x%x)\n", s->resolution_code,
       s->resolution_code);
  return dev_cmd (s, mode, 6 + mode[4], 0, 0);
}

/* Pro series */
static SANE_Status
mode_select_pro (Mustek_Scanner * s)
{
  SANE_Byte mode[19], *cp;

  memset (mode, 0, sizeof (mode));

  mode[0] = MUSTEK_SCSI_MODE_SELECT;
  mode[4] = 0x0d;

  if (s->mode & MUSTEK_MODE_COLOR)
    {
      if (strcmp (s->val[OPT_BIT_DEPTH].s, "12") == 0)
	mode[6] = 0xE0;
      else
	mode[6] = 0x60;
    }
  else if (s->mode & MUSTEK_MODE_GRAY)
    {
      if (s->val[OPT_FAST_GRAY_MODE].w)
	mode[6] = 0x20;
      else
	mode[6] = 0x40;
    }
  else
    mode[6] = 0x00;		/* lineart */

  mode[7] = 0;
  mode[8] = 0;
  mode[9] = 0;
  mode[10] = 0;
  mode[11] = 0x00;
  mode[12] = 0x27;
  mode[13] = 0xb0;
  mode[14] = 0x04;
  mode[15] = 0x43;
  mode[16] = 0x41;

  cp = mode + 17;
  STORE16L (cp, s->resolution_code);

  DBG (5, "mode_select_pro: resolution_code=%d (0x%x), mode=0x%x\n",
       s->resolution_code, s->resolution_code, mode[6]);
  return dev_cmd (s, mode, 6 + mode[4], 0, 0);
}

/* Paragon and Pro series. According to Mustek, the only builtin gamma
   table is a linear table, so all we support here is user-defined
   gamma tables.  */
static SANE_Status
gamma_correction (Mustek_Scanner * s, SANE_Int color_code)
{
  SANE_Int i, j, table = 0, len = 0, bytes_per_channel, num_channels = 1;
  SANE_Byte gamma[4096 + 10], val, *cp;	/* for Paragon models 3 x 256 is the
					   maximum. Pro needs 4096 bytes */

  if ((s->hw->flags & MUSTEK_FLAG_N)
      && ((s->mode & MUSTEK_MODE_LINEART)
	  || (s->mode & MUSTEK_MODE_HALFTONE)))
    {
      /* sigh! - the 600 II N needs a (dummy) table download even for
         lineart and halftone mode, else it produces a completely
         white image. Thank Mustek for their buggy firmware !  */
      memset (gamma, 0, sizeof (gamma));
      gamma[0] = MUSTEK_SCSI_LOOKUP_TABLE;
      gamma[2] = 0x0;		/* indicate any preloaded gamma table */
      DBG (5, "gamma_correction: sending dummy gamma table\n");
      return dev_cmd (s, gamma, 6, 0, 0);
    }

  if (((s->mode & MUSTEK_MODE_LINEART) || (s->mode & MUSTEK_MODE_HALFTONE))
      && !(s->hw->flags & MUSTEK_FLAG_PRO))
    {
      DBG (5, "gamma_correction: nothing to do in lineart mode -- exiting\n");
      return SANE_STATUS_GOOD;
    }

  if ((!s->val[OPT_CUSTOM_GAMMA].w) && (!(s->hw->flags & MUSTEK_FLAG_PRO)))
    {
      /* Do we need to upload a gamma table even if the user didn't select
         this option? Some scanners need this work around. */
      if (!(s->hw->flags & MUSTEK_FLAG_FORCE_GAMMA) ||
	  !((s->mode & MUSTEK_MODE_COLOR) || (s->mode & MUSTEK_MODE_GRAY)))
	{
	  DBG (5,
	       "gamma_correction: no custom table selected -- exititing\n");
	  return SANE_STATUS_GOOD;
	}
    }

  if (s->mode & MUSTEK_MODE_COLOR)
    {
      table = 1;
      if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	table += s->pass;
      else
	{
	  if ((color_code == MUSTEK_CODE_GRAY)
	      && !(s->hw->flags & MUSTEK_FLAG_PRO))
	    num_channels = 3;
	  else
	    table = color_code;
	}
    }
  else if (s->hw->flags & MUSTEK_FLAG_N)
    {
      /* it seems 600 II N (firmware 1.x at least) wants 768 bytes in
       * gray mode too */
      num_channels = 3;
    }

  memset (gamma, 0, sizeof (gamma));
  gamma[0] = MUSTEK_SCSI_LOOKUP_TABLE;

  if (s->hw->flags & MUSTEK_FLAG_PRO)
    {
      bytes_per_channel = 4096;
      len = bytes_per_channel;
      if (s->mode == MUSTEK_MODE_COLOR)
	{
	  gamma[9] = (color_code << 6);	/* color */
	  if (strcmp (s->val[OPT_BIT_DEPTH].s, "12") == 0)
	    gamma[2] = 0x7f;	/* medium brightness */
	}
      else if (s->mode == MUSTEK_MODE_GRAY)
	{
	  gamma[9] = 0x80;	/* grayscale */
	  if (s->val[OPT_FAST_GRAY_MODE].w)
	    gamma[2] = 0x7f;	/* medium brightness */
	}
      else			/* lineart */
	{
	  gamma[2] =
	    128 - 127 * SANE_UNFIX (s->val[OPT_BRIGHTNESS].w) / 100.0;
	  gamma[9] = 0x80;	/* grayscale/lineart */
	  DBG (5, "gamma_correction: sending brightness information\n");
	}
      gamma[7] = (len >> 8) & 0xff;	/* big endian! */
      gamma[8] = (len >> 0) & 0xff;
    }
  else
    {
      bytes_per_channel = 256;
      len = num_channels * bytes_per_channel;
      gamma[2] = 0x27;		/* indicate user-selected gamma table */
      if (s->hw->flags & MUSTEK_FLAG_N)
	{
	  /* 600 II N always uses 6-byte cdb */
	  gamma[3] = (len >> 8) & 0xff;	/* big endian! */
	  gamma[4] = (len >> 0) & 0xff;
	  /* no way to pass color_code (?) */
	}
      else
	{
	  gamma[7] = (len >> 8) & 0xff;	/* big endian! */
	  gamma[8] = (len >> 0) & 0xff;
	  gamma[9] = (color_code << 6);
	}
    }

  if (len > 0)
    {
      cp = gamma + 10;
      for (j = 0; j < num_channels; ++j)
	{
	  for (i = 0; i < bytes_per_channel; ++i)
	    {
	      if (s->val[OPT_CUSTOM_GAMMA].w == SANE_TRUE)
		val = s->gamma_table[table][i * 256 / bytes_per_channel];
	      else
		val = i * 256 / bytes_per_channel;
	      if ((s->mode & MUSTEK_MODE_COLOR)
		  && (s->val[OPT_CUSTOM_GAMMA].w == SANE_TRUE))
		/* compose intensity gamma and color channel gamma: */
		val = s->gamma_table[0][val];
	      *cp++ = val;
	    }
	  if (!(s->hw->flags & MUSTEK_FLAG_N)
	      || !(s->mode & MUSTEK_MODE_GRAY))
	    table++;
	}
    }
  DBG (5, "gamma_correction: sending gamma table of %d bytes\n", len);
  return dev_cmd (s, gamma, 10 + len, 0, 0);
}

static SANE_Status
send_gamma_table (Mustek_Scanner * s)
{
  SANE_Status status;

  if (s->one_pass_color_scan)
    {
      if (s->hw->flags & MUSTEK_FLAG_N)
	/* This _should_ work for all one-pass scanners (not just
	   AB306N scanners), but it doesn't work for my Paragon
	   600 II SP with firmware rev 1.01.  Too bad, since it would
	   simplify the gamma correction code quite a bit.  */
	status = gamma_correction (s, MUSTEK_CODE_GRAY);
      else
	{
	  status = gamma_correction (s, MUSTEK_CODE_RED);
	  if (status != SANE_STATUS_GOOD)
	    return status;

	  status = gamma_correction (s, MUSTEK_CODE_GREEN);
	  if (status != SANE_STATUS_GOOD)
	    return status;

	  status = gamma_correction (s, MUSTEK_CODE_BLUE);
	}
    }
  else
    status = gamma_correction (s, MUSTEK_CODE_GRAY);
  return status;
}


/* ScanExpress and Paragon series */
static SANE_Status
start_scan (Mustek_Scanner * s)
{
  SANE_Byte start[6];
  SANE_Status status;

  memset (start, 0, sizeof (start));
  start[0] = MUSTEK_SCSI_START_STOP;
  start[4] = 0x01;

  DBG (4, "start_scan\n");
  /* ScanExpress and Pro models don't have any variants */
  if (!(s->hw->flags & MUSTEK_FLAG_SE) && !(s->hw->flags & MUSTEK_FLAG_PRO))
    {
      if (s->mode & MUSTEK_MODE_COLOR)
	{
	  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	    start[4] |= ((s->pass + 1) << 3);
	  else
	    start[4] |= 0x20;
	}
      /* or in single/multi bit: */
      start[4] |= ((s->mode & MUSTEK_MODE_LINEART)
		   || (s->mode & MUSTEK_MODE_HALFTONE)) ? 0 : (1 << 6);

      /* or in expanded resolution bit: */
      if (s->val[OPT_RESOLUTION].w > (s->hw->dpi_range.max / 2)
	  && ((s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	      || (s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	      || (s->hw->flags & MUSTEK_FLAG_PARAGON_2)))
	start[4] |= 1 << 7;

      /* block mode (or whatever) */
      if (s->hw->flags & MUSTEK_FLAG_USE_BLOCK)
	{
	  start[5] = 0x08;
	  DBG (4, "start_scan: using block mode\n");
	}
    }

  status = dev_cmd (s, start, sizeof (start), 0, 0);
  if (status != SANE_STATUS_GOOD)
    DBG (1, "start_scan returned status %s\n", sane_strstatus (status));
  return status;
}

static SANE_Status
do_eof (Mustek_Scanner * s)
{
  if (s->pipe >= 0)
    {
      close (s->pipe);
      s->pipe = -1;
      DBG (5, "do_eof: closing pipe\n");
    }
  return SANE_STATUS_EOF;
}

static SANE_Status
do_stop (Mustek_Scanner * s)
{
  SANE_Status status = SANE_STATUS_GOOD;

  DBG (5, "do_stop\n");

  if (s->cancelled)
    status = SANE_STATUS_CANCELLED;

  s->scanning = SANE_FALSE;
  s->pass = 0;

  if (sanei_thread_is_valid (s->reader_pid))
    {
      SANE_Int exit_status;
      struct timeval now;
      long int scan_time;
      long int scan_size;
      SANE_Pid pid;

      /* print scanning time */
      gettimeofday (&now, 0);
      scan_time = now.tv_sec - s->start_time;
      if (scan_time < 1)
	scan_time = 1;
      scan_size = s->hw->bpl * s->hw->lines / 1024;
      DBG (2, "Scanning time was %ld seconds, %ld kB/s\n", scan_time,
	   scan_size / scan_time);

      if (s->total_bytes == s->params.lines * s->params.bytes_per_line)
	DBG (3, "Scanned %d bytes as expected\n", s->total_bytes);
      else if (s->total_bytes < s->params.lines * s->params.bytes_per_line)
	DBG (3, "Scanned %d bytes, expected %d bytes\n", s->total_bytes,
	     s->params.lines * s->params.bytes_per_line);
      else
	DBG (1, "Warning: Scanned %d bytes, but expected only %d bytes\n",
	     s->total_bytes, s->params.lines * s->params.bytes_per_line);

      /* ensure child knows it's time to stop: */
      DBG (5, "do_stop: terminating reader process\n");
      sanei_thread_kill (s->reader_pid);

      pid = sanei_thread_waitpid (s->reader_pid, &exit_status);
      if (!sanei_thread_is_valid (pid))
	{
	  DBG (1,
	       "do_stop: sanei_thread_waitpid failed, already terminated? (%s)\n",
	       strerror (errno));
	}
      else
	{
	  DBG (2, "do_stop: reader process terminated with status %s\n",
	       sane_strstatus (exit_status));
	  if (status != SANE_STATUS_CANCELLED
	      && exit_status != SANE_STATUS_GOOD)
	    status = exit_status;
	}

      sanei_thread_invalidate (s->reader_pid);
    }

  if (s->fd >= 0)
    {
      if (!sanei_thread_is_forked ())
	sanei_scsi_req_flush_all ();	/* flush SCSI queue */

      if (s->hw->flags & MUSTEK_FLAG_PRO)
	{
	  if (s->total_bytes < s->params.lines * s->params.bytes_per_line)
	    status = dev_cmd (s, scsi_start_stop, sizeof (scsi_start_stop),
			      0, 0);
	  dev_wait_ready (s);
	}
      else if ((s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	       || (s->hw->flags & MUSTEK_FLAG_PARAGON_2)
	       || (s->hw->flags & MUSTEK_FLAG_THREE_PASS))
	{
	  if (s->cancelled &&
	      (s->total_bytes < s->params.lines * s->params.bytes_per_line))
	    status = dev_cmd (s, scsi_start_stop, sizeof (scsi_start_stop),
			      0, 0);
	}
      else
	status = dev_cmd (s, scsi_start_stop, sizeof (scsi_start_stop), 0, 0);

      if (force_wait)
	{
	  DBG (5, "do_stop: waiting for scanner to be ready\n");
	  dev_wait_ready (s);
	}

      do_eof (s);
      DBG (5, "do_stop: closing scanner\n");
      dev_close (s);
      s->fd = -1;
    }

  DBG (5, "do_stop: finished\n");
  return status;
}

/* Paragon I + II: Determine the CCD's distance between the primary color
   lines.  */
static SANE_Status
line_distance (Mustek_Scanner * s)
{
  SANE_Int factor, color, res, peak_res;
  SANE_Status status;
  SANE_Byte result[5];
  size_t len;

  memset (result, 0, 5);

  res = SANE_UNFIX (s->val[OPT_RESOLUTION].w) + 0.5;
  peak_res = SANE_UNFIX (s->hw->dpi_range.max) + 0.5;

  s->ld.buf[0] = NULL;

  len = sizeof (result);
  status = dev_cmd (s, scsi_ccd_distance, sizeof (scsi_ccd_distance),
		    result, &len);
  if (status != SANE_STATUS_GOOD)
    return status;

  DBG (3, "line_distance: got factor=%d, (r/g/b)=(%d/%d/%d)\n",
       result[0] | (result[1] << 8), result[2], result[3], result[4]);

  if (s->hw->flags & MUSTEK_FLAG_LD_FIX)
    {
      result[0] = 0xff;
      result[1] = 0xff;
      if (s->mode & MUSTEK_MODE_COLOR)
	{
	  if (s->hw->flags & MUSTEK_FLAG_N)
	    {
	      /* According to Andreas Czechanowski, the line-distance values
		 returned for the AB306N scanners are garbage, so we have to
		 fix things up manually.  Not good.
		 This seems to be true only for firmware 2.00 which is
		 extremely seldom.. AB306N scanners with firmware 1.01 don't
		 need this fix. <henning@meier-geinitz.de> */
	      if (peak_res == 600)
		{
		  if (res < 51)
		    {
		      result[0] = 8;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 2;
		      result[4] = 3;
		    }
		  else if (res < 75 || (res > 90 && res < 150))
		    {
		      /* 51-74 and 91-149 dpi: */
		      result[0] = 4;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 3;
		      result[4] = 5;
		    }
		  else if (res <= 90 || (res >= 150 && res <= 300))
		    {
		      /* 75-90 and 150-300 dpi: */
		      result[0] = 2;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 5;
		      result[4] = 9;
		    }
		  else
		    {
		      /* 301-600 dpi: */
		      result[0] = 1;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 9;
		      result[4] = 23;
		    }
		}
	      else
		DBG (1, "don't know how to fix up line-distance for %d dpi\n",
		     peak_res);
	    }
	  else if (!(s->hw->flags & MUSTEK_FLAG_LD_NONE))
	    {
	      if (peak_res == 600)
		{
		  if (res < 51)
		    {
		      /* 1-50 dpi: */
		      result[0] = 4;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 3;
		      result[4] = 5;
		    }
		  else if (res <= 300)
		    {
		      /* 51-300 dpi: */
		      result[0] = 2;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 5;
		      result[4] = 9;
		    }
		  else
		    {
		      /*301-600 dpi: */
		      result[0] = 1;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 9;
		      result[4] = 17;
		    }
		}
	      else if (peak_res == 800)
		{
		  if (res < 72)
		    {
		      /* 1-71 dpi: */
		      result[0] = 4;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 3;
		      result[4] = 5;
		    }
		  else if (res <= 400)
		    {
		      /* 72-400 dpi: */
		      result[0] = 2;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 9;
		      result[4] = 17;
		    }
		  else
		    {
		      /*401-800 dpi: */
		      result[0] = 1;
		      result[1] = 0;
		      result[2] = 0;
		      result[3] = 16;
		      result[4] = 32;
		    }
		}
	    }
	}
      DBG (4, "line_distance: fixed up to factor=%d, (r/g/b)=(%d/%d/%d)\n",
	   result[0] | (result[1] << 8), result[2], result[3], result[4]);
    }

  factor = result[0] | (result[1] << 8);
  if (factor != 0xffff)
    {
      /* need to do line-distance adjustment ourselves... */

      s->ld.max_value = peak_res;

      if (factor == 0)
	{
	  if (res <= peak_res / 2)
	    res *= 2;
	}
      else
	res *= factor;
      s->ld.peak_res = res;
      for (color = 0; color < 3; ++color)
	{
	  s->ld.dist[color] = result[2 + color];
	  s->ld.quant[color] = s->ld.max_value;
	  s->ld.index[color] = -s->ld.dist[color];
	}
      s->ld.lmod3 = -1;

      DBG (4, "line_distance: max_value = %d, peak_res = %d, ld.quant = "
	   "(%d, %d, %d)\n", s->ld.max_value, s->ld.peak_res, s->ld.quant[0],
	   s->ld.quant[1], s->ld.quant[2]);
    }
  else
    s->ld.max_value = 0;

  return SANE_STATUS_GOOD;
}

/* Paragon + Pro series */
static SANE_Status
get_image_status (Mustek_Scanner * s, SANE_Int * bpl, SANE_Int * lines)
{
  SANE_Byte result[6];
  SANE_Status status;
  size_t len;
  SANE_Int busy, offset;
  long res, half_res;

  memset (result, 0, 6);

  /* The 600 II N v1.01 and Paragon 12000SP need a larger scan-area for
     line-distance correction in color mode */
  offset = 0;
  if ((s->hw->flags & MUSTEK_FLAG_LD_N1) && (s->mode & MUSTEK_MODE_COLOR))
    offset = s->ld.dist[1];
  else if ((s->hw->flags & MUSTEK_FLAG_LD_BLOCK)
	   && (s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	   && (s->mode & MUSTEK_MODE_COLOR))
    offset = MAX_LINE_DIST * SANE_UNFIX (s->val[OPT_RESOLUTION].w)
      / SANE_UNFIX (s->hw->dpi_range.max);

  do
    {
      len = sizeof (result);
      status = dev_cmd (s, scsi_get_image_status,
			sizeof (scsi_get_image_status), result, &len);
      if (status != SANE_STATUS_GOOD)
	return status;

      busy = result[0];
      if (busy)
	usleep (100000);

      if (!s->scanning)		/* ? */
	if (!(s->hw->flags & MUSTEK_FLAG_PRO))
	  return do_stop (s);
    }
  while (busy);

  s->hw->bpl = result[1] | (result[2] << 8);
  s->hw->lines = result[3] | (result[4] << 8) | (result[5] << 16);

  res = SANE_UNFIX (s->val[OPT_RESOLUTION].w);
  half_res = SANE_UNFIX (s->hw->dpi_range.max) / 2;
  /* Need to interpolate resolutions > max x-resolution? */
  if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
    {
      *bpl = (s->hw->bpl * res) / half_res / 3;
      *bpl *= 3;
      DBG (4, "get_image_status: resolution > x-max; enlarge %d bpl to "
	   "%d bpl\n", s->hw->bpl, *bpl);
    }
  else
    *bpl = s->hw->bpl;

  *lines = s->hw->lines - offset;

  DBG (3, "get_image_status: bytes_per_line=%d, lines=%d (offset = %d)\n",
       *bpl, *lines, offset);
  return SANE_STATUS_GOOD;
}

/* ScanExpress models */
static SANE_Status
get_window (Mustek_Scanner * s, SANE_Int * bpl, SANE_Int * lines,
	    SANE_Int * pixels)
{
  SANE_Byte result[48];
  SANE_Status status;
  size_t len;
  SANE_Int color;
  long res, half_res;

  res = s->resolution_code;
  half_res = SANE_UNFIX (s->hw->dpi_range.max) / 2;

  DBG (5, "get_window: resolution: %ld dpi (hardware: %d dpi)\n",
       res, s->ld.peak_res);

  len = sizeof (result);
  status = dev_cmd (s, scsi_get_window, sizeof (scsi_get_window), result,
		    &len);
  if (status != SANE_STATUS_GOOD)
    return status;

  if (!s->scanning)
    return do_stop (s);

  s->hw->cal.bytes = (result[6] << 24) | (result[7] << 16) |
    (result[8] << 8) | (result[9] << 0);
  s->hw->cal.lines = (result[10] << 24) | (result[11] << 16) |
    (result[12] << 8) | (result[13] << 0);

  DBG (4, "get_window: calibration bpl=%d, lines=%d\n",
       s->hw->cal.bytes, s->hw->cal.lines);

  s->hw->bpl = (result[14] << 24) | (result[15] << 16) |
    (result[16] << 8) | result[17];

  s->hw->lines = (result[18] << 24) | (result[19] << 16) |
    (result[20] << 8) | result[21];

  DBG (4, "get_window: scan bpl=%d, lines=%d\n", s->hw->bpl, s->hw->lines);

  if ((s->hw->cal.bytes == 0) || (s->hw->cal.lines == 0)
      || (s->hw->bpl == 0) || (s->hw->lines == 0))
    {
      DBG (1, "get_window: oops, none of these values should be 0 "
	   "-- exiting\n");
      return SANE_STATUS_INVAL;
    }

  s->hw->gamma_length = 1 << result[26];
  DBG (4, "get_window: gamma length=%d\n", s->hw->gamma_length);

  if (s->mode & MUSTEK_MODE_COLOR)
    {
      s->ld.buf[0] = NULL;
      for (color = 0; color < 3; ++color)
	{
	  s->ld.dist[color] = result[42 + color];
	}

      DBG (4, "get_window: LD res=%d, (r/g/b)=(%d/%d/%d)\n",
	   (result[40] << 8) | result[41], s->ld.dist[0],
	   s->ld.dist[1], s->ld.dist[2]);
      s->ld.max_value = (result[40] << 8) | result[41];
      if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
	{
	  /* We must interpolate resolutions > max x-resolution */
	  *bpl = *pixels = (((s->hw->bpl / 3) * res) / half_res) * 3;
	}
      else
	{
	  /* Scale down the image according to desired resolution */
	  *bpl = *pixels = (((s->hw->bpl / 3) * res) / s->ld.peak_res) * 3;
	}
      *lines = (s->hw->lines - s->ld.dist[2]) * res / s->ld.peak_res;
    }
  else
    {
      if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
	{
	  /* We must interpolate resolutions > max x-resolution */
	  *bpl = s->hw->bpl * res / half_res;
	}
      else
	{
	  *bpl = s->hw->bpl;
	}
      *lines = s->hw->lines;
    }
  DBG (4, "get_window: bpl = %d (hardware: %d), lines = %d (hardware: %d)\n",
       *bpl, s->hw->bpl, *lines, s->hw->lines);
  return SANE_STATUS_GOOD;
}

static SANE_Status
adf_and_backtrack (Mustek_Scanner * s)
{
  SANE_Byte backtrack[6];
  SANE_Int code = 0x80;

  if (!(s->hw->flags & MUSTEK_FLAG_NO_BACKTRACK))
    code |= 0x02;		/* enable backtracking */

  if (strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0)
    code |= 0x01;
  else if (strcmp (s->val[OPT_SOURCE].s, "Transparency Adapter") == 0)
    code |= 0x04;
  memset (backtrack, 0, sizeof (backtrack));
  backtrack[0] = MUSTEK_SCSI_ADF_AND_BACKTRACK;
  backtrack[4] = code;

  DBG (4, "adf_and_backtrack: backtrack: %s; ADF: %s; TA: %s\n",
       code & 0x02 ? "yes" : "no", code & 0x01 ? "yes" : "no",
       code & 0x04 ? "yes" : "no");
  return dev_cmd (s, backtrack, sizeof (backtrack), 0, 0);
}

/* 600 II N firmware 2.x */
static SANE_Int
fix_line_distance_n_2 (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
		       SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *out_end, *out_ptr, *raw_end = raw + num_lines * bpl;
  SANE_Int c, num_saved_lines, line;

  if (!s->ld.buf[0])
    {
      /* This buffer must be big enough to hold maximum line distance
         times max_bpl bytes.  The maximum line distance for the
         Paragon 600 II N scanner is 23, so 40 should be safe.  */
      DBG (5,
	   "fix_line_distance_n_2: allocating temp buffer of %d*%d bytes\n",
	   MAX_LINE_DIST, bpl);
      s->ld.buf[0] = malloc (MAX_LINE_DIST * (long) bpl);
      if (!s->ld.buf[0])
	{
	  DBG (1,
	       "fix_line_distance_n_2: failed to malloc temporary buffer\n");
	  return 0;
	}
    }

  num_saved_lines = s->ld.index[0] - s->ld.index[2];
  if (num_saved_lines > 0)
    /* restore the previously saved lines: */
    memcpy (out, s->ld.buf[0], num_saved_lines * bpl);

  while (1)
    {
      if (++s->ld.lmod3 >= 3)
	s->ld.lmod3 = 0;

      c = color_seq[s->ld.lmod3];
      if (s->ld.index[c] < 0)
	++s->ld.index[c];
      else if (s->ld.index[c] < s->params.lines)
	{
	  s->ld.quant[c] += s->ld.peak_res;
	  if (s->ld.quant[c] > s->ld.max_value)
	    {
	      s->ld.quant[c] -= s->ld.max_value;
	      line = s->ld.index[c]++ - s->ld.ld_line;
	      out_ptr = out + line * bpl + c;
	      out_end = out_ptr + bpl;
	      while (out_ptr != out_end)
		{
		  *out_ptr = *raw++;
		  out_ptr += 3;
		}

	      if (raw >= raw_end)
		{
		  DBG (3, "fix_line_distance_n_2: lmod3=%d, "
		       "index=(%d,%d,%d)\n", s->ld.lmod3,
		       s->ld.index[0], s->ld.index[1], s->ld.index[2]);
		  num_lines = s->ld.index[2] - s->ld.ld_line;

		  /* copy away the lines with at least one missing
		     color component, so that we can interleave them
		     with new scan data on the next call */
		  num_saved_lines = s->ld.index[0] - s->ld.index[2];
		  memcpy (s->ld.buf[0], out + num_lines * bpl,
			  num_saved_lines * bpl);

		  /* notice the number of lines we processed */
		  s->ld.ld_line = s->ld.index[2];
		  /* return number of complete (r+g+b) lines */
		  return num_lines;
		}
	    }
	}
    }
}

/* 600 II N firmware 1.x */
static SANE_Int
fix_line_distance_n_1 (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
		       SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *out_end, *out_ptr, *raw_end = raw + num_lines * bpl;
  SANE_Int c, num_saved_lines, line;

  /* For firmware 1.x the scanarea must be soemwhat bigger than needed
     because of the linedistance correction */

  if (!s->ld.buf[0])
    {
      /* This buffer must be big enough to hold maximum line distance
         times max_bpl bytes.  The maximum line distance for the 600 II N
         is 23, so 40 is safe. */
      DBG (5,
	   "fix_line_distance_n_1: allocating temp buffer of %d*%d bytes\n",
	   MAX_LINE_DIST, bpl);
      s->ld.buf[0] = malloc (MAX_LINE_DIST * (long) bpl);
      if (!s->ld.buf[0])
	{
	  DBG (1,
	       "fix_line_distance_n_1: failed to malloc temporary buffer\n");
	  return 0;
	}
    }
  num_saved_lines = s->ld.index[0] - s->ld.index[1];
  DBG (5, "fix_line_distance_n_1: got %d lines, %d bpl\n", num_lines, bpl);
  DBG (5, "fix_line_distance_n_1: num_saved_lines = %d; peak_res = %d; "
       "max_value = %d\n", num_saved_lines, s->ld.peak_res, s->ld.max_value);
  if (num_saved_lines > 0)
    /* restore the previously saved lines: */
    memcpy (out, s->ld.buf[0], num_saved_lines * bpl);

  while (1)
    {
      if (++s->ld.lmod3 >= 3)
	s->ld.lmod3 = 0;
      c = s->ld.lmod3;
      if (s->ld.index[c] < 0)
	++s->ld.index[c];
      else
	{
	  s->ld.quant[c] += s->ld.peak_res;
	  if (s->ld.quant[c] > s->ld.max_value)
	    {
	      s->ld.quant[c] -= s->ld.max_value;
	      line = s->ld.index[c]++ - s->ld.ld_line;
	      out_ptr = out + line * bpl + c;
	      out_end = out_ptr + bpl;
	      while (out_ptr != out_end)
		{
		  *out_ptr = *raw++;
		  out_ptr += 3;
		}
	      DBG (5, "fix_line_distance_n_1: copied line %d (color %d)\n",
		   line, c);
	    }
	}
      if ((raw >= raw_end) || ((s->ld.index[0] >= s->params.lines) &&
			       (s->ld.index[1] >= s->params.lines) &&
			       (s->ld.index[2] >= s->params.lines)))
	{
	  DBG (3, "fix_line_distance_n_1: lmod3=%d, index=(%d,%d,%d)%s\n",
	       s->ld.lmod3,
	       s->ld.index[0], s->ld.index[1], s->ld.index[2],
	       raw >= raw_end ? " raw >= raw_end" : "");
	  num_lines = s->ld.index[1] - s->ld.ld_line;
	  if (num_lines < 0)
	    num_lines = 0;
	  DBG (4, "fix_line_distance_n_1: lines ready: %d\n", num_lines);

	  /* copy away the lines with at least one missing
	     color component, so that we can interleave them
	     with new scan data on the next call */
	  num_saved_lines = s->ld.index[0] - s->ld.index[1];
	  DBG (4, "fix_line_distance_n_1: copied %d lines to "
	       "ld.buf\n", num_saved_lines);
	  memcpy (s->ld.buf[0], out + num_lines * bpl, num_saved_lines * bpl);
	  /* notice the number of lines we processed */
	  s->ld.ld_line = s->ld.index[1];
	  if (s->ld.ld_line < 0)
	    s->ld.ld_line = 0;
	  /* return number of complete (r+g+b) lines */
	  return num_lines;
	}

    }
}

/* For ScanExpress models */
static SANE_Int
fix_line_distance_se (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
		      SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *raw_end = raw + num_lines * bpl;
  SANE_Byte *out_ptr[3], *ptr;
  SANE_Int index[3], lines[3], quant[3], dist[3];
  SANE_Int max_value;
  SANE_Int color, pixel, res, half_res, scale;
  SANE_Int bpc = bpl / 3;	/* bytes per color (per line) */
  SANE_Bool preview = SANE_FALSE;

  res = s->resolution_code;
  half_res = SANE_UNFIX (s->hw->dpi_range.max) / 2;
  max_value = s->ld.max_value;
  if ((s->val[OPT_PREVIEW].w == SANE_TRUE)
      && (s->val[OPT_FAST_PREVIEW].w == SANE_TRUE))
    {
      preview = SANE_TRUE;
      /*max_value = 75; */
      dist[0] = s->ld.dist[0];
      dist[1] = s->ld.dist[1];
      dist[2] = s->ld.dist[2];
    }
  else
    {
      dist[0] = s->ld.dist[0];
      dist[1] = s->ld.dist[1];
      dist[2] = s->ld.dist[2];
    }

  if (!s->ld.buf[0])
    {
      /* This buffer must be big enough to hold maximum line distance times
         3*bpl bytes.  The maximum line distance for 1200 dpi is 32 */
      DBG (5, "fix_line_distance_se: allocating temp buffer of %d*%d bytes\n",
	   3 * MAX_LINE_DIST, bpc);
      s->ld.buf[0] = malloc (3 * MAX_LINE_DIST * (long) bpc);

      if (!s->ld.buf[0])
	{
	  DBG (1,
	       "fix_line_distance_se: failed to malloc temporary buffer\n");
	  return 0;
	}

      /* Note that either s->ld.buf[1] or  s->ld.buf[2] is never used. */
      s->ld.buf[1] = s->ld.buf[2] =
	s->ld.buf[0] + 2 * MAX_LINE_DIST * (long) bpc;

      /* Since the blocks don't start necessarily with red note color. */
      s->ld.color = 0;

      /* The scan area must be longer than desired because of the line
         distance. So me must count complete (r+g+b) lines already
         submitted to the fronted. */
      s->ld.ld_line = s->params.lines;

      for (color = 0; color < 3; ++color)
	{
	  s->ld.index[color] = -dist[color];
	  s->ld.quant[color] = 0;
	  s->ld.saved[color] = 0;
	}
    }

  num_lines *= 3;
  DBG (5, "fix_line_distance_se: start color: %d; %d lines \n",
       s->ld.color, num_lines);

  /* First scan the lines read and count red, green and blue ones.
     Since we will step through the lines a second time we must not
     alter any global variables here! */
  for (color = 0; color < 3; ++color)
    {
      index[color] = s->ld.index[color];
      lines[color] = s->ld.saved[color];
      quant[color] = s->ld.quant[color];
    }

  color = s->ld.color;
  while (num_lines > 0)
    {
      if (index[color] < 0)
	++index[color];
      else
	{
	  quant[color] += res;
	  if (quant[color] >= max_value)
	    {
	      /* This line must be processed, not dropped. */
	      quant[color] -= max_value;
	      ++lines[color];
	      --num_lines;
	    }
	  else if (!preview)
	    --num_lines;

	}
      if (++color > 2)
	color = 0;
    }

  /* Calculate how many triples of color lines we can output now.
     Because the number of available red lines is always greater
     than for the other colors we may ignore the red ones here. */
  num_lines = MIN (lines[1], lines[2]);

  DBG (5, "fix_line_distance_se: saved lines: %d/%d/%d\n", s->ld.saved[0],
       s->ld.saved[1], s->ld.saved[2]);
  DBG (5, "fix_line_distance_se: available:  %d/%d/%d --> triples: %d\n",
       lines[0], lines[1], lines[2], num_lines);

  lines[0] = lines[1] = lines[2] = num_lines;

  /* Output the color lines saved in previous call first.
     Note that data is converted in r/g/b interleave on the fly. */
  for (color = 0; color < 3; ++color)
    {
      out_ptr[color] = out + color;
      ptr = s->ld.buf[color];
      while ((s->ld.saved[color] > 0) && (lines[color] > 0))
	{
	  scale = 0;
	  if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
	    {
	      /* Need to enlarge x-resolution */
	      SANE_Byte *ptr_start = ptr;
	      for (pixel = 0; pixel < s->params.pixels_per_line; ++pixel)
		{
		  *out_ptr[color] = *ptr;
		  out_ptr[color] += 3;
		  scale += half_res;
		  if (scale >= half_res)
		    {
		      scale -= res;
		      ++ptr;
		    }
		}
	      DBG (5, "fix_line_distance_se: got saved line: %d; line: %d; "
		   "color: %d; raw bytes: %lu; out bytes: %d\n",
		   s->ld.saved[color], lines[color], color, (u_long) (ptr - ptr_start),
		   s->params.pixels_per_line);
	      ptr = ptr_start + bpc;
	    }
	  else
	    {
	      if (preview)
		{
		  for (pixel = 0; pixel < bpc; ++pixel)
		    {
		      *out_ptr[color] = *ptr++;
		      out_ptr[color] += 3;
		    }
		}
	      else
		{
		  for (pixel = 0; pixel < bpc; ++pixel)
		    {
		      scale += res;
		      if (scale >= max_value)
			{
			  scale -= max_value;
			  *out_ptr[color] = *ptr;
			  out_ptr[color] += 3;
			}
		      ++ptr;
		    }
		}
	      DBG (5, "fix_line_distance_se: got saved line: %d; line: %d; "
		   "color: %d\n", s->ld.saved[color], lines[color], color);
	    }
	  --(s->ld.saved[color]);
	  --lines[color];
	}
      if (s->ld.saved[color] > 0)
	memmove (s->ld.buf[color], ptr, s->ld.saved[color] * bpc);
    }

  while (1)
    {
      if (s->ld.index[s->ld.color] < 0)
	++(s->ld.index[s->ld.color]);
      else
	{
	  s->ld.quant[s->ld.color] += res;
	  if (s->ld.quant[s->ld.color] >= max_value)
	    {
	      /* This line must be processed, not dropped. */
	      s->ld.quant[s->ld.color] -= max_value;

	      if (lines[s->ld.color] > 0)
		{
		  /* There's still a line to be output for current color.
		     Then shuffle current color line to output buffer. */
		  scale = 0;
		  /* need to enlarge x-resolution? */
		  if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X)
		      && (res > half_res))
		    {
		      SANE_Byte *raw_start = raw;
		      for (pixel = 0; pixel < s->params.pixels_per_line;
			   ++pixel)
			{
			  *out_ptr[s->ld.color] = *raw;
			  out_ptr[s->ld.color] += 3;
			  scale += half_res;
			  if (scale >= half_res)
			    {
			      scale -= res;
			      ++raw;
			    }

			}
		      DBG (5,
			   "fix_line_distance_se: got line: %d; color: %d; "
			   "raw bytes: %lu; out bytes: %d\n",
			   lines[s->ld.color], s->ld.color, (u_long) (raw - raw_start),
			   s->params.pixels_per_line);
		      raw = raw_start + bpc;
		    }
		  else
		    {
		      if (preview)
			{
			  for (pixel = 0; pixel < bpc; ++pixel)
			    {
			      *out_ptr[s->ld.color] = *raw++;
			      out_ptr[s->ld.color] += 3;
			    }
			}
		      else
			{
			  for (pixel = 0; pixel < bpc; ++pixel)
			    {
			      scale += res;
			      if (scale >= max_value)
				{
				  scale -= max_value;
				  *out_ptr[s->ld.color] = *raw;
				  out_ptr[s->ld.color] += 3;
				}
			      ++raw;
			    }
			}

		      DBG (5, "fix_line_distance_se: got line: %d; color: "
			   "%d\n", lines[s->ld.color], s->ld.color);
		    }
		  --lines[s->ld.color];
		}
	      else
		{
		  /* At least one component missing, so save this line. */
		  memcpy (s->ld.buf[s->ld.color] + s->ld.saved[s->ld.color]
			  * bpc, raw, bpc);
		  DBG (5, "fix_line_distance_se: saved line %d; color %d\n",
		       s->ld.saved[s->ld.color], s->ld.color);
		  ++(s->ld.saved[s->ld.color]);
		  raw += bpc;
		}
	    }
	  else
	    {
	      if (!preview)
		raw += bpc;
	      DBG (5, "fix_line_distance_se: ignored line; color: %d\n",
		   s->ld.color);
	    }

	  if (raw >= raw_end)
	    {
	      /* Reduce num_lines if we encounter excess lines. */
	      if (num_lines > s->ld.ld_line)
		num_lines = s->ld.ld_line;
	      s->ld.ld_line -= num_lines;

	      if (++s->ld.color > 2)
		s->ld.color = 0;
	      return num_lines;
	    }
	}
      if (++s->ld.color > 2)
	s->ld.color = 0;
    }
}


/* For Pro models. Not really a linedistance correction (they don't need one)
   only enlarging x-res here */
static void
fix_line_distance_pro (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
		       SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *out_addr, *in_addr;
  SANE_Int res, half_res, y, x_out, x_in;


  res = SANE_UNFIX (s->val[OPT_RESOLUTION].w);
  half_res = SANE_UNFIX (s->hw->dpi_range.max) / 2;

  DBG (5, "fix_line_distance_pro: res=%d; halfres=%d; num_lines=%d; bpl=%d\n",
       res, half_res, num_lines, bpl);

  if (strcmp (s->val[OPT_BIT_DEPTH].s, "12") == 0)
    {
      if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
	{
	  /*12 bit, need to enlarge x-resolution */
	  DBG (5, "fix_line_distance_pro: res > half_res --> need to "
	       "enlarge x\n");
	  if (little_endian ())
	    for (y = 0; y < num_lines; y++)
	      {
		for (x_out = 0; x_out < s->params.pixels_per_line; x_out++)
		  {
		    x_in = x_out * bpl / s->params.bytes_per_line / 2;
		    x_in *= 2;
		    out_addr = out + y * s->params.bytes_per_line + x_out * 6;
		    in_addr = raw + y * bpl + x_in * 6;
		    *(out_addr) = *(in_addr) << 4;
		    *(out_addr + 1) = (*(in_addr) >> 4) +
		      (*(in_addr + 1) << 4);
		    *(out_addr + 2) = *(in_addr + 2) << 4;
		    *(out_addr + 3) = (*(in_addr + 2) >> 4) +
		      (*(in_addr + 3) << 4);
		    *(out_addr + 4) = *(in_addr + 4) << 4;
		    *(out_addr + 5) = (*(in_addr + 4) >> 4) +
		      (*(in_addr + 5) << 4);
		  }
	      }
	  else			/* big endian */
	    for (y = 0; y < num_lines; y++)
	      {
		for (x_out = 0; x_out < s->params.pixels_per_line; x_out++)
		  {
		    x_in = x_out * bpl / s->params.bytes_per_line / 2;
		    out_addr = out + y * s->params.bytes_per_line + x_out * 6;
		    in_addr = raw + y * bpl + x_in * 6;
		    *(out_addr) = (*(in_addr) >> 4) + (*(in_addr + 1) << 4);
		    *(out_addr + 1) = *(in_addr) << 4;
		    *(out_addr + 2) = (*(in_addr + 2) >> 4) +
		      (*(in_addr + 3) << 4);
		    *(out_addr + 3) = *(in_addr + 2) << 4;
		    *(out_addr + 4) = (*(in_addr + 4) >> 4) +
		      (*(in_addr + 5) << 4);
		    *(out_addr + 5) = *(in_addr + 4) << 4;
		  }
	      }
	}
      else			/* 12 bit, no need to enlarge x */
	{
	  SANE_Word pixel;

	  if (little_endian ())
	    for (pixel = 0; pixel < (num_lines * bpl / 2); pixel++)
	      {
		*(out + pixel * 2) = *(raw + pixel * 2) << 4;
		*(out + pixel * 2 + 1) = (*(raw + pixel * 2) >> 4) +
		  (*(raw + pixel * 2 + 1) << 4);
	      }
	  else			/* big endian */
	    for (pixel = 0; pixel < (num_lines * bpl / 2); pixel++)
	      {
		*(out + pixel * 2) = (*(raw + pixel * 2) >> 4) +
		  (*(raw + pixel * 2 + 1) << 4);
		*(out + pixel * 2 + 1) = *(raw + pixel * 2) << 4;
	      }

	}
    }
  else				/* 8 bit */
    {
      /* need to enlarge x-resolution? */
      if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) && (res > half_res))
	{
	  DBG (5, "fix_line_distance_pro: res > half_res --> need to "
	       "enlarge x\n");

	  for (y = 0; y < num_lines; y++)
	    {
	      for (x_out = 0; x_out < s->params.pixels_per_line; x_out++)
		{
		  x_in = x_out * bpl / s->params.bytes_per_line;
		  out_addr = out + y * s->params.bytes_per_line + x_out * 3;
		  in_addr = raw + y * bpl + x_in * 3;
		  *(out_addr) = *(in_addr);
		  *(out_addr + 1) = *(in_addr + 1);
		  *(out_addr + 2) = *(in_addr + 2);
		}
	    }
	}
      else
	memcpy (out, raw, num_lines * bpl);
    }
  return;
}

/* For MFS-08000SP, MFS-06000SP. MFC-08000CZ, MFC-06000CZ */
static void
fix_line_distance_normal (Mustek_Scanner * s, SANE_Int num_lines,
			  SANE_Int bpl, SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *out_end, *out_ptr, *raw_end = raw + num_lines * bpl;
  SANE_Int index[3];		/* index of the next output line for color C */
  SANE_Int i, color;

  /* Initialize the indices with the line distances that were returned
     by the CCD linedistance command or set manually (option
     linedistance-fix).  We want to skip data for the first OFFSET
     rounds, so we initialize the indices to the negative of this
     offset.  */

  DBG (5, "fix_line_distance_normal: %d lines, %d bpl\n", num_lines, bpl);

  for (color = 0; color < 3; ++color)
    index[color] = -s->ld.dist[color];

  while (1)
    {
      for (i = 0; i < 3; ++i)
	{
	  color = color_seq[i];
	  if (index[color] < 0)
	    ++index[color];
	  else if (index[color] < num_lines)
	    {
	      s->ld.quant[color] += s->ld.peak_res;
	      if (s->ld.quant[color] > s->ld.max_value)
		{
		  s->ld.quant[color] -= s->ld.max_value;
		  out_ptr = out + index[color] * bpl + color;
		  out_end = out_ptr + bpl;
		  while (out_ptr != out_end)
		    {
		      *out_ptr = *raw++;
		      out_ptr += 3;
		    }
		  ++index[color];
		  if (raw >= raw_end)
		    return;
		}
	    }
	}
    }
}

/* Paragon series I + II. */
static SANE_Int
fix_line_distance_block (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
			 SANE_Byte * raw, SANE_Byte * out,
			 SANE_Int num_lines_total)
{
  SANE_Byte *out_end, *out_ptr, *raw_end = raw + num_lines * bpl;
  SANE_Int c, num_saved_lines, line, max_index, min_index;

  if (!s->ld.buf[0])
    {
      DBG (5, "fix_line_distance_block: allocating temp buffer of %d*%d "
	   "bytes\n", MAX_LINE_DIST, bpl);
      s->ld.buf[0] = malloc (MAX_LINE_DIST * (long) bpl);
      if (!s->ld.buf[0])
	{
	  DBG (1, "fix_line_distance_block: failed to malloc temporary "
	       "buffer\n");
	  return 0;
	}
    }
  DBG (5, "fix_line_distance_block: s->ld.index = {%d, %d, %d}, "
       "s->ld.lmod3 = %d\n", s->ld.index[0], s->ld.index[1], s->ld.index[2],
       s->ld.lmod3);
  DBG (5, "fix_line_distance_block: s->ld.quant = {%d, %d, %d}, "
       "s->ld.max_value = %d\n", s->ld.quant[0], s->ld.quant[1],
       s->ld.quant[2], s->ld.max_value);
  DBG (5,
       "fix_line_distance_block: s->ld.peak_res = %d, s->ld.ld_line = %d\n",
       s->ld.peak_res, s->ld.ld_line);

  /* search maximum and minimum index */
  max_index = MAX (s->ld.index[0], MAX (s->ld.index[1], s->ld.index[2]));
  min_index = MIN (s->ld.index[0], MIN (s->ld.index[1], s->ld.index[2]));
  num_saved_lines = max_index - min_index;
  if (s->ld.index[0] == 0)
    num_saved_lines = 0;
  /* restore the previously saved lines: */
  memcpy (out, s->ld.buf[0], num_saved_lines * bpl);
  DBG (5, "fix_line_distance_block: copied %d lines from "
       "ld.buf to buffer (max=%d, min=%d)\n", num_saved_lines, max_index,
       min_index);
  while (1)
    {
      if (++s->ld.lmod3 >= 3)
	s->ld.lmod3 = 0;

      c = color_seq[s->ld.lmod3];
      if (s->ld.index[c] < 0)
	++s->ld.index[c];
      else if (s->ld.index[c] < num_lines_total)
	{
	  s->ld.quant[c] += s->ld.peak_res;
	  if (s->ld.quant[c] > s->ld.max_value)
	    {
	      s->ld.quant[c] -= s->ld.max_value;
	      line = s->ld.index[c]++ - s->ld.ld_line;
	      out_ptr = out + line * bpl + c;
	      out_end = out_ptr + bpl;
	      while (out_ptr != out_end)
		{
		  *out_ptr = *raw++;
		  out_ptr += 3;
		}
	      DBG (5, "fix_line_distance_block: copied line %d (color %d)\n",
		   line + s->ld.ld_line, c);

	      max_index = MAX (s->ld.index[0],
			       MAX (s->ld.index[1], s->ld.index[2]));
	      min_index = MIN (s->ld.index[0],
			       MIN (s->ld.index[1], s->ld.index[2]));
	      if ((raw >= raw_end) || ((min_index >= num_lines_total)))
		{
		  DBG (5, "fix_line_distance_block: got num_lines: %d\n",
		       num_lines);
		  num_lines = min_index - s->ld.ld_line;
		  if (num_lines < 0)
		    num_lines = 0;
		  if ((s->total_lines + num_lines) > s->params.lines)
		    num_lines = s->params.lines - s->total_lines;
		  s->total_lines += num_lines;

		  /* copy away the lines with at least one missing
		     color component, so that we can interleave them
		     with new scan data on the next call */
		  num_saved_lines = max_index - min_index;

		  DBG (5, "fix_line_distance_block: num_saved_lines = %d; "
		       "num_lines = %d; bpl = %d\n", num_saved_lines,
		       num_lines, bpl);

		  memcpy (s->ld.buf[0], out + num_lines * bpl,
			  num_saved_lines * bpl);

		  DBG (5, "fix_line_distance_block: copied %d lines to "
		       "ld.buf\n", num_saved_lines);

		  /* notice the number of lines we processed */
		  s->ld.ld_line = min_index;
		  if (s->ld.ld_line < 0)
		    s->ld.ld_line = 0;

		  DBG (4, "fix_line_distance_block: lmod3=%d, "
		       "index=(%d,%d,%d), line = %d, lines = %d\n",
		       s->ld.lmod3,
		       s->ld.index[0], s->ld.index[1], s->ld.index[2],
		       s->ld.ld_line, num_lines);
		  /* return number of complete (r+g+b) lines */
		  return num_lines;
		}
	    }
	}
    }
}

/* For MFS-1200SP 1.00 and others */
/* No LD correction necessary, just shuffle around data */
static SANE_Int
fix_line_distance_none (Mustek_Scanner * s, SANE_Int num_lines, SANE_Int bpl,
			SANE_Byte * raw, SANE_Byte * out)
{
  SANE_Byte *red_ptr, *grn_ptr, *blu_ptr, *ptr, *ptr_end;
  SANE_Word y;

  ptr = out;
  red_ptr = raw;

  DBG (5, "fix_line_distance_none: no ld correction necessary (%d lines)\n",
       num_lines);

  s->ld.ld_line += num_lines;

  if (s->ld.ld_line > s->params.lines)
    num_lines -= (s->ld.ld_line - s->params.lines);
  if (num_lines < 0)
    num_lines = 0;

  DBG (5, "fix_line_distance_none: using %d lines (ld_line = %d, "
       "s->params.lines = %d)\n", num_lines, s->ld.ld_line, s->params.lines);

  for (y = 0; y < num_lines; ++y)
    {
      grn_ptr = red_ptr + bpl / 3;
      blu_ptr = grn_ptr + bpl / 3;
      ptr_end = red_ptr + bpl;

      while (blu_ptr != ptr_end)
	{
	  *ptr++ = *red_ptr++;
	  *ptr++ = *grn_ptr++;
	  *ptr++ = *blu_ptr++;
	}
      red_ptr = ptr_end;
    }
  return num_lines;
}


static SANE_Status
init_options (Mustek_Scanner * s)
{
  SANE_Int i, j, gammasize;

  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;
  if (s->hw->flags & MUSTEK_FLAG_SE)
    {
      s->opt[OPT_MODE].size = max_string_size (mode_list_se);
      s->opt[OPT_MODE].constraint.string_list = mode_list_se;
      s->val[OPT_MODE].s = strdup (mode_list_se[1]);
      if (!s->val[OPT_MODE].s)
	return SANE_STATUS_NO_MEM;
    }
  else
    {
      s->opt[OPT_MODE].size = max_string_size (mode_list_paragon);
      s->opt[OPT_MODE].constraint.string_list = mode_list_paragon;
      s->val[OPT_MODE].s = strdup (mode_list_paragon[2]);
      if (!s->val[OPT_MODE].s)
	return SANE_STATUS_NO_MEM;
    }

  /* fast gray mode (pro models) */
  s->opt[OPT_FAST_GRAY_MODE].name = "fast-gray-mode";
  s->opt[OPT_FAST_GRAY_MODE].title = SANE_I18N ("Fast gray mode");
  s->opt[OPT_FAST_GRAY_MODE].desc = SANE_I18N ("Scan in fast gray mode "
					       "(lower quality).");
  s->opt[OPT_FAST_GRAY_MODE].type = SANE_TYPE_BOOL;
  s->val[OPT_FAST_GRAY_MODE].w = SANE_FALSE;
  s->opt[OPT_FAST_GRAY_MODE].cap |= SANE_CAP_INACTIVE;
  if (s->hw->flags & MUSTEK_FLAG_PRO)
    {
      /* Only Pro models support fast gray mode */
      s->opt[OPT_FAST_GRAY_MODE].cap &= ~SANE_CAP_INACTIVE;
    }

  /* 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_FIXED;
  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_RESOLUTION].constraint.range = &s->hw->dpi_range;
  s->val[OPT_RESOLUTION].w = MAX (SANE_FIX (72), s->hw->dpi_range.min);

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

  /* speed */
  s->opt[OPT_SPEED].name = SANE_NAME_SCAN_SPEED;
  s->opt[OPT_SPEED].title = SANE_TITLE_SCAN_SPEED;
  s->opt[OPT_SPEED].desc = SANE_DESC_SCAN_SPEED;
  s->opt[OPT_SPEED].type = SANE_TYPE_STRING;
  s->opt[OPT_SPEED].size = max_string_size (speed_list);
  s->opt[OPT_SPEED].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_SPEED].constraint.string_list = speed_list;
  s->val[OPT_SPEED].s = strdup (speed_list[4]);
  if (!s->val[OPT_SPEED].s)
    return SANE_STATUS_NO_MEM;
  if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    {
      /* Speed only supported by 3-pass scanners */
      s->opt[OPT_SPEED].cap |= SANE_CAP_INACTIVE;
    }

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

  if ((s->hw->flags & MUSTEK_FLAG_SE) || (s->hw->flags & MUSTEK_FLAG_N)
      || (s->hw->flags & MUSTEK_FLAG_TA))
    {
      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;
    }
  else if (s->hw->flags & MUSTEK_FLAG_ADF)
    {
      s->opt[OPT_SOURCE].size = max_string_size (adf_source_list);
      s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
      s->opt[OPT_SOURCE].constraint.string_list = adf_source_list;
      s->val[OPT_SOURCE].s = strdup (adf_source_list[0]);
      if (!s->val[OPT_SOURCE].s)
	return SANE_STATUS_NO_MEM;
    }
  else
    {
      s->opt[OPT_SOURCE].size = max_string_size (source_list);
      s->opt[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
      s->opt[OPT_SOURCE].constraint.string_list = source_list;
      s->val[OPT_SOURCE].s = strdup (source_list[0]);
      s->opt[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
      if (!s->val[OPT_SOURCE].s)
	return SANE_STATUS_NO_MEM;
    }

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

  /* fast preview */
  s->opt[OPT_FAST_PREVIEW].name = "fast-preview";
  s->opt[OPT_FAST_PREVIEW].title = SANE_I18N ("Fast preview");
  s->opt[OPT_FAST_PREVIEW].desc = SANE_I18N ("Request that all previews are "
					     "done in the fastest (low-quality) mode. This may be a non-color "
					     "mode or a low resolution mode.");
  s->opt[OPT_FAST_PREVIEW].type = SANE_TYPE_BOOL;
  s->val[OPT_FAST_PREVIEW].w = SANE_FALSE;

  /* lamp off time*/
  s->opt[OPT_LAMP_OFF_TIME].name = "lamp-off-time";
  s->opt[OPT_LAMP_OFF_TIME].title = SANE_I18N ("Lamp off time (minutes)");
  s->opt[OPT_LAMP_OFF_TIME].desc = SANE_I18N ("Set the time (in minutes) after "
					      "which the lamp is shut off.");
  s->opt[OPT_LAMP_OFF_TIME].type = SANE_TYPE_INT;
  if (strcmp (s->hw->sane.model, "1200 A3 PRO") != 0)
    s->opt[OPT_LAMP_OFF_TIME].cap |= SANE_CAP_INACTIVE;
  s->opt[OPT_LAMP_OFF_TIME].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_LAMP_OFF_TIME].constraint.range = &u8_range;
  s->val[OPT_LAMP_OFF_TIME].w = 60;

  /* shut lamp off */
  s->opt[OPT_LAMP_OFF_BUTTON].name = "lamp-off";
  s->opt[OPT_LAMP_OFF_BUTTON].title = SANE_I18N ("Turn lamp off");
  s->opt[OPT_LAMP_OFF_BUTTON].desc = SANE_I18N ("Turns the lamp off immediately.");
  s->opt[OPT_LAMP_OFF_BUTTON].type = SANE_TYPE_BUTTON;
  s->opt[OPT_LAMP_OFF_BUTTON].cap = SANE_CAP_SOFT_SELECT;
  if (strcmp (s->hw->sane.model, "1200 A3 PRO") != 0)
    s->opt[OPT_LAMP_OFF_BUTTON].cap |= SANE_CAP_INACTIVE;

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

  /* brightness */
  s->opt[OPT_BRIGHTNESS].name = SANE_NAME_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].desc = SANE_DESC_BRIGHTNESS;
  s->opt[OPT_BRIGHTNESS].type = SANE_TYPE_FIXED;
  s->opt[OPT_BRIGHTNESS].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS].constraint.range = &percentage_range;
  if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    /* 1-pass scanners don't support brightness in multibit mode */
    s->opt[OPT_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_BRIGHTNESS].w = 0;

  /* brightness red */
  s->opt[OPT_BRIGHTNESS_R].name = "brightness-r";
  s->opt[OPT_BRIGHTNESS_R].title = SANE_I18N ("Red brightness");
  s->opt[OPT_BRIGHTNESS_R].desc = SANE_I18N ("Controls the brightness of "
					     "the red channel of the "
					     "acquired image.");
  s->opt[OPT_BRIGHTNESS_R].type = SANE_TYPE_FIXED;
  s->opt[OPT_BRIGHTNESS_R].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_BRIGHTNESS_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS_R].constraint.range = &percentage_range;
  s->opt[OPT_BRIGHTNESS_R].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_BRIGHTNESS_R].w = 0;

  /* brightness green */
  s->opt[OPT_BRIGHTNESS_G].name = "brightness-g";
  s->opt[OPT_BRIGHTNESS_G].title = SANE_I18N ("Green brightness");
  s->opt[OPT_BRIGHTNESS_G].desc = SANE_I18N ("Controls the brightness of "
					     "the green channel of the "
					     "acquired image.");
  s->opt[OPT_BRIGHTNESS_G].type = SANE_TYPE_FIXED;
  s->opt[OPT_BRIGHTNESS_G].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_BRIGHTNESS_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS_G].constraint.range = &percentage_range;
  s->opt[OPT_BRIGHTNESS_G].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_BRIGHTNESS_G].w = 0;

  /* brightness blue */
  s->opt[OPT_BRIGHTNESS_B].name = "brightness-b";
  s->opt[OPT_BRIGHTNESS_B].title = SANE_I18N ("Blue brightness");
  s->opt[OPT_BRIGHTNESS_B].desc = SANE_I18N ("Controls the brightness of "
					     "the blue channel of the "
					     "acquired image.");
  s->opt[OPT_BRIGHTNESS_B].type = SANE_TYPE_FIXED;
  s->opt[OPT_BRIGHTNESS_B].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_BRIGHTNESS_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BRIGHTNESS_B].constraint.range = &percentage_range;
  s->opt[OPT_BRIGHTNESS_B].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_BRIGHTNESS_B].w = 0;

  /* contrast */
  s->opt[OPT_CONTRAST].name = SANE_NAME_CONTRAST;
  s->opt[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  s->opt[OPT_CONTRAST].desc = SANE_DESC_CONTRAST;
  s->opt[OPT_CONTRAST].type = SANE_TYPE_FIXED;
  s->opt[OPT_CONTRAST].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST].constraint.range = &percentage_range;
  if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    /* 1-pass scanners don't support contrast in multibit mode */
    s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_CONTRAST].w = 0;

  /* contrast red */
  s->opt[OPT_CONTRAST_R].name = "contrast-r";
  s->opt[OPT_CONTRAST_R].title = SANE_I18N ("Contrast red channel");
  s->opt[OPT_CONTRAST_R].desc = SANE_I18N ("Controls the contrast of "
					   "the red channel of the "
					   "acquired image.");
  s->opt[OPT_CONTRAST_R].type = SANE_TYPE_FIXED;
  s->opt[OPT_CONTRAST_R].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_CONTRAST_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST_R].constraint.range = &percentage_range;
  s->opt[OPT_CONTRAST_R].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_CONTRAST_R].w = 0;

  /* contrast green */
  s->opt[OPT_CONTRAST_G].name = "contrast-g";
  s->opt[OPT_CONTRAST_G].title = SANE_I18N ("Contrast green channel");
  s->opt[OPT_CONTRAST_G].desc = SANE_I18N ("Controls the contrast of "
					   "the green channel of the "
					   "acquired image.");
  s->opt[OPT_CONTRAST_G].type = SANE_TYPE_FIXED;
  s->opt[OPT_CONTRAST_G].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_CONTRAST_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST_G].constraint.range = &percentage_range;
  s->opt[OPT_CONTRAST_G].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_CONTRAST_G].w = 0;

  /* contrast blue */
  s->opt[OPT_CONTRAST_B].name = "contrast-b";
  s->opt[OPT_CONTRAST_B].title = SANE_I18N ("Contrast blue channel");
  s->opt[OPT_CONTRAST_B].desc = SANE_I18N ("Controls the contrast of "
					   "the blue channel of the "
					   "acquired image.");
  s->opt[OPT_CONTRAST_B].type = SANE_TYPE_FIXED;
  s->opt[OPT_CONTRAST_B].unit = SANE_UNIT_PERCENT;
  s->opt[OPT_CONTRAST_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_CONTRAST_B].constraint.range = &percentage_range;
  s->opt[OPT_CONTRAST_B].cap |= SANE_CAP_INACTIVE;
  s->val[OPT_CONTRAST_B].w = 0;

  /* gamma */
  gammasize = 256;
  for (i = 0; i < 4; ++i)
    for (j = 0; j < gammasize; ++j)
      s->gamma_table[i][j] = j;

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

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

  /* 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].cap |= SANE_CAP_INACTIVE;
  s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_R].wa = &s->gamma_table[1][0];
  s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;

  /* 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].cap |= SANE_CAP_INACTIVE;
  s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_G].wa = &s->gamma_table[2][0];
  s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;

  /* 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].cap |= SANE_CAP_INACTIVE;
  s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word);
  s->val[OPT_GAMMA_VECTOR_B].wa = &s->gamma_table[3][0];
  s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;

  /* quality 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_DESC_QUALITY_CAL;
  s->opt[OPT_QUALITY_CAL].type = SANE_TYPE_BOOL;
  if (s->hw->flags & MUSTEK_FLAG_PRO)
    s->val[OPT_QUALITY_CAL].w = SANE_TRUE;
  else
    s->val[OPT_QUALITY_CAL].w = SANE_FALSE;
  s->opt[OPT_QUALITY_CAL].cap |= SANE_CAP_INACTIVE;
  if ((s->hw->flags & MUSTEK_FLAG_PRO)
      || (s->hw->flags & MUSTEK_FLAG_SE_PLUS))
    {
      /* Only Pro and SE Plus models support calibration */
      s->opt[OPT_QUALITY_CAL].cap &= ~SANE_CAP_INACTIVE;
    }

  /* halftone dimension */
  s->opt[OPT_HALFTONE_DIMENSION].name = SANE_NAME_HALFTONE_DIMENSION;
  s->opt[OPT_HALFTONE_DIMENSION].title = SANE_TITLE_HALFTONE_DIMENSION;
  s->opt[OPT_HALFTONE_DIMENSION].desc = SANE_DESC_HALFTONE_DIMENSION;
  s->opt[OPT_HALFTONE_DIMENSION].type = SANE_TYPE_STRING;
  s->opt[OPT_HALFTONE_DIMENSION].size = max_string_size (halftone_list);
  s->opt[OPT_HALFTONE_DIMENSION].constraint_type =
    SANE_CONSTRAINT_STRING_LIST;
  s->opt[OPT_HALFTONE_DIMENSION].constraint.string_list = halftone_list;
  s->val[OPT_HALFTONE_DIMENSION].s = strdup (halftone_list[0]);
  if (!s->val[OPT_HALFTONE_DIMENSION].s)
    return SANE_STATUS_NO_MEM;
  s->opt[OPT_HALFTONE_DIMENSION].cap |= SANE_CAP_INACTIVE;

  /* halftone pattern */
  s->opt[OPT_HALFTONE_PATTERN].name = SANE_NAME_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].desc = SANE_DESC_HALFTONE_PATTERN;
  s->opt[OPT_HALFTONE_PATTERN].type = SANE_TYPE_INT;
  s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;
  s->opt[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_HALFTONE_PATTERN].constraint.range = &u8_range;
  s->val[OPT_HALFTONE_PATTERN].wa = s->halftone_pattern;

  return SANE_STATUS_GOOD;
}

/* The following three functions execute as a child process.  The
   reason for using a subprocess is that some (most?) generic SCSI
   interfaces block a SCSI request until it has completed.  With a
   subprocess, we can let it block waiting for the request to finish
   while the main process can go about to do more important things
   (such as recognizing when the user presses a cancel button).

   WARNING: Since this is executed as a subprocess, it's NOT possible
   to update any of the variables in the main process (in particular
   the scanner state cannot be updated).

   NOTE: At least for Linux, it seems that we could get rid of the
   subprocess.  Linux v2.0 does seem to allow select() on SCSI
   descriptors.  */

static void
output_data (Mustek_Scanner * s, FILE * fp,
	     SANE_Byte * data, SANE_Int lines_per_buffer, SANE_Int bpl,
	     SANE_Byte * extra)
{
  SANE_Byte *ptr, *ptr_end;
  SANE_Int y, num_lines;

  DBG (5, "output_data: data=%p, lpb=%d, bpl=%d, extra=%p\n",
       data, lines_per_buffer, bpl, extra);

  /* convert to pixel-interleaved format: */
  if ((s->mode & MUSTEK_MODE_COLOR)
      && !(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    {
      num_lines = lines_per_buffer;

      /* need to correct for distance between r/g/b sensors: */
      if (s->hw->flags & MUSTEK_FLAG_PRO)
	fix_line_distance_pro (s, num_lines, bpl, data, extra);
      else if (s->hw->flags & MUSTEK_FLAG_SE)
	{
	  num_lines = fix_line_distance_se (s, num_lines, bpl, data, extra);
	}
      else if (s->hw->flags & MUSTEK_FLAG_N)
	{
	  if (s->hw->flags & MUSTEK_FLAG_LD_N2)
	    num_lines = fix_line_distance_n_2 (s, num_lines, bpl, data,
					       extra);
	  else
	    num_lines = fix_line_distance_n_1 (s, num_lines, bpl, data,
					       extra);
	}
      else if ((s->hw->flags & MUSTEK_FLAG_LD_BLOCK)
	       && (s->ld.max_value != 0))
	{
	  if (s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	    num_lines = fix_line_distance_block (s, num_lines, bpl, data,
						 extra, s->hw->lines);
	  else
	    num_lines = fix_line_distance_block (s, num_lines, bpl, data,
						 extra,
						 s->hw->lines_per_block);
	}
      else if (!(s->hw->flags & MUSTEK_FLAG_LD_NONE)
	       && (s->ld.max_value != 0))
	fix_line_distance_normal (s, num_lines, bpl, data, extra);
      else
	num_lines = fix_line_distance_none (s, num_lines, bpl, data, extra);

      if (strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0)
	{
	  /* need to revert line direction */
	  SANE_Int line_number;
	  SANE_Int byte_number;

	  DBG (5, "output_data: ADF found, mirroring lines\n");
	  for (line_number = 0; line_number < num_lines; line_number++)
	    {
	      for (byte_number = bpl - 3; byte_number >= 0; byte_number -= 3)
		{
		  fputc (*(extra + line_number * bpl + byte_number), fp);
		  fputc (*(extra + line_number * bpl + byte_number + 1), fp);
		  fputc (*(extra + line_number * bpl + byte_number + 2), fp);
		}
	    }
	}
      else
	fwrite (extra, num_lines, s->params.bytes_per_line, fp);
    }
  else
    {
      DBG (5, "output_data: write %d lpb; %d bpl\n", lines_per_buffer, bpl);
      /* Scale x-resolution above 1/2 of the maximum resolution for
         SE and Pro scanners */
      if ((s->hw->flags & MUSTEK_FLAG_ENLARGE_X) &&
	  (s->val[OPT_RESOLUTION].w > (s->hw->dpi_range.max / 2)))
	{
	  SANE_Int x;
	  SANE_Int half_res = SANE_UNFIX (s->hw->dpi_range.max) / 2;
	  SANE_Int res = SANE_UNFIX (s->val[OPT_RESOLUTION].w);
	  SANE_Int res_counter;
	  SANE_Int enlarged_x;

	  DBG (5, "output_data: enlarge lines from %d bpl to %d bpl\n",
	       s->hw->bpl, s->params.bytes_per_line);

	  for (y = 0; y < lines_per_buffer; y++)
	    {
	      SANE_Byte byte = 0;

	      x = 0;
	      res_counter = 0;
	      enlarged_x = 0;

	      while (enlarged_x < s->params.pixels_per_line)
		{
		  if (s->mode & MUSTEK_MODE_GRAY)
		    {
		      fputc (*(data + y * bpl + x), fp);
		      res_counter += half_res;
		      if (res_counter >= half_res)
			{
			  res_counter -= res;
			  x++;
			}
		      enlarged_x++;
		    }
		  else		/* lineart */
		    {
		      /* need to invert image because of funny SANE 1-bit image
			 polarity */
		      if (*(data + x / 8 + y * bpl) & (1 << (7 - (x % 8))))
			byte |= 1 << (7 - (enlarged_x % 8));

		      if ((enlarged_x % 8) == 7)
			{
			  fputc (~byte, fp);	/* invert image */
			  byte = 0;
			}
		      res_counter += half_res;
		      if (res_counter >= half_res)
			{
			  res_counter -= res;
			  x++;
			}
		      enlarged_x++;
		    }
		}
	    }
	}
      else			/* lineart, gray or halftone (nothing to scale) */
	{
	  if ((s->mode & MUSTEK_MODE_LINEART)
	      || (s->mode & MUSTEK_MODE_HALFTONE))
	    {
	      /* need to invert image because of funny SANE 1-bit image
		 polarity */
	      ptr = data;
	      ptr_end = ptr + lines_per_buffer * bpl;

	      if (strcmp (s->val[OPT_SOURCE].s,
			  "Automatic Document Feeder") == 0)
		{
		  while (ptr != ptr_end)
		    {
		      (*ptr) = ~(*ptr);
		      ptr++;
		      /* need to revert bit direction */
		      *ptr = ((*ptr & 0x80) >> 7) + ((*ptr & 0x40) >> 5)
			+ ((*ptr & 0x20) >> 3) + ((*ptr & 0x10) >> 1)
			+ ((*ptr & 0x08) << 1) + ((*ptr & 0x04) << 3)
			+ ((*ptr & 0x02) << 5) + ((*ptr & 0x01) << 7);
		    }
		}
	      else
		while (ptr != ptr_end)
		  {
		    (*ptr) = ~(*ptr);
		    ptr++;
		  }
	    }
	  if (strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0)
	    {
	      /* need to revert line direction */
	      SANE_Int line_number;
	      SANE_Int byte_number;

	      DBG (5, "output_data: ADF found, mirroring lines\n");
	      for (line_number = 0; line_number < lines_per_buffer;
		   line_number++)
		{
		  for (byte_number = bpl - 1; byte_number >= 0; byte_number--)
		    {
		      fputc (*(data + line_number * bpl + byte_number), fp);
		    }
		}
	    }
	  else
	    {
	      fwrite (data, lines_per_buffer, bpl, fp);
	    }
	}
    }
  DBG (5, "output_data: end\n");
}

static void
sigterm_handler (int signal)
{
  DBG (4,
       "sigterm_handler: started, signal is %d, starting sanei_scsi_req_flush_all()\n",
       signal);
  sanei_scsi_req_flush_all ();	/* flush SCSI queue */
  DBG (4,
       "sigterm_handler: sanei_scsi_req_flush_all() finisheshed, _exiting()\n");
  _exit (SANE_STATUS_GOOD);
}


static SANE_Int
reader_process (void *data)
{
  Mustek_Scanner *s = (Mustek_Scanner *) data;
  SANE_Int lines_per_buffer, bpl;
  SANE_Byte *extra = 0, *ptr;
  sigset_t sigterm_set;
  struct SIGACTION act;
  SANE_Status status;
  FILE *fp;
  int fd = s->reader_fds;
  SANE_Int buffernumber = 0;
  SANE_Int buffer_count, max_buffers;
  struct
  {
    void *id;			/* scsi queue id */
    SANE_Byte *data;		/* data buffer */
    SANE_Byte *command;		/* command buffer */
    SANE_Int lines;		/* # lines in buffer */
    size_t num_read;		/* # of bytes read (return value) */
    SANE_Int bank;		/* needed by SE models */
    SANE_Bool ready;		/* ready to send to application? */
    SANE_Bool finished;		/* block is finished */
  }
  bstat[2];

  DBG (3, "reader_process: started\n");
  if (sanei_thread_is_forked ())
    {
      DBG (4, "reader_process: using fork ()\n");
      close (s->pipe);
      s->pipe = -1;
    }
  else
    {
      DBG (4, "reader_process: using threads\n");
    }

  if (sanei_thread_is_forked ())
    {
      /* ignore SIGTERM while writing SCSI commands */
      sigemptyset (&sigterm_set);
      sigaddset (&sigterm_set, SIGTERM);

      /* call our sigterm handler to clean up ongoing SCSI requests */
      memset (&act, 0, sizeof (act));
      act.sa_handler = sigterm_handler;
      sigaction (SIGTERM, &act, 0);
    }

  if (disable_double_buffering)
    DBG (3, "reader_process: disable_double_buffering is set, this may be "
	 "slow\n");

  fp = fdopen (fd, "w");
  if (!fp)
    return SANE_STATUS_IO_ERROR;

  s->total_lines = 0;
  bpl = s->hw->bpl;

  /* buffer size is scanner dependent */
  lines_per_buffer = s->hw->buffer_size / bpl / 2;

  if (strip_height > 0.0)
    {
      SANE_Int max_lines;
      double dpi;

      dpi = SANE_UNFIX (s->val[OPT_RESOLUTION].w);
      max_lines = (int) (strip_height * dpi + 0.5);

      if (lines_per_buffer > max_lines)
	{
	  DBG (2, "reader_process: limiting strip height to %g inches "
	       "(%d lines)\n", strip_height, max_lines);
	  lines_per_buffer = max_lines;
	}
    }

  if (!lines_per_buffer)
    {
      DBG (1, "reader_process: bpl (%d) > SCSI buffer size / 2 (%d)\n",
	   bpl, s->hw->buffer_size / 2);
      return SANE_STATUS_NO_MEM;	/* resolution is too high */
    }

  DBG (4, "reader_process: %d lines per buffer, %d bytes per line, "
       "%d bytes per buffer\n", lines_per_buffer, bpl,
       lines_per_buffer * bpl);

  bstat[0].data = malloc (2 * lines_per_buffer * (long) bpl);
  if (!bstat[0].data)
    {
      DBG (1, "reader_process: failed to malloc %ld bytes for data buffer\n",
	   lines_per_buffer * (long) bpl);
      return SANE_STATUS_NO_MEM;
    }
  bstat[1].data = bstat[0].data + lines_per_buffer * (long) bpl;

  bstat[0].command = malloc (2 * 10);
  if (!bstat[0].command)
    {
      DBG (1,
	   "reader_process: failed to malloc %d bytes for command buffer\n",
	   2 * 10);
      return SANE_STATUS_NO_MEM;
    }
  bstat[1].command = bstat[0].command + 10;

  /* Touch all pages of the buffer to fool the memory management. */
  ptr = bstat[0].data + 2 * lines_per_buffer * (long) bpl - 1;
  while (ptr >= bstat[0].data)
    {
      *ptr = 0x00;
      ptr -= 256;
    }

  if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    {
      /* get temporary buffer for line-distance correction and/or bit
         expansion. For some scanners more space is needed because the
         data must be read in as single big block (cut up into pieces
         of lines_per_buffer). This requires that the line distance
         correction continues on every call exactly where it stopped
         if the image shall be reconstructed without any stripes. */

      extra = malloc ((lines_per_buffer + MAX_LINE_DIST)
		      * (long) s->params.bytes_per_line);
      if (!extra)
	{
	  DBG (1, "reader_process: failed to malloc extra buffer\n");
	  return SANE_STATUS_NO_MEM;
	}
    }

  if (s->hw->flags & MUSTEK_FLAG_N)
    {
      /* reacquire port access rights (lost because of fork()): */
      sanei_ab306_get_io_privilege (s->fd);
    }

  if ((s->hw->flags & MUSTEK_FLAG_N) || (s->hw->flags & MUSTEK_FLAG_LD_BLOCK))
    {
      /* reset counter of line number for line-dictance correction */
      s->ld.ld_line = 0;
    }

  max_buffers = s->hw->max_block_buffer_size / (lines_per_buffer * bpl);
  if (max_buffers < 1)
    {
      DBG (1, "reader_process: buffersize > blocksize!\n");
      return SANE_STATUS_NO_MEM;
    }
  DBG (4, "reader_process: limiting block read to %d buffers (%d lines)\n",
       max_buffers, MIN (s->hw->lines, (max_buffers * lines_per_buffer)));

  while (s->line < s->hw->lines)
    {
      s->hw->lines_per_block =
	MIN (s->hw->lines - s->line, (max_buffers * lines_per_buffer));
      status = dev_block_read_start (s, s->hw->lines_per_block);
      if (status != SANE_STATUS_GOOD)
	return status;

      for (buffernumber = 0; buffernumber < 2; buffernumber++)
	{
	  bstat[buffernumber].ready = SANE_FALSE;
	  bstat[buffernumber].finished = SANE_FALSE;
	}
      buffer_count = 0;
      buffernumber = 0;

      while (1)
	{
	  /* omit reading first two buffers (not yet ready) */
	  if (bstat[buffernumber].ready == SANE_TRUE)
	    {
	      DBG (4, "reader_process: buffer %d: waiting for request to be "
		   "ready\n", buffernumber + 1);
	      status = dev_req_wait (bstat[buffernumber].id);
	      if (status == SANE_STATUS_GOOD)
		{
		  DBG (4, "reader_process: buffer %d is ready, wanted %d, "
		       "got %ld bytes\n", buffernumber + 1,
		       bstat[buffernumber].lines * bpl,
		       (long int) bstat[buffernumber].num_read);
		}
	      else
		{
		  DBG (1, "reader_process: failed to read data, status: %s, "
		       "buffer: %d\n", sane_strstatus (status),
		       buffernumber + 1);
		  if (status == SANE_STATUS_NO_MEM)
		    {
		      DBG (1,
			   "Probably the size of the kernel SCSI buffer is "
			   "too small for the\n         selected buffersize "
			   "in mustek.conf. Either decrease "
			   "buffersize in\n         mustek.conf to e.g. 32, "
			   "increase SG_BIG_BUF in kernel to 130560, "
			   "or\n         use SANE_SG_BUFFERSIZE variable. "
			   "See man sane-scsi and README for\n         "
			   "details.\n");
		    }
		  return status;
		}

	      DBG (4, "reader_process: buffer %d: sending %ld bytes to "
		   "output_data\n", buffernumber + 1,
		   (long int) bstat[buffernumber].num_read);
	      output_data (s, fp, bstat[buffernumber].data,
			   bstat[buffernumber].lines, bpl, extra);
	      if (bstat[buffernumber].finished)
		break;		/* everything written; exit loop */
	    }
	  if (disable_double_buffering)
	    {
	      /* Enter only one buffer at once */
	      if (buffernumber == 1)
		buffernumber = 0;
	      else
		buffernumber = 1;
	    }

	  /* enter read requests only if data left */
	  if ((s->line < s->hw->lines) && (buffer_count < max_buffers))
	    {
	      if (s->line + lines_per_buffer >= s->hw->lines)
		{
		  /* do the last few lines: */
		  bstat[buffernumber].lines = s->hw->lines - s->line;
		  bstat[buffernumber].bank = 0x01;
		  bstat[buffernumber].finished = SANE_TRUE;
		}
	      else
		{
		  bstat[buffernumber].lines = lines_per_buffer;
		  bstat[buffernumber].bank = 0x00;
		}

	      if ((buffer_count + 1) >= max_buffers)
		bstat[buffernumber].finished = SANE_TRUE;

	      s->line += bstat[buffernumber].lines;
	      bstat[buffernumber].ready = SANE_TRUE;

	      buffer_count++;

	      DBG (4,
		   "reader_process: buffer %d: entering read request for %d "
		   "bytes (buffer %d)\n", buffernumber + 1,
		   bstat[buffernumber].lines * bpl, buffer_count);
	      sigprocmask (SIG_BLOCK, &sigterm_set, 0);
	      status = dev_read_req_enter (s, bstat[buffernumber].data,
					   bstat[buffernumber].lines, bpl,
					   &bstat[buffernumber].num_read,
					   &bstat[buffernumber].id,
					   bstat[buffernumber].bank,
					   bstat[buffernumber].command);
	      sigprocmask (SIG_UNBLOCK, &sigterm_set, 0);


	      if (status == SANE_STATUS_GOOD)
		{
		  DBG (5, "reader_process: buffer %d: entered (line %d of %d,"
		       " buffer %d)\n", buffernumber + 1, s->line,
		       s->hw->lines, buffer_count);
		}
	      else
		{
		  DBG (1, "reader_process: buffer %d: failed to enter read "
		       "request, status: %s\n", buffernumber + 1,
		       sane_strstatus (status));
		  return status;
		}
	    }
	  if (!disable_double_buffering)
	    {
	      if (buffernumber == 1)
		buffernumber = 0;
	      else
		buffernumber = 1;
	    }
	  /* This is said to fix the scanner hangs that reportedly show on
	     some MFS-12000SP scanners.  */
	  if (s->mode == 0 && (s->hw->flags & MUSTEK_FLAG_LINEART_FIX))
	    usleep (200000);
	}
    }

  fclose (fp);
  free (bstat[0].data);
  if (s->ld.buf[0])
    free (s->ld.buf[0]);
  s->ld.buf[0] = NULL;
  if (extra)
    free (extra);
  close (fd);
  return SANE_STATUS_GOOD;
}

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

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

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

  sanei_thread_init ();

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

  DBG (2, "SANE mustek backend version %d.%d build %d from %s\n", SANE_CURRENT_MAJOR,
       SANE_CURRENT_MINOR, BUILD, PACKAGE_STRING);

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

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

#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
  DBG (5, "sane_init: using sanei_scsi_open_extended\n");
#else
  DBG (5, "sane_init: using sanei_scsi_open with buffer size = %d bytes\n",
       sanei_scsi_max_request_size);
#endif

  num_devices = 0;
  force_wait = SANE_FALSE;
  disable_double_buffering = SANE_FALSE;
  first_dev = 0;
  first_handle = 0;
  devlist = 0;
  new_dev = 0;
  new_dev_len = 0;
  new_dev_alloced = 0;

  fp = sanei_config_open (MUSTEK_CONFIG_FILE);
  if (!fp)
    {
      /* default to /dev/scanner instead of insisting on config file */
      DBG (3, "sane_init: couldn't find config file (%s), trying "
	   "/dev/scanner directly\n", MUSTEK_CONFIG_FILE);
      attach ("/dev/scanner", 0, SANE_FALSE);
      return SANE_STATUS_GOOD;
    }
  linenumber = 0;
  DBG (4, "sane_init: reading config file `%s'\n", MUSTEK_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, "strip-height") == 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;
		}

	      errno = 0;
	      strip_height = strtod (word, &end);
	      if (end == word)
		{
		  DBG (3, "sane-init: config file line %d: strip-height "
		       "must have a parameter; using 1 inch\n", linenumber);
		  strip_height = 1.0;
		}
	      if (errno)
		{
		  DBG (3, "sane-init: config file line %d: strip-height `%s' "
		       "is invalid (%s); using 1 inch\n", linenumber,
		       word, strerror (errno));
		  strip_height = 1.0;
		}
	      else
		{
		  if (strip_height < 0.1)
		    strip_height = 0.1;
		  DBG (3, "sane_init: config file line %d: strip-height set "
		       "to %g inches\n", linenumber, strip_height);
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "force-wait") == 0)
	    {
	      DBG (3, "sane_init: config file line %d: enabling force-wait\n",
		   linenumber);
	      force_wait = SANE_TRUE;
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "disable-double-buffering") == 0)
	    {
	      DBG (3, "sane_init: config file line %d: disabling "
		   "double-buffering\n", linenumber);
	      disable_double_buffering = SANE_TRUE;
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "legal-size") == 0)
	    {
	      if (new_dev_len > 0)
		{
		  /* Check for 12000 LS, no way to find out automatically */
		  if (strcmp (new_dev[new_dev_len - 1]->sane.model,
			      "ScanExpress 12000SP") == 0)
		    {
		      new_dev[new_dev_len - 1]->x_range.max =
			SANE_FIX (220.0);
		      new_dev[new_dev_len - 1]->y_range.max =
			SANE_FIX (360.0);
		      new_dev[new_dev_len - 1]->sane.model =
			"Paragon 1200 LS";
		      DBG (3,
			   "sane_init: config file line %d: enabling "
			   "legal-size for %s\n", linenumber,
			   new_dev[new_dev_len - 1]->sane.name);
		    }
		  else
		    {
		      DBG (3, "sane_init: config file line %d: option "
			   "legal-size ignored, device %s is not a "
			   "Paragon 1200 LS\n", linenumber,
			   new_dev[new_dev_len - 1]->sane.name);
		    }

		}
	      else
		{
		  DBG (3, "sane_init: config file line %d: option "
		       "legal-size ignored, was set before any device "
		       "name\n", linenumber);
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "linedistance-fix") == 0)
	    {
	      if (new_dev_len > 0)
		{
		  new_dev[new_dev_len - 1]->flags |= MUSTEK_FLAG_LD_FIX;
		  DBG (3, "sane_init: config file line %d: enabling "
		       "linedistance-fix for %s\n", linenumber,
		       new_dev[new_dev_len - 1]->sane.name);
		}
	      else
		{
		  DBG (3, "sane_init: config file line %d: option "
		       "linedistance-fix ignored, was set before any device "
		       "name\n", linenumber);
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "disable-backtracking") == 0)
	    {
	      if (new_dev_len > 0)
		{
		  new_dev[new_dev_len - 1]->flags |= MUSTEK_FLAG_NO_BACKTRACK;
		  DBG (3, "sane_init: config file line %d: disabling "
		       "backtracking for %s\n", linenumber,
		       new_dev[new_dev_len - 1]->sane.name);
		}
	      else
		{
		  DBG (3, "sane_init: config file line %d: option "
		       "disable-backtracking ignored, was set before any "
		       "device name\n", linenumber);
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "lineart-fix") == 0)
	    {
	      if (new_dev_len > 0)
		{
		  new_dev[new_dev_len - 1]->flags |= MUSTEK_FLAG_LINEART_FIX;
		  DBG (3, "sane_init: config file line %d: enabling "
		       "lineart-fix for %s\n", linenumber,
		       new_dev[new_dev_len - 1]->sane.name);
		}
	      else
		{
		  DBG (3, "sane_init: config file line %d: option "
		       "lineart-fix ignored, was set before any device name\n",
		       linenumber);
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "buffersize") == 0)
	    {
	      long buffer_size;

	      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;
	      buffer_size = strtol (word, &end, 0);

	      if (end == word)
		{
		  DBG (3, "sane-init: config file line %d: buffersize must "
		       "have a parameter; using default (%d kb)\n",
		       linenumber, new_dev[new_dev_len - 1]->max_buffer_size);
		}
	      if (errno)
		{
		  DBG (3, "sane-init: config file line %d: buffersize `%s' "
		       "is invalid (%s); using default (%d kb)\n", linenumber,
		       word, strerror (errno),
		       new_dev[new_dev_len - 1]->max_buffer_size);
		}
	      else
		{
		  if (new_dev_len > 0)
		    {
		      if (buffer_size < 32.0)
			buffer_size = 32.0;
		      new_dev[new_dev_len - 1]->max_buffer_size =
			buffer_size * 1024;
		      DBG (3,
			   "sane_init: config file line %d: buffersize set "
			   "to %ld kb for %s\n", linenumber, buffer_size,
			   new_dev[new_dev_len - 1]->sane.name);
		    }
		  else
		    {
		      DBG (3, "sane_init: config file line %d: option "
			   "buffersize ignored, was set before any device "
			   "name\n", linenumber);
		    }
		}
	      if (word)
		free (word);
	      word = 0;
	    }
	  else if (strcmp (word, "blocksize") == 0)
	    {
	      long block_size;

	      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;
	      block_size = strtol (word, &end, 0);

	      if (end == word)
		{
		  DBG (3, "sane-init: config file line %d:: blocksize must "
		       "have a parameter; using default (1 GB)\n",
		       linenumber);
		}
	      if (errno)
		{
		  DBG (3, "sane-init: config file line %d: blocksize `%s' "
		       "is invalid (%s); using default (1 GB)\n", linenumber,
		       word, strerror (errno));
		}
	      else
		{
		  if (new_dev_len > 0)
		    {
		      if (block_size < 256.0)
			block_size = 256.0;
		      new_dev[new_dev_len - 1]->max_block_buffer_size =
			block_size * 1024;
		      DBG (3, "sane_init: config file line %d: blocksize set "
			   "to %ld kb for %s\n", linenumber, block_size,
			   new_dev[new_dev_len - 1]->sane.name);
		    }
		  else
		    {
		      DBG (3, "sane_init: config file line %d: option "
			   "blocksize ignored, was set before any device "
			   "name\n", linenumber);
		    }
		}
	      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_config_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: end\n");
  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  Mustek_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;
  sanei_ab306_exit ();		/* may have to do some cleanup */
  mustek_scsi_pp_exit ();
  DBG (5, "sane_exit: finished\n");
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  Mustek_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)
{
  Mustek_Device *dev;
  SANE_Status status;
  Mustek_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, SANE_TRUE);
	  if (status != SANE_STATUS_GOOD)
	    return status;
	}
    }
  else
    /* empty devicname -> use first device */
    dev = first_dev;

  if (!dev)
    return SANE_STATUS_INVAL;

  s = malloc (sizeof (*s));
  if (!s)
    return SANE_STATUS_NO_MEM;
  memset (s, 0, sizeof (*s));
  s->fd = -1;
  s->pipe = -1;
  s->hw = dev;
  s->ld.ld_line = 0;
  s->halftone_pattern = malloc (8 * 8 * sizeof (SANE_Int));
  if (!s->halftone_pattern)
    return SANE_STATUS_NO_MEM;
  init_options (s);

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

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

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

  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->scanning)
    do_stop (handle);

  if (s->ld.buf[0])
    free (s->ld.buf[0]);
  if (s->val[OPT_MODE].s)
    free (s->val[OPT_MODE].s);
  if (s->val[OPT_BIT_DEPTH].s)
    free (s->val[OPT_BIT_DEPTH].s);
  if (s->val[OPT_SPEED].s)
    free (s->val[OPT_SPEED].s);
  if (s->val[OPT_SOURCE].s)
    free (s->val[OPT_SOURCE].s);
  if (s->val[OPT_HALFTONE_DIMENSION].s)
    free (s->val[OPT_HALFTONE_DIMENSION].s);
  if (s->halftone_pattern)
    free (s->halftone_pattern);
  if (prev)
    prev->next = s->next;
  else
    first_handle = s->next;
  free (handle);
  handle = 0;
  DBG (5, "sane_close: finished\n");
}

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

  if (((unsigned) option >= NUM_OPTIONS) || (option < 0))
    {
      DBG (4, "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 (5, "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 (5, "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)
{
  Mustek_Scanner *s = handle;
  SANE_Status status;
  SANE_Word w, cap;

  if (((unsigned) option >= NUM_OPTIONS) || (option < 0))
    {
      DBG (4, "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 (s->opt[option].type != SANE_TYPE_BUTTON && !val)
    {
      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 (5, "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 (5, "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 (4, "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 (4, "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_FAST_PREVIEW:
	case OPT_RESOLUTION:
	case OPT_FAST_GRAY_MODE:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_NUM_OPTS:
	case OPT_BRIGHTNESS:
	case OPT_BRIGHTNESS_R:
	case OPT_BRIGHTNESS_G:
	case OPT_BRIGHTNESS_B:
	case OPT_CONTRAST:
	case OPT_CONTRAST_R:
	case OPT_CONTRAST_G:
	case OPT_CONTRAST_B:
	case OPT_CUSTOM_GAMMA:
	case OPT_QUALITY_CAL:
	case OPT_LAMP_OFF_TIME:
	  *(SANE_Word *) val = s->val[option].w;
	  return SANE_STATUS_GOOD;

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

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

      status = constrain_value (s, 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)
	{
	case OPT_LAMP_OFF_BUTTON:
	  {
	    SANE_Int old_time = lamp_off_time;
	    SANE_Status status;

	    status = dev_open (s->hw->sane.name, s, sense_handler);
	    if (status != SANE_STATUS_GOOD)
	      return status;
	    lamp_off_time = 0;
	    set_window_pro (s);
	    lamp_off_time = old_time;
	    dev_close (s);
	    return SANE_STATUS_GOOD;
	  }
	  /* (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_PREVIEW:
	case OPT_FAST_PREVIEW:
	case OPT_FAST_GRAY_MODE:
	case OPT_BRIGHTNESS:
	case OPT_BRIGHTNESS_R:
	case OPT_BRIGHTNESS_G:
	case OPT_BRIGHTNESS_B:
	case OPT_CONTRAST:
	case OPT_CONTRAST_R:
	case OPT_CONTRAST_G:
	case OPT_CONTRAST_B:
	case OPT_QUALITY_CAL:
	case OPT_LAMP_OFF_TIME:
	  s->val[option].w = *(SANE_Word *) val;
	  return SANE_STATUS_GOOD;

	  /* side-effect-free word-array options: */
	case OPT_HALFTONE_PATTERN:
	case OPT_GAMMA_VECTOR:
	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;

	  /* side-effect-free single-string options: */
	case OPT_SPEED:
	  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;

	  return SANE_STATUS_GOOD;

	  /* side-effect-free string list options: */
	case OPT_BIT_DEPTH:
	  {
	    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);
	      }
	    s->val[option].s = strdup (val);
	    if (!s->val[option].s)
	      return SANE_STATUS_NO_MEM;
	    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;
	  if (w)
	    {
	      SANE_String_Const mode = s->val[OPT_MODE].s;

	      if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0)
		s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
	      else if (strcmp (mode, SANE_VALUE_SCAN_MODE_COLOR) == 0)
		{
		  s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		  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;
		}
	      else if ((strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0)
		       && (s->hw->flags & MUSTEK_FLAG_PRO))
		{
		  s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		}
	    }
	  else
	    {
	      s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	      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_MODE:
	  {
	    SANE_Char *old_val = s->val[option].s;
	    SANE_Int halftoning, binary;

	    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_BRIGHTNESS].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_BRIGHTNESS_R].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_BRIGHTNESS_G].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_BRIGHTNESS_B].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CONTRAST].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CONTRAST_R].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CONTRAST_G].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CONTRAST_B].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	    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_HALFTONE_DIMENSION].cap |= SANE_CAP_INACTIVE;
	    s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;

	    halftoning = strcmp (val, SANE_VALUE_SCAN_MODE_HALFTONE) == 0;
	    binary = (halftoning || strcmp (val, SANE_VALUE_SCAN_MODE_LINEART) == 0);

	    if (binary)
	      {
		/* enable brightness/contrast for  when in a binary mode */
		s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
		/* The SE and paragon models support only threshold
		   in lineart */
		if (!(s->hw->flags & MUSTEK_FLAG_SE)
		    && !(s->hw->flags & MUSTEK_FLAG_PRO))
		  s->opt[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;

		if (halftoning)
		  {
		    s->opt[OPT_HALFTONE_DIMENSION].cap &= ~SANE_CAP_INACTIVE;
		    encode_halftone (s);
		    if (s->custom_halftone_pattern)
		      {
			s->opt[OPT_HALFTONE_PATTERN].cap
			  &= ~SANE_CAP_INACTIVE;
		      }
		    else
		      {
			s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;
		      }
		  }
	      }
	    else
	      {
		s->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
	      }

	    if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	      {
		if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
		  {
		    s->opt[OPT_BRIGHTNESS_R].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_BRIGHTNESS_G].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_BRIGHTNESS_B].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_CONTRAST_R].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_CONTRAST_G].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_CONTRAST_B].cap &= ~SANE_CAP_INACTIVE;
		  }
		else
		  {
		    s->opt[OPT_BRIGHTNESS].cap &= ~SANE_CAP_INACTIVE;
		    s->opt[OPT_CONTRAST].cap &= ~SANE_CAP_INACTIVE;
		  }
	      }
	    else if (s->hw->flags & MUSTEK_FLAG_PRO)
	      {
		if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_GRAY) == 0)
		  s->opt[OPT_FAST_GRAY_MODE].cap &= ~SANE_CAP_INACTIVE;
		else
		  s->opt[OPT_FAST_GRAY_MODE].cap |= SANE_CAP_INACTIVE;
		if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
		  s->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE;
		else
		  s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
	      }
	    else if (s->hw->flags & MUSTEK_FLAG_SE_PLUS)
	      {
		if (strcmp (s->val[OPT_MODE].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
		  s->opt[OPT_BIT_DEPTH].cap &= ~SANE_CAP_INACTIVE;
		else
		  s->opt[OPT_BIT_DEPTH].cap |= SANE_CAP_INACTIVE;
	      }

	    if (s->val[OPT_CUSTOM_GAMMA].w)
	      {
		if (strcmp (val, SANE_VALUE_SCAN_MODE_GRAY) == 0)
		  s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		else if (strcmp (val, SANE_VALUE_SCAN_MODE_COLOR) == 0)
		  {
		    s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
		    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_HALFTONE_DIMENSION:
	  /* halftone pattern dimension affects halftone pattern option: */
	  {
	    if (strcmp (s->val[option].s, (SANE_String) val) == 0)
	      return SANE_STATUS_GOOD;	/* no change */

	    if (info)
	      *info |= SANE_INFO_RELOAD_OPTIONS;

	    s->val[option].s = strdup (val);
	    if (!s->val[option].s)
	      return SANE_STATUS_NO_MEM;
	    encode_halftone (s);
	    s->opt[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;
	    if (s->custom_halftone_pattern)
	      {
		s->opt[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;
		/* BUG: The SANE standard does nor allow to change the option
		   size at run time */
		s->opt[OPT_HALFTONE_PATTERN].size =
		  (s->halftone_pattern_type & 0x0f) * sizeof (SANE_Word);
	      }
	    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;
	}
    }
  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)
{
  Mustek_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 = SANE_UNFIX (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;
	}
      encode_halftone (s);
      mode = s->val[OPT_MODE].s;
      if (strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0 || strcmp (mode, SANE_VALUE_SCAN_MODE_HALFTONE) == 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, SANE_VALUE_SCAN_MODE_GRAY) == 0)
	{
	  s->params.format = SANE_FRAME_GRAY;
	  s->params.bytes_per_line = s->params.pixels_per_line;
	  s->params.depth = 8;
	}
      else
	{
	  /* it's one of the color modes... */

	  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	    {
	      s->params.format = SANE_FRAME_RED + s->pass;
	      s->params.bytes_per_line = s->params.pixels_per_line;
	      s->params.depth = 8;
	    }
	  else			/* 1-pass */
	    {
	      if (strcmp (s->val[OPT_BIT_DEPTH].s, "12") == 0)
		{
		  s->params.bytes_per_line = 6 * s->params.pixels_per_line;
		  s->params.depth = 16;
		}
	      else
		{
		  s->params.bytes_per_line = 3 * s->params.pixels_per_line;
		  s->params.depth = 8;
		}
	      s->params.format = SANE_FRAME_RGB;
	    }
	}
    }
  else if ((s->mode & MUSTEK_MODE_COLOR)
	   && (s->hw->flags & MUSTEK_FLAG_THREE_PASS))
    s->params.format = SANE_FRAME_RED + s->pass;
  s->params.last_frame = (s->params.format != SANE_FRAME_RED
			  && s->params.format != SANE_FRAME_GREEN);
  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)
{
  Mustek_Scanner *s = handle;
  SANE_Status status;
  int fds[2];
  struct SIGACTION act;

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

  DBG (4, "sane_start\n");
  /* First make sure we have a current parameter set.  Some of the
     parameters will be overwritten below, but that's OK.  */
  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;

  if (s->fd < 0)
    {
      /* this is the first (and maybe only) pass... */
      SANE_String_Const mode;
      struct timeval start;

      /* 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;
      if (strcmp (mode, SANE_VALUE_SCAN_MODE_LINEART) == 0)
	s->mode = MUSTEK_MODE_LINEART;
      else if (strcmp (mode, SANE_VALUE_SCAN_MODE_HALFTONE) == 0)
	s->mode = MUSTEK_MODE_HALFTONE;
      else if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0)
	s->mode = MUSTEK_MODE_GRAY;
      else if (strcmp (mode, SANE_VALUE_SCAN_MODE_COLOR) == 0)
	s->mode = MUSTEK_MODE_COLOR;

      /* scanner dependent specials */
      s->one_pass_color_scan = SANE_FALSE;
      if ((s->mode & MUSTEK_MODE_COLOR)
	  && !(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
	{
	  s->one_pass_color_scan = SANE_TRUE;
	}

      s->resolution_code = encode_resolution (s);

      if (s->val[OPT_PREVIEW].w && s->val[OPT_FAST_PREVIEW].w)
	{
	  if (s->hw->flags & MUSTEK_FLAG_THREE_PASS)
	    {
	      if (s->mode & MUSTEK_MODE_COLOR)
		{
		  /* Force gray-scale mode when previewing.  */
		  s->mode = MUSTEK_MODE_GRAY;
		  s->params.format = SANE_FRAME_GRAY;
		  s->params.bytes_per_line = s->params.pixels_per_line;
		  s->params.last_frame = SANE_TRUE;
		}
	    }
	  else if (s->hw->flags & MUSTEK_FLAG_SE)
	    {
	      /* use 36 dpi color in any case */
	      s->mode = MUSTEK_MODE_COLOR;
	      s->params.format = SANE_FRAME_RGB;
	      s->params.depth = 8;
	      s->one_pass_color_scan = SANE_TRUE;
	      s->resolution_code = 36;
	    }
	  else if (s->hw->flags & MUSTEK_FLAG_PARAGON_1)
	    {
	      /* use 36 dpi */
	      s->resolution_code = 36;
	    }
	  else if (s->hw->flags & MUSTEK_FLAG_PRO)
	    {
	      /* use 30 dpi color mode */
	      s->mode = MUSTEK_MODE_COLOR;
	      s->params.format = SANE_FRAME_RGB;
	      s->params.depth = 8;
	      s->one_pass_color_scan = SANE_TRUE;
	      s->resolution_code = 30;
	    }
	  DBG (4, "sane_start: use fast preview (res=%d dpi)\n",
	       s->resolution_code);
	}

      status = dev_open (s->hw->sane.name, s, sense_handler);
      if (status != SANE_STATUS_GOOD)
	return status;
    }

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

  if (!(s->hw->flags & MUSTEK_FLAG_SCSI_PP))
    {
      /* SCSI-over-parallel port doesn't seem to like being inquired here */
      status = inquiry (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: inquiry command failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}
    }

  if ((strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0) &&
      !(s->hw->flags & MUSTEK_FLAG_ADF_READY))
    {
      DBG (2, "sane_start: automatic document feeder is out of documents\n");
      status = SANE_STATUS_NO_DOCS;
      goto stop_scanner_and_return;
    }

  if (s->hw->flags & MUSTEK_FLAG_SE)
    {
      status = set_window_se (s, 0);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: set window command failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}

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

      dev_wait_ready (s);

      status = get_window (s, &s->params.bytes_per_line,
			   &s->params.lines, &s->params.pixels_per_line);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: get window command failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}

      status = calibration_se (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = start_scan (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = send_gamma_table_se (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;
    }

  else if (s->hw->flags & MUSTEK_FLAG_PRO)
    {

      status = dev_wait_ready (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = set_window_pro (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

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

      status = adf_and_backtrack (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = mode_select_pro (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = scsi_sense_wait_ready (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = calibration_pro (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = send_gamma_table (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = start_scan (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = get_image_status (s, &s->params.bytes_per_line,
				 &s->params.lines);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = scsi_sense_wait_ready (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;
    }

  else				/* Paragon series */
    {
      status = area_and_windows (s);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_start: set scan area command failed: %s\n",
	       sane_strstatus (status));
	  goto stop_scanner_and_return;
	}

      status = adf_and_backtrack (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      if (s->one_pass_color_scan)
	{
	  status = mode_select_paragon (s, MUSTEK_CODE_RED);
	  if (status != SANE_STATUS_GOOD)
	    goto stop_scanner_and_return;

	  status = mode_select_paragon (s, MUSTEK_CODE_GREEN);
	  if (status != SANE_STATUS_GOOD)
	    goto stop_scanner_and_return;

	  status = mode_select_paragon (s, MUSTEK_CODE_BLUE);
	}
      else
	status = mode_select_paragon (s, MUSTEK_CODE_GRAY);

      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

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

      status = send_gamma_table (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      status = start_scan (s);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      if (!(s->hw->flags & MUSTEK_FLAG_SCSI_PP))
	{
	  /* This second gamma table download upsets the SCSI-over-parallel models */
	  status = send_gamma_table (s);
	  if (status != SANE_STATUS_GOOD)
	    goto stop_scanner_and_return;
	}

      s->ld.max_value = 0;
      if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS))
	{
	  status = line_distance (s);
	  if (status != SANE_STATUS_GOOD)
	    goto stop_scanner_and_return;
	}

      status = get_image_status (s, &s->params.bytes_per_line,
				 &s->params.lines);
      if (status != SANE_STATUS_GOOD)
	goto stop_scanner_and_return;

      if ((strcmp (s->val[OPT_SOURCE].s, "Automatic Document Feeder") == 0)
	  && (s->hw->flags & MUSTEK_FLAG_PARAGON_2))
	{
	  status = paragon_2_get_adf_status (s);
	  if (status != SANE_STATUS_GOOD)
	    goto stop_scanner_and_return;
	}
    }

  s->params.pixels_per_line = s->params.bytes_per_line;
  if (s->one_pass_color_scan)
    {
      if (strcmp (s->val[OPT_BIT_DEPTH].s, "12") == 0)
	s->params.pixels_per_line /= 6;
      else
	s->params.pixels_per_line /= 3;
    }
  else if ((s->mode & MUSTEK_MODE_LINEART)
	   || (s->mode & MUSTEK_MODE_HALFTONE))
    s->params.pixels_per_line *= 8;

  s->line = 0;

  /* don't call any SIGTERM or SIGCHLD handlers
     this is to stop xsane and other frontends from calling
     its quit handlers */
  memset (&act, 0, sizeof (act));
  sigaction (SIGTERM, &act, 0);
  sigaction (SIGCHLD, &act, 0);

  if (pipe (fds) < 0)
    return SANE_STATUS_IO_ERROR;

  s->reader_fds = fds[1];

  /* create reader routine as new process or thread */
  s->reader_pid = sanei_thread_begin (reader_process, (void *) s);

  if (!sanei_thread_is_valid (s->reader_pid))
    {
      DBG (1, "sane_start: sanei_thread_begin failed (%s)\n",
	   strerror (errno));
      return SANE_STATUS_NO_MEM;
    }

  if (sanei_thread_is_forked ())
    {
      close (s->reader_fds);
      s->reader_fds = -1;
    }

  s->pipe = fds[0];

  return SANE_STATUS_GOOD;

stop_scanner_and_return:
  do_stop (s);
  return status;
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len,
	   SANE_Int * len)
{
  Mustek_Scanner *s = handle;
  SANE_Status status;
  ssize_t nread;


  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 (3, "sane_read: must call sane_start before sane_read\n");
      return SANE_STATUS_INVAL;
    }

  while (*len < max_len)
    {
      nread = read (s->pipe, buf + *len, max_len - *len);

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

      if (nread < 0)
	{
	  if (errno == EAGAIN)
	    {
	      if (*len == 0)
		DBG (5, "sane_read: no more data at the moment--try again\n");
	      else
		DBG (5, "sane_read: read buffer of %d bytes "
		     "(%d bytes total)\n", *len, s->total_bytes);
	      return SANE_STATUS_GOOD;
	    }
	  else
	    {
	      DBG (1, "sane_read: IO error\n");
	      do_stop (s);
	      *len = 0;
	      return SANE_STATUS_IO_ERROR;
	    }
	}

      *len += nread;
      s->total_bytes += nread;

      if (nread == 0)
	{
	  if (*len == 0)
	    {
	      if (!(s->hw->flags & MUSTEK_FLAG_THREE_PASS)
		  || !(s->mode & MUSTEK_MODE_COLOR) || ++s->pass >= 3)
		{
		  DBG (5, "sane_read: pipe was closed ... calling do_stop\n");
		  status = do_stop (s);
		  if (status != SANE_STATUS_CANCELLED
		      && status != SANE_STATUS_GOOD)
		    return status;	/* something went wrong */
		}
	      else		/* 3pass color first or second pass */
		{
		  DBG (5,
		       "sane_read: pipe was closed ... finishing pass %d\n",
		       s->pass);
		}

	      return do_eof (s);
	    }
	  else
	    {
	      DBG (5, "sane_read: read last buffer of %d bytes "
		   "(%d bytes total)\n", *len, s->total_bytes);
	      return SANE_STATUS_GOOD;
	    }
	}
    }
  DBG (5, "sane_read: read full buffer of %d bytes (%d total bytes)\n",
       *len, s->total_bytes);
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  Mustek_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;
      do_stop (handle);
    }
  DBG (5, "sane_cancel: finished\n");
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  Mustek_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 (fcntl (s->pipe, F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
    {
      DBG (1, "sane_set_io_mode: can't set io mode");
      return SANE_STATUS_IO_ERROR;
    }

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  Mustek_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;

  *fd = s->pipe;
  return SANE_STATUS_GOOD;
}

#include "mustek_scsi_pp.c"
