/***************************************************************************
 * SANE - Scanner Access Now Easy.

   microtek.c

   This file Copyright 2002 Matthew Marjanovic

   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 Microtek scanners.

   (feedback to:  mtek-bugs@mir.com)
   (for latest info:  http://www.mir.com/mtek/)

 ***************************************************************************/


#define MICROTEK_MAJOR 0
#define MICROTEK_MINOR 13
#define MICROTEK_PATCH 1

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

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>

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

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

#define BACKEND_NAME microtek
#include "../include/sane/sanei_backend.h"

#include "microtek.h"


#define MICROTEK_CONFIG_FILE "microtek.conf"

#ifndef PATH_MAX
# define PATH_MAX	1024
#endif


#define SCSI_BUFF_SIZE sanei_scsi_max_request_size


#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))

static int num_devices = 0;
static Microtek_Device *first_dev = NULL;     /* list of known devices */
static Microtek_Scanner *first_handle = NULL; /* list of open scanners */
static const SANE_Device **devlist = NULL;    /* sane_get_devices() */


static SANE_Bool inhibit_clever_precal = SANE_FALSE;
static SANE_Bool inhibit_real_calib = SANE_FALSE;


#define M_GSS_WAIT 5 /* seconds */

#define M_LINEART  SANE_VALUE_SCAN_MODE_LINEART
#define M_HALFTONE SANE_VALUE_SCAN_MODE_HALFTONE
#define M_GRAY     SANE_VALUE_SCAN_MODE_GRAY
#define M_COLOR    SANE_VALUE_SCAN_MODE_COLOR

#define M_OPAQUE   "Opaque/Normal"
#define M_TRANS    "Transparency"
#define M_AUTOFEED "AutoFeeder"

#define M_NONE   "None"
#define M_SCALAR "Scalar"
#define M_TABLE  "Table"

static SANE_String_Const gamma_mode_list[4] = {
  M_NONE,
  M_SCALAR,
  M_TABLE,
  NULL
};


/* These are for the E6.  Does this hold for other models? */
static SANE_String_Const halftone_mode_list[13] = {
  " 1 53-dot screen (53 gray levels)",
  " 2 Horiz. screen (65 gray levels)",
  " 3 Vert. screen (65 gray levels)",
  " 4 Mixed page (33 gray levels)",
  " 5 71-dot screen (29 gray levels)",
  " 6 60-dot #1 (26 gray levels)",
  " 7 60-dot #2 (26 gray levels)",
  " 8 Fine detail #1 (17 gray levels)",
  " 9 Fine detail #2 (17 gray levels)",
  "10 Slant line (17 gray levels)",
  "11 Posterizing (10 gray levels)",
  "12 High Contrast (5 gray levels)",
  NULL
};



static SANE_Range speed_range = {1, 7, 1};

static SANE_Range brightness_range = {-100, 100, 1};
/*static SANE_Range brightness_range = {0, 255, 1};*/
/*static SANE_Range exposure_range = {-18, 21, 3};*/
/*static SANE_Range contrast_range = {-42, 49, 7};*/
static SANE_Range u8_range = {0, 255, 1};
static SANE_Range analog_gamma_range =
{ SANE_FIX(0.1), SANE_FIX(4.0), SANE_FIX(0) };




#define MAX_MDBG_LENGTH 1024
static char _mdebug_string[MAX_MDBG_LENGTH];

static void MDBG_INIT(const char *format, ...)
{
  va_list ap;
  va_start(ap, format);
  vsnprintf(_mdebug_string, MAX_MDBG_LENGTH, format, ap);
  va_end(ap);
}

static void MDBG_ADD(const char *format, ...)
{
  int len = strlen(_mdebug_string);
  va_list ap;
  va_start(ap, format);
  vsnprintf(_mdebug_string+len, MAX_MDBG_LENGTH-len, format, ap);
  va_end(ap);
}

static void MDBG_FINISH(int dbglvl)
{
  DBG(dbglvl, "%s\n", _mdebug_string);
}



/********************************************************************/
/********************************************************************/
/*** Utility Functions **********************************************/
/********************************************************************/
/********************************************************************/

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

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



/********************************************************************/
/* Allocate/create a new ring buffer                                */
/********************************************************************/
static ring_buffer *
ring_alloc (size_t initial_size, size_t bpl, size_t ppl)
{
  ring_buffer *rb;
  uint8_t *buff;

  if ((rb = (ring_buffer *)malloc(sizeof(*rb))) == NULL)
    return NULL;
  if ((buff = (uint8_t *)malloc(initial_size * sizeof(*buff))) == NULL) {
    free(rb);
    return NULL;
  }
  rb->base = buff;
  rb->size = initial_size;
  rb->initial_size = initial_size;

  rb->bpl = bpl;
  rb->ppl = ppl;

  rb->tail_red   = 0;
  rb->tail_green = 1;
  rb->tail_blue  = 2;
  rb->head_complete = 0;

  rb->red_extra   = 0;
  rb->green_extra = 0;
  rb->blue_extra  = 0;
  rb->complete_count = 0;

  return rb;
}


/********************************************************************/
/* Enlarge an existing ring buffer                                  */
/********************************************************************/
static SANE_Status
ring_expand (ring_buffer *rb, size_t amount)
{
  uint8_t *buff;
  size_t oldsize;

  if (rb == NULL) return SANE_STATUS_INVAL;
  buff = (uint8_t *)realloc(rb->base, (rb->size + amount) * sizeof(*buff));
  if (buff == NULL) return SANE_STATUS_NO_MEM;

  rb->base = buff;
  oldsize = rb->size;
  rb->size += amount;

  DBG(23, "ring_expand:  old, new, inc size:  %lu, %lu, %lu\n",
      (u_long)oldsize, (u_long)rb->size, (u_long)amount);
  DBG(23, "ring_expand:  old  tr: %lu  tg: %lu  tb: %lu  hc: %lu\n",
      (u_long)rb->tail_red, (u_long)rb->tail_green,
      (u_long)rb->tail_blue, (u_long)rb->head_complete);
  /* if necessary, move data and fix up them pointers */
  /* (will break subtly if ring is filled with G or B bytes,
     and tail_g or tail_b have rolled over...) */
  if (((rb->complete_count) ||
       (rb->red_extra) ||
       (rb->green_extra) ||
       (rb->blue_extra)) && ((rb->tail_red <= rb->head_complete) ||
			     (rb->tail_green <= rb->head_complete) ||
			     (rb->tail_blue <= rb->head_complete))) {
    memmove(rb->base + rb->head_complete + amount,
	    rb->base + rb->head_complete,
	    oldsize - rb->head_complete);
    if ((rb->tail_red > rb->head_complete) ||
	((rb->tail_red == rb->head_complete) &&
	 !(rb->complete_count) && !(rb->red_extra)))
      rb->tail_red += amount;
    if ((rb->tail_green > rb->head_complete) ||
	((rb->tail_green == rb->head_complete) &&
	 !(rb->complete_count) && !(rb->green_extra)))
      rb->tail_green += amount;
    if ((rb->tail_blue > rb->head_complete) ||
	((rb->tail_blue == rb->head_complete) &&
	 !(rb->complete_count) && !(rb->blue_extra)))
      rb->tail_blue += amount;
    rb->head_complete += amount;
  }
  DBG(23, "ring_expand:  new  tr: %lu  tg: %lu  tb: %lu  hc: %lu\n",
      (u_long)rb->tail_red, (u_long)rb->tail_green,
      (u_long)rb->tail_blue, (u_long)rb->head_complete);
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Deallocate a ring buffer                                         */
/********************************************************************/
static void
ring_free (ring_buffer *rb)
{
  free(rb->base);
  free(rb);
}



/********************************************************************/
/********************************************************************/
/*** Basic SCSI Commands ********************************************/
/********************************************************************/
/********************************************************************/


/********************************************************************/
/* parse sense from scsi error                                      */
/*  (even though microtek sense codes are non-standard and          */
/*   typically misinterpreted/munged by the low-level scsi driver)  */
/********************************************************************/
static SANE_Status
sense_handler (int scsi_fd, u_char *sense, void *arg)
{
  int *sense_flags = (int *)arg;
  SANE_Status stat;

  DBG(10, "SENSE!  fd = %d\n", scsi_fd);
  DBG(10, "sense = %02x %02x %02x %02x.\n",
            sense[0], sense[1], sense[2], sense[3]);
  switch(sense[0]) {
  case 0x00:
    return SANE_STATUS_GOOD;
  case 0x81:           /* COMMAND/DATA ERROR */
    stat = SANE_STATUS_GOOD;
    if (sense[1] & 0x01) {
      if ((sense_flags != NULL) && (*sense_flags & MS_SENSE_IGNORE))
	DBG(10, "sense:  ERR_SCSICMD -- ignored\n");
      else {
	DBG(10, "sense:  ERR_SCSICMD\n");
	stat = SANE_STATUS_IO_ERROR;
      }
    }
    if (sense[1] & 0x02) {
      DBG(10, "sense:  ERR_TOOMANY\n");
      stat = SANE_STATUS_IO_ERROR;
    }
    return stat;
  case 0x82 :           /* SCANNER HARDWARE ERROR */
    if (sense[1] & 0x01) DBG(10, "sense:  ERR_CPURAMFAIL\n");
    if (sense[1] & 0x02) DBG(10, "sense:  ERR_SYSRAMFAIL\n");
    if (sense[1] & 0x04) DBG(10, "sense:  ERR_IMGRAMFAIL\n");
    if (sense[1] & 0x10) DBG(10, "sense:  ERR_CALIBRATE\n");
    if (sense[1] & 0x20) DBG(10, "sense:  ERR_LAMPFAIL\n");
    if (sense[1] & 0x40) DBG(10, "sense:  ERR_MOTORFAIL\n");
    if (sense[1] & 0x80) DBG(10, "sense:  ERR_FEEDERFAIL\n");
    if (sense[2] & 0x01) DBG(10, "sense:  ERR_POWERFAIL\n");
    if (sense[2] & 0x02) DBG(10, "sense:  ERR_ILAMPFAIL\n");
    if (sense[2] & 0x04) DBG(10, "sense:  ERR_IMOTORFAIL\n");
    if (sense[2] & 0x08) DBG(10, "sense:  ERR_PAPERFAIL\n");
    if (sense[2] & 0x10) DBG(10, "sense:  ERR_FILTERFAIL\n");
    return SANE_STATUS_IO_ERROR;
  case 0x83 :           /* OPERATION ERROR */
    if (sense[1] & 0x01) DBG(10, "sense:  ERR_ILLGRAIN\n");
    if (sense[1] & 0x02) DBG(10, "sense:  ERR_ILLRES\n");
    if (sense[1] & 0x04) DBG(10, "sense:  ERR_ILLCOORD\n");
    if (sense[1] & 0x10) DBG(10, "sense:  ERR_ILLCNTR\n");
    if (sense[1] & 0x20) DBG(10, "sense:  ERR_ILLLENGTH\n");
    if (sense[1] & 0x40) DBG(10, "sense:  ERR_ILLADJUST\n");
    if (sense[1] & 0x80) DBG(10, "sense:  ERR_ILLEXPOSE\n");
    if (sense[2] & 0x01) DBG(10, "sense:  ERR_ILLFILTER\n");
    if (sense[2] & 0x02) DBG(10, "sense:  ERR_NOPAPER\n");
    if (sense[2] & 0x04) DBG(10, "sense:  ERR_ILLTABLE\n");
    if (sense[2] & 0x08) DBG(10, "sense:  ERR_ILLOFFSET\n");
    if (sense[2] & 0x10) DBG(10, "sense:  ERR_ILLBPP\n");
    return SANE_STATUS_IO_ERROR;
  default :
    DBG(10, "sense: unknown error\n");
    return SANE_STATUS_IO_ERROR;
  }
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* wait (via polling) until scanner seems "ready"                   */
/********************************************************************/
static SANE_Status
wait_ready(Microtek_Scanner *ms)
{
  uint8_t comm[6] = { 0, 0, 0, 0, 0, 0 };
  SANE_Status status;
  int retry = 0;

  DBG(23, ".wait_ready %d...\n", ms->sfd);
  while ((status = sanei_scsi_cmd(ms->sfd, comm, 6, 0, 0))
	 != SANE_STATUS_GOOD) {
    DBG(23, "wait_ready failed (%d)\n", retry);
    if (retry > 5) return SANE_STATUS_IO_ERROR; /* XXXXXXXX */
    retry++;
    sleep(3);
  }
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* send scan region coordinates                                     */
/********************************************************************/
static SANE_Status
scanning_frame(Microtek_Scanner *ms)
{
  uint8_t *data, comm[15] = { 0x04, 0, 0, 0, 0x09, 0 };
  int x1, y1, x2, y2;

  DBG(23, ".scanning_frame...\n");

  x1 = ms->x1;
  x2 = ms->x2;
  y1 = ms->y1;
  y2 = ms->y2;
  /* E6 weirdness (other models too?) */
  if (ms->unit_type == MS_UNIT_18INCH) {
    x1 /= 2;
    x2 /= 2;
    y1 /= 2;
    y2 /= 2;
  }

  DBG(23, ".scanning_frame:  in- %d,%d  %d,%d\n",
      ms->x1, ms->y1, ms->x2, ms->y2);
  DBG(23, ".scanning_frame: out- %d,%d  %d,%d\n", x1, y1, x2, y2);
  data = comm + 6;
  data[0] =
    ((ms->unit_type == MS_UNIT_PIXELS) ? 0x08 : 0 ) |
    ((ms->mode == MS_MODE_HALFTONE) ? 0x01 : 0 );
  data[1] = x1 & 0xFF;
  data[2] = (x1 >> 8) & 0xFF;
  data[3] = y1 & 0xFF;
  data[4] = (y1 >> 8) & 0xFF;
  data[5] = x2 & 0xFF;
  data[6] = (x2 >> 8) & 0xFF;
  data[7] = y2 & 0xFF;
  data[8] = (y2 >> 8) & 0xFF;
  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "SF: ");
    for (i=0;i<6+0x09;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("SF: ");
    for (i=0;i<6+0x09;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6 + 0x09, 0, 0);
}



/********************************************************************/
/* send "mode_select"                                               */
/********************************************************************/
static SANE_Status
mode_select(Microtek_Scanner *ms)
{
  uint8_t *data, comm[19] = { 0x15, 0, 0, 0, 0, 0 };

  DBG(23, ".mode_select %d...\n", ms->sfd);
  data = comm + 6;
  data[0] =
    0x81 |
    ((ms->unit_type == MS_UNIT_18INCH) ? 0 : 0x08) |
    ((ms->res_type == MS_RES_5PER) ? 0 : 0x02);
  data[1] = ms->resolution_code;
  data[2] = ms->exposure;
  data[3] = ms->contrast;
  data[4] = ms->pattern;
  data[5] = ms->velocity;
  data[6] = ms->shadow;
  data[7] = ms->highlight;
  DBG(23, ".mode_select:  pap_len: %d\n", ms->paper_length);
  data[8] = ms->paper_length & 0xFF;
  data[9] = (ms->paper_length >> 8) & 0xFF;
  data[10] = ms->midtone;
  /* set command/data length */
  comm[4] = (ms->midtone_support) ? 0x0B : 0x0A;

  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "MSL: ");
    for (i=0;i<6+comm[4];i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("MSL: ");
    for (i=0;i<6+comm[4];i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6 + comm[4], 0, 0);
}



/********************************************************************/
/* send "mode_select_1"                                             */
/********************************************************************/
static SANE_Status
mode_select_1(Microtek_Scanner *ms)
{
  uint8_t *data, comm[16] = { 0x16, 0, 0, 0, 0x0A, 0,
                               0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

  DBG(23, ".mode_select_1 %d...\n", ms->sfd);
  data = comm + 6;
  data[1] = ms->bright_r;
  data[3] = ((ms->allow_calibrate) ? 0 : 0x02); /* | 0x01; */

  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "MSL1: ");
    for (i=0;i<6+0x0A;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("MSL1: ");
    for (i=0;i<6+0x0A;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6 + 0x0A, 0, 0);
}



/********************************************************************/
/* record mode_sense results in the mode_sense buffer               */
/*  (this is to tell if something catastrophic has happened         */
/*   to the scanner in-between scans)                               */
/********************************************************************/
static SANE_Status
save_mode_sense(Microtek_Scanner *ms)
{
  uint8_t data[20], comm[6] = { 0x1A, 0, 0, 0, 0, 0};
  size_t lenp;
  SANE_Status status;
  int i;

  DBG(23, ".save_mode_sense %d...\n", ms->sfd);
  if (ms->onepass) comm[4] = 0x13;
  else if (ms->midtone_support) comm[4] = 0x0B;
  else comm[4] = 0x0A;
  lenp = comm[4];

  status = sanei_scsi_cmd(ms->sfd, comm, 6, data, &lenp);
  for (i=0; i<10; i++) ms->mode_sense_cache[i] = data[i];

  if (DBG_LEVEL >= 192) {
    unsigned int i;
#if 0
    fprintf(stderr, "SMS: ");
    for (i=0;i<lenp;i++) fprintf(stderr, "%2x ", data[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("SMS: ");
    for (i=0;i<lenp;i++) MDBG_ADD("%2x ", data[i]);
    MDBG_FINISH(192);
  }

  return status;
}


/********************************************************************/
/* read mode_sense and compare to what we saved before              */
/********************************************************************/
static SANE_Status
compare_mode_sense(Microtek_Scanner *ms, int *match)
{
  uint8_t data[20], comm[6] = { 0x1A, 0, 0, 0, 0, 0};
  size_t lenp;
  SANE_Status status;
  int i;

  DBG(23, ".compare_mode_sense %d...\n", ms->sfd);
  if (ms->onepass) comm[4] = 0x13;
  else if (ms->midtone_support) comm[4] = 0x0B;
  else comm[4] = 0x0A;
  lenp = comm[4];

  status = sanei_scsi_cmd(ms->sfd, comm, 6, data, &lenp);
  *match = 1;
  for (i=0; i<10; i++)
    *match = *match && (ms->mode_sense_cache[i] == data[i]);

  if (DBG_LEVEL >= 192) {
    unsigned int i;
#if 0
    fprintf(stderr, "CMS: ");
    for (i=0;i<lenp;i++) fprintf(stderr, "%2x(%2x) ",
				 data[i],
				 ms->mode_sense_cache[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("CMS: ");
    for (i=0;i<lenp;i++) MDBG_ADD("%2x(%2x) ",
				  data[i],
				  ms->mode_sense_cache[i]);
    MDBG_FINISH(192);
  }

  return status;
}

/********************************************************************/
/* send mode_sense_1, and upset every scsi driver known to mankind  */
/********************************************************************/
#if 0
static SANE_Status
mode_sense_1(Microtek_Scanner *ms)
{
  uint8_t *data, comm[36] = { 0x19, 0, 0, 0, 0x1E, 0 };

  DBG(23, ".mode_sense_1...\n");
  data = comm + 6;
  memset(data, 0, 30);
  data[1] = ms->bright_r;
  data[2] = ms->bright_g;
  data[3] = ms->bright_b;
  if (DBG_LEVEL >= 192) {
    int i;
    fprintf(stderr, "MS1: ");
    for (i=0;i<6+0x1E;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6 + 0x1E, 0, 0);
}
#endif



/********************************************************************/
/* send "accessory" command                                         */
/********************************************************************/
static SANE_Status
accessory(Microtek_Scanner *ms)
{
  uint8_t comm[6] = { 0x10, 0, 0, 0, 0, 0 };

  DBG(23, ".accessory...\n");
  comm[4] =
    ((ms->useADF) ? 0x41 : 0x40) |
    ((ms->prescan) ? 0x18 : 0x10) |
    ((ms->transparency) ? 0x24 : 0x20) |
    ((ms->allowbacktrack) ? 0x82 : 0x80);

  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "AC: ");
    for (i=0;i<6;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("AC: ");
    for (i=0;i<6;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6, 0, 0);
}



/********************************************************************/
/* start the scanner a-scannin'                                     */
/********************************************************************/
static SANE_Status
start_scan(Microtek_Scanner *ms)
{
  uint8_t comm[6] = { 0x1B, 0, 0, 0, 0, 0 };

  DBG(23, ".start_scan...\n");
  comm[4] =
    0x01 |  /* "start" */
    ((ms->expandedresolution) ? 0x80 : 0) |
    ((ms->multibit) ? 0x40 : 0) |
    ((ms->onepasscolor) ? 0x20 : 0) |
    ((ms->reversecolors) ? 0x04 : 0) |
    ((ms->fastprescan) ? 0x02 : 0) |
    ((ms->filter == MS_FILT_RED) ? 0x08 : 0) |
    ((ms->filter == MS_FILT_GREEN) ? 0x10 : 0) |
    ((ms->filter == MS_FILT_BLUE) ? 0x18 : 0) ;

  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "SS: ");
    for (i=0;i<6;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("SS: ");
    for (i=0;i<6;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6, 0, 0);
}



/********************************************************************/
/* stop the scanner a-scannin'                                      */
/********************************************************************/
static SANE_Status
stop_scan(Microtek_Scanner *ms)
{
  uint8_t comm[6] = { 0x1B, 0, 0, 0, 0, 0 };

  DBG(23, ".stop_scan...\n");
  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "SPS:");
    for (i=0;i<6;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("SPS:");
    for (i=0;i<6;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 6, 0, 0);
}



/********************************************************************/
/* get scan status                                                  */
/********************************************************************/
static SANE_Status
get_scan_status(Microtek_Scanner *ms,
		SANE_Int *busy,
		SANE_Int *bytes_per_line,
		SANE_Int *lines)
{
  uint8_t data[6], comm[6] = { 0x0F, 0, 0, 0, 0x06, 0 };
  SANE_Status status;
  size_t lenp;
  int retry = 0;

  DBG(23, ".get_scan_status %d...\n", ms->sfd);

  do {
    lenp = 6;
    /* do some retry stuff in here, too */
    status = sanei_scsi_cmd(ms->sfd, comm, 6, data, &lenp);
    if (status != SANE_STATUS_GOOD) {
      DBG(20, "get_scan_status:  scsi error\n");
      return status;
    }
    *busy = data[0];
    *bytes_per_line = (data[1]) + (data[2] << 8);
    *lines = (data[3]) + (data[4] << 8) + (data[5] << 16);

    DBG(20, "get_scan_status(%lu): %d, %d, %d  -> #%d\n",
	(u_long) lenp, *busy, *bytes_per_line, *lines, retry);
    DBG(20, "> %2x %2x %2x %2x %2x %2x\n",
	    data[0], data[1], data[2], data[3], data[4], data[5]);
    if (*busy != 0) {
      retry++;
      DBG(23, "get_scan_status:  busy, retry in %d...\n",
	  M_GSS_WAIT * retry);
      sleep(M_GSS_WAIT * retry);
    }
  } while ((*busy != 0) && (retry < 4));

  if (*busy == 0) return status;
  else return SANE_STATUS_IO_ERROR;
}



/********************************************************************/
/* get scanlines from scanner                                       */
/********************************************************************/
static SANE_Status
read_scan_data(Microtek_Scanner *ms,
	       int lines,
	       uint8_t *buffer,
	       size_t *bufsize)
{
  uint8_t comm[6] = { 0x08, 0, 0, 0, 0, 0 };

  DBG(23, ".read_scan_data...\n");
  comm[2] = (lines >> 16) & 0xFF;
  comm[3] = (lines >> 8) & 0xFF;
  comm[4] = (lines) & 0xFF;

  return sanei_scsi_cmd(ms->sfd, comm, 6, buffer, bufsize);
}



/********************************************************************/
/* download color LUT to scanner (if it takes one)                  */
/********************************************************************/
static SANE_Status
download_gamma(Microtek_Scanner *ms)
{
  uint8_t *data, *comm; /* commbytes[10] = { 0x55, 0, 0x27, 0, 0,
						0, 0,    0, 0, 0 };*/
  int i, pl;
  int commsize;
  int bit_depth = 8; /* hard-code for now, should match bpp XXXXXXX */
  int max_entry;
  SANE_Status status;

  DBG(23, ".download_gamma...\n");
  /* skip if scanner doesn't take 'em */
  if (!(ms->gamma_entries)) {
    DBG(23, ".download_gamma:  no entries; skipping\n");
    return SANE_STATUS_GOOD;
  }
  if ((ms->gamma_entry_size != 1) && (ms->gamma_entry_size != 2)) {
    DBG(23, ".download_gamma:  entry size %d?!?!?\n", ms->gamma_entry_size);
    return SANE_STATUS_INVAL; /* XXXXXXXxx */
  }

  max_entry = (1 << bit_depth) - 1;

  DBG(23, ".download_gamma:  %d entries of %d bytes, max %d\n",
      ms->gamma_entries, ms->gamma_entry_size, max_entry);
  commsize = 10 + (ms->gamma_entries * ms->gamma_entry_size);
  comm = calloc(commsize, sizeof(uint8_t));
  if (comm == NULL) {
    DBG(23, ".download_gamma:  couldn't allocate %d bytes for comm buffer!\n",
	commsize);
    return SANE_STATUS_NO_MEM;
  }
  data = comm + 10;

  comm[0] = 0x55;
  comm[1] = 0;
  comm[2] = 0x27;
  comm[3] = 0;
  comm[4] = 0;
  comm[5] = 0;
  comm[6] = 0;
  comm[7] = ((ms->gamma_entries * ms->gamma_entry_size) >> 8) & 0xFF;
  comm[8] = (ms->gamma_entries * ms->gamma_entry_size) & 0xFF;
  comm[9] = (ms->gamma_entry_size == 2) ? 1 : 0;

  if (!(strcmp(ms->val[OPT_CUSTOM_GAMMA].s, M_TABLE))) {
    /***** Gamma by TABLE *****/
    int table_shift = (ms->gamma_bit_depth - bit_depth);

    DBG(23, ".download_gamma: by table (%d bpe, %d shift)\n",
	ms->gamma_bit_depth, table_shift);

    if (ms->val[OPT_GAMMA_BIND].w == SANE_TRUE) {
      for (i=0; i<ms->gamma_entries; i++) {
	int val = ms->gray_lut[i] >> table_shift;
	switch (ms->gamma_entry_size) {
	case 1:
	  data[i] = (uint8_t) val;
	  break;
	case 2:
	  data[i*2] =  val & 0xFF;
	  data[(i*2)+1] = (val>>8) & 0xFF;
	  break;
	}
      }
      status = sanei_scsi_cmd(ms->sfd, comm, commsize, 0, 0);
    } else {
      pl = 1;
      do {
	SANE_Int *pl_lut;
	switch (pl) {
	case 1: pl_lut = ms->red_lut;   break;
	case 2: pl_lut = ms->green_lut; break;
	case 3: pl_lut = ms->blue_lut;  break;
	default:
	  DBG(23, ".download_gamma:  uh, exceeded pl bound!\n");
	  free(comm);
	  return SANE_STATUS_INVAL; /* XXXXXXXxx */
	  break;
	}
	for (i=0; i<ms->gamma_entries; i++) {
	  int val = pl_lut[i] >> table_shift;
	  switch (ms->gamma_entry_size) {
	  case 1:
	    data[i] = (uint8_t) val;
	    break;
	  case 2:
	    data[i*2] =  val & 0xFF;
	    data[(i*2)+1] =  (val>>8) & 0xFF;
	    break;
	  }
	}
	/* XXXXXXX */
	comm[9] = (comm[9] & 0x3F) | (pl << 6);
	status = sanei_scsi_cmd(ms->sfd, comm, commsize, 0, 0);
	pl++;
      } while ((pl < 4) && (status == SANE_STATUS_GOOD));
    }
  } else if (!(strcmp(ms->val[OPT_CUSTOM_GAMMA].s, M_SCALAR))) {
    /***** Gamma by SCALAR *****/
    DBG(23, ".download_gamma: by scalar\n");
    if (ms->val[OPT_GAMMA_BIND].w == SANE_TRUE) {
      double gamma = SANE_UNFIX(ms->val[OPT_ANALOG_GAMMA].w);
      for (i=0; i<ms->gamma_entries; i++) {
	int val =  (max_entry *
		    pow((double) i / ((double) ms->gamma_entries - 1.0),
			1.0 / gamma));
	switch (ms->gamma_entry_size) {
	case 1:
	  data[i] = (uint8_t) val;
	  break;
	case 2:
	  data[i*2] = val & 0xFF;
	  data[(i*2)+1] = (val>>8) & 0xFF;
	  break;
	}
      }
      status = sanei_scsi_cmd(ms->sfd, comm, commsize, 0, 0);
    } else {
      double gamma;
      pl = 1;
      do {
	switch (pl) {
	case 1: gamma = SANE_UNFIX(ms->val[OPT_ANALOG_GAMMA_R].w); break;
	case 2: gamma = SANE_UNFIX(ms->val[OPT_ANALOG_GAMMA_G].w); break;
	case 3: gamma = SANE_UNFIX(ms->val[OPT_ANALOG_GAMMA_B].w); break;
	default: gamma = 1.0; break; /* should never happen */
	}
	for (i=0; i<ms->gamma_entries; i++) {
	  int val =  (max_entry *
		      pow((double) i / ((double) ms->gamma_entries - 1.0),
			  1.0 / gamma));
	  switch (ms->gamma_entry_size) {
	    case 1:
	      data[i] = (uint8_t) val;
	      break;
	  case 2:
	    data[i*2] = val & 0xFF;
	    data[(i*2)+1] = (val>>8) & 0xFF;
	    break;
	  }
	}
	comm[9] = (comm[9] & 0x3F) | (pl << 6);
	status = sanei_scsi_cmd(ms->sfd, comm, commsize, 0, 0);
	pl++;
      } while ((pl < 4) && (status == SANE_STATUS_GOOD));
    }
  } else {
    /***** No custom Gamma *****/
    DBG(23, ".download_gamma: by default\n");
    for (i=0; i<ms->gamma_entries; i++) {
      /*      int val =  (((double) max_entry * (double) i /
	      ((double) ms->gamma_entries - 1.0)) + 0.5);     ROUNDING????*/
      int val =
	(double) max_entry * (double) i /
	((double) ms->gamma_entries - 1.0);
      switch (ms->gamma_entry_size) {
      case 1:
	data[i] = (uint8_t) val;
	break;
      case 2:
	data[i*2] = val & 0xFF;
	data[(i*2)+1] = (val >> 8) & 0xFF;
	break;
      }
    }
    status = sanei_scsi_cmd(ms->sfd, comm, commsize, 0, 0);
  }
  free(comm);
  return status;
}


/********************************************************************/
/* magic command to start calibration                               */
/********************************************************************/
static SANE_Status
start_calibration(Microtek_Scanner *ms)
{
  uint8_t comm[8] = { 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x0a };

  DBG(23, ".start_calibrate...\n");
  if (DBG_LEVEL >= 192) {
    int i;
#if 0
    fprintf(stderr, "STCal:");
    for (i=0;i<8;i++) fprintf(stderr, "%2x ", comm[i]);
    fprintf(stderr, "\n");
#endif
    MDBG_INIT("STCal:");
    for (i=0;i<8;i++) MDBG_ADD("%2x ", comm[i]);
    MDBG_FINISH(192);
  }
  return sanei_scsi_cmd(ms->sfd, comm, 8, 0, 0);
}



/********************************************************************/
/* magic command to download calibration values                     */
/********************************************************************/
static SANE_Status
download_calibration(Microtek_Scanner *ms, uint8_t *comm,
		     uint8_t letter, int linewidth)
{
  DBG(23, ".download_calibration... %c %d\n", letter, linewidth);

  comm[0] = 0x0c;
  comm[1] = 0x00;
  comm[2] = 0x00;
  comm[3] = (linewidth >> 8) & 0xFF;
  comm[4] = linewidth & 0xFF;
  comm[5] = 0x00;

  comm[6] = 0x00;
  switch (letter) {
  case 'R': comm[7] = 0x40; break;
  case 'G': comm[7] = 0x80; break;
  case 'B': comm[7] = 0xc0; break;
  default: /* XXXXXXX */ break;
  }

  return sanei_scsi_cmd(ms->sfd, comm, 6 + linewidth, 0, 0);
}



/********************************************************************/
/********************************************************************/
/*                                                                  */
/* Myriad of internal functions                                     */
/*                                                                  */
/********************************************************************/
/********************************************************************/



/********************************************************************/
/* Initialize the options registry                                  */
/********************************************************************/
static SANE_Status
init_options(Microtek_Scanner *ms)
{
  int i;
  SANE_Option_Descriptor *sod = ms->sod;
  Option_Value *val = ms->val;

  DBG(15, "init_options...\n");

  memset(ms->sod, 0, sizeof(ms->sod));
  memset(ms->val, 0, sizeof(ms->val));
  /* default:  software selectable word options... */
  for (i=0; i<NUM_OPTIONS; i++) {
    sod[i].size = sizeof(SANE_Word);
    sod[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  sod[OPT_NUM_OPTS].name =   SANE_NAME_NUM_OPTIONS;
  sod[OPT_NUM_OPTS].title =  SANE_TITLE_NUM_OPTIONS;
  sod[OPT_NUM_OPTS].desc =   SANE_DESC_NUM_OPTIONS;
  sod[OPT_NUM_OPTS].type =   SANE_TYPE_INT;
  sod[OPT_NUM_OPTS].unit =   SANE_UNIT_NONE;
  sod[OPT_NUM_OPTS].size =   sizeof (SANE_Word);
  sod[OPT_NUM_OPTS].cap =    SANE_CAP_SOFT_DETECT;
  sod[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE;

  /* The Scan Mode Group */
  sod[OPT_MODE_GROUP].name  = "";
  sod[OPT_MODE_GROUP].title = "Scan Mode";
  sod[OPT_MODE_GROUP].desc  = "";
  sod[OPT_MODE_GROUP].type  = SANE_TYPE_GROUP;
  sod[OPT_MODE_GROUP].cap   = 0;
  sod[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  sod[OPT_MODE].name = SANE_NAME_SCAN_MODE;
  sod[OPT_MODE].title = SANE_TITLE_SCAN_MODE;
  sod[OPT_MODE].desc = SANE_DESC_SCAN_MODE;
  sod[OPT_MODE].type = SANE_TYPE_STRING;
  sod[OPT_MODE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *mode_list;
    mode_list = (SANE_String_Const *) malloc(5 * sizeof(SANE_String_Const));
    if (mode_list == NULL) return SANE_STATUS_NO_MEM;
    i = 0;
    if (ms->dev->info.modes & MI_MODES_COLOR)    mode_list[i++] = M_COLOR;
    if (ms->dev->info.modes & MI_MODES_GRAY)     mode_list[i++] = M_GRAY;
    if (ms->dev->info.modes & MI_MODES_HALFTONE) mode_list[i++] = M_HALFTONE;
    if (ms->dev->info.modes & MI_MODES_LINEART)  mode_list[i++] = M_LINEART;
    mode_list[i] = NULL;
    sod[OPT_MODE].constraint.string_list = mode_list;
    sod[OPT_MODE].size                   = max_string_size(mode_list);
    val[OPT_MODE].s = strdup(mode_list[0]);
  }

  sod[OPT_RESOLUTION].name  = SANE_NAME_SCAN_RESOLUTION;
  sod[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  sod[OPT_RESOLUTION].desc  = SANE_DESC_SCAN_RESOLUTION;
  sod[OPT_RESOLUTION].type  = SANE_TYPE_FIXED;
  sod[OPT_RESOLUTION].unit  = SANE_UNIT_DPI;
  sod[OPT_RESOLUTION].constraint_type  = SANE_CONSTRAINT_RANGE;
  {
    SANE_Int maxres = ms->dev->info.base_resolution;

    ms->res_range.max = SANE_FIX(maxres);
    ms->exp_res_range.max = SANE_FIX(2 * maxres);
    if (ms->dev->info.res_step & MI_RESSTEP_1PER) {
      DBG(23, "init_options:  quant yes\n");
      ms->res_range.min = SANE_FIX( maxres / 100 );
      ms->res_range.quant = ms->res_range.min;
      ms->exp_res_range.min = SANE_FIX(2 * maxres / 100);
      ms->exp_res_range.quant = ms->exp_res_range.min;
    } else {
      /* XXXXXXXXXXX */
      DBG(23, "init_options:  quant no\n");
      ms->res_range.quant = SANE_FIX( 0 );
    }
    sod[OPT_RESOLUTION].constraint.range = &(ms->res_range);
  }
  val[OPT_RESOLUTION].w     = SANE_FIX(100);

  sod[OPT_HALFTONE_PATTERN].name = SANE_NAME_HALFTONE_PATTERN;
  sod[OPT_HALFTONE_PATTERN].title = SANE_TITLE_HALFTONE_PATTERN;
  sod[OPT_HALFTONE_PATTERN].desc = SANE_DESC_HALFTONE_PATTERN;
  sod[OPT_HALFTONE_PATTERN].type = SANE_TYPE_STRING;
  sod[OPT_HALFTONE_PATTERN].size = max_string_size(halftone_mode_list);
  sod[OPT_HALFTONE_PATTERN].cap  |= SANE_CAP_INACTIVE;
  sod[OPT_HALFTONE_PATTERN].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  sod[OPT_HALFTONE_PATTERN].constraint.string_list = halftone_mode_list;
  val[OPT_HALFTONE_PATTERN].s = strdup(halftone_mode_list[0]);

  sod[OPT_NEGATIVE].name  = SANE_NAME_NEGATIVE;
  sod[OPT_NEGATIVE].title = SANE_TITLE_NEGATIVE;
  sod[OPT_NEGATIVE].desc  = SANE_DESC_NEGATIVE;
  sod[OPT_NEGATIVE].type  = SANE_TYPE_BOOL;
  sod[OPT_NEGATIVE].cap   |=
    (ms->dev->info.modes & MI_MODES_NEGATIVE) ? 0 : SANE_CAP_INACTIVE;
  val[OPT_NEGATIVE].w     = SANE_FALSE;

  sod[OPT_SPEED].name  = SANE_NAME_SCAN_SPEED;
  sod[OPT_SPEED].title = SANE_TITLE_SCAN_SPEED;
  /*  sod[OPT_SPEED].desc  = SANE_DESC_SCAN_SPEED;*/
  sod[OPT_SPEED].desc  = "Scan speed throttle -- higher values are *slower*.";
  sod[OPT_SPEED].type  = SANE_TYPE_INT;
  sod[OPT_SPEED].cap   |= SANE_CAP_ADVANCED;
  sod[OPT_SPEED].unit  = SANE_UNIT_NONE;
  sod[OPT_SPEED].size  = sizeof(SANE_Word);
  sod[OPT_SPEED].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_SPEED].constraint.range = &speed_range;
  val[OPT_SPEED].w     = 1;

  sod[OPT_SOURCE].name  = SANE_NAME_SCAN_SOURCE;
  sod[OPT_SOURCE].title = SANE_TITLE_SCAN_SOURCE;
  sod[OPT_SOURCE].desc  = SANE_DESC_SCAN_SOURCE;
  sod[OPT_SOURCE].type  = SANE_TYPE_STRING;
  sod[OPT_SOURCE].unit  = SANE_UNIT_NONE;
  sod[OPT_SOURCE].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *source_list;
    source_list = (SANE_String_Const *) malloc(4 * sizeof(SANE_String_Const));
    if (source_list == NULL) return SANE_STATUS_NO_MEM;
    i = 0;
    source_list[i++] = M_OPAQUE;
    if (ms->dev->info.source_options & MI_SRC_HAS_TRANS)
      source_list[i++] = M_TRANS;
    if (ms->dev->info.source_options & MI_SRC_HAS_FEED)
      source_list[i++] = M_AUTOFEED;
    source_list[i] = NULL;
    sod[OPT_SOURCE].constraint.string_list = source_list;
    sod[OPT_SOURCE].size                   = max_string_size(source_list);
    if (i < 2)
      sod[OPT_SOURCE].cap |= SANE_CAP_INACTIVE;
    val[OPT_SOURCE].s = strdup(source_list[0]);
  }

  sod[OPT_PREVIEW].name  = SANE_NAME_PREVIEW;
  sod[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  sod[OPT_PREVIEW].desc  = SANE_DESC_PREVIEW;
  sod[OPT_PREVIEW].type  = SANE_TYPE_BOOL;
  sod[OPT_PREVIEW].unit  = SANE_UNIT_NONE;
  sod[OPT_PREVIEW].size  = sizeof(SANE_Word);
  val[OPT_PREVIEW].w     = SANE_FALSE;


  sod[OPT_GEOMETRY_GROUP].name  = "";
  sod[OPT_GEOMETRY_GROUP].title = "Geometry";
  sod[OPT_GEOMETRY_GROUP].desc  = "";
  sod[OPT_GEOMETRY_GROUP].type  = SANE_TYPE_GROUP;
  sod[OPT_GEOMETRY_GROUP].cap   = SANE_CAP_ADVANCED;
  sod[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;


  sod[OPT_TL_X].name  = SANE_NAME_SCAN_TL_X;
  sod[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  sod[OPT_TL_X].desc  = SANE_DESC_SCAN_TL_X;
  sod[OPT_TL_X].type  = SANE_TYPE_FIXED;
  sod[OPT_TL_X].unit  = SANE_UNIT_MM;
  sod[OPT_TL_X].size  = sizeof(SANE_Word);
  sod[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;

  sod[OPT_TL_Y].name  = SANE_NAME_SCAN_TL_Y;
  sod[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  sod[OPT_TL_Y].desc  = SANE_DESC_SCAN_TL_Y;
  sod[OPT_TL_Y].type  = SANE_TYPE_FIXED;
  sod[OPT_TL_Y].unit  = SANE_UNIT_MM;
  sod[OPT_TL_Y].size  = sizeof(SANE_Word);
  sod[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;

  sod[OPT_BR_X].name  = SANE_NAME_SCAN_BR_X;
  sod[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  sod[OPT_BR_X].desc  = SANE_DESC_SCAN_BR_X;
  sod[OPT_BR_X].type  = SANE_TYPE_FIXED;
  sod[OPT_BR_X].unit  = SANE_UNIT_MM;
  sod[OPT_BR_X].size  = sizeof(SANE_Word);
  sod[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;

  sod[OPT_BR_Y].name  = SANE_NAME_SCAN_BR_Y;
  sod[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  sod[OPT_BR_Y].desc  = SANE_DESC_SCAN_BR_Y;
  sod[OPT_BR_Y].type  = SANE_TYPE_FIXED;
  sod[OPT_BR_Y].unit  = SANE_UNIT_MM;
  sod[OPT_BR_Y].size  = sizeof(SANE_Word);
  sod[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;

  sod[OPT_TL_X].constraint.range =
    sod[OPT_BR_X].constraint.range = &(ms->dev->info.doc_x_range);
  sod[OPT_TL_Y].constraint.range =
    sod[OPT_BR_Y].constraint.range = &(ms->dev->info.doc_y_range);

  /* set default scan region to be maximum size */
  val[OPT_TL_X].w     = sod[OPT_TL_X].constraint.range->min;
  val[OPT_TL_Y].w     = sod[OPT_TL_Y].constraint.range->min;
  val[OPT_BR_X].w     = sod[OPT_BR_X].constraint.range->max;
  val[OPT_BR_Y].w     = sod[OPT_BR_Y].constraint.range->max;

  sod[OPT_ENHANCEMENT_GROUP].name  = "";
  sod[OPT_ENHANCEMENT_GROUP].title = "Enhancement";
  sod[OPT_ENHANCEMENT_GROUP].desc  = "";
  sod[OPT_ENHANCEMENT_GROUP].type  = SANE_TYPE_GROUP;
  sod[OPT_ENHANCEMENT_GROUP].cap   = 0;
  sod[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  sod[OPT_EXPOSURE].name  = "exposure";
  sod[OPT_EXPOSURE].title = "Exposure";
  sod[OPT_EXPOSURE].desc  = "Analog exposure control";
  sod[OPT_EXPOSURE].type  = SANE_TYPE_INT;
  sod[OPT_EXPOSURE].unit  = SANE_UNIT_PERCENT;
  sod[OPT_EXPOSURE].size  = sizeof(SANE_Word);
  sod[OPT_EXPOSURE].constraint_type = SANE_CONSTRAINT_RANGE;
  ms->exposure_range.min = ms->dev->info.min_exposure;
  ms->exposure_range.max = ms->dev->info.max_exposure;
  ms->exposure_range.quant = 3;
  sod[OPT_EXPOSURE].constraint.range      = &(ms->exposure_range);
  val[OPT_EXPOSURE].w     = 0;

  sod[OPT_BRIGHTNESS].name  = SANE_NAME_BRIGHTNESS;
  sod[OPT_BRIGHTNESS].title = SANE_TITLE_BRIGHTNESS;
  sod[OPT_BRIGHTNESS].desc  = SANE_DESC_BRIGHTNESS;
  sod[OPT_BRIGHTNESS].type  = SANE_TYPE_INT;
  sod[OPT_BRIGHTNESS].unit  = SANE_UNIT_PERCENT;
  sod[OPT_BRIGHTNESS].size  = sizeof(SANE_Word);
  sod[OPT_BRIGHTNESS].cap   |=
    ((ms->dev->info.extra_cap & MI_EXCAP_OFF_CTL) ? 0: SANE_CAP_INACTIVE);
  sod[OPT_BRIGHTNESS].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_BRIGHTNESS].constraint.range      = &brightness_range;
  val[OPT_BRIGHTNESS].w     = 0;

  sod[OPT_CONTRAST].name  = SANE_NAME_CONTRAST;
  sod[OPT_CONTRAST].title = SANE_TITLE_CONTRAST;
  sod[OPT_CONTRAST].desc  = SANE_DESC_CONTRAST;
  sod[OPT_CONTRAST].type  = SANE_TYPE_INT;
  sod[OPT_CONTRAST].unit  = SANE_UNIT_PERCENT;
  sod[OPT_CONTRAST].size  = sizeof(SANE_Word);
  sod[OPT_CONTRAST].constraint_type = SANE_CONSTRAINT_RANGE;
  ms->contrast_range.min = ms->dev->info.min_contrast;
  ms->contrast_range.max = ms->dev->info.max_contrast;
  ms->contrast_range.quant = 7;
  sod[OPT_CONTRAST].constraint.range      = &(ms->contrast_range);
  val[OPT_CONTRAST].w     = 0;


  sod[OPT_HIGHLIGHT].name  = SANE_NAME_WHITE_LEVEL;
  sod[OPT_HIGHLIGHT].title = SANE_TITLE_WHITE_LEVEL;
  sod[OPT_HIGHLIGHT].desc  = SANE_DESC_WHITE_LEVEL;
  sod[OPT_HIGHLIGHT].type  = SANE_TYPE_INT;
  sod[OPT_HIGHLIGHT].unit  = SANE_UNIT_NONE;
  sod[OPT_HIGHLIGHT].size  = sizeof(SANE_Word);
  sod[OPT_HIGHLIGHT].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_HIGHLIGHT].constraint.range      = &u8_range;
  val[OPT_HIGHLIGHT].w     = 255;

  sod[OPT_SHADOW].name  = SANE_NAME_BLACK_LEVEL;
  sod[OPT_SHADOW].title = SANE_TITLE_BLACK_LEVEL;
  sod[OPT_SHADOW].desc  = SANE_DESC_BLACK_LEVEL;
  sod[OPT_SHADOW].type  = SANE_TYPE_INT;
  sod[OPT_SHADOW].unit  = SANE_UNIT_NONE;
  sod[OPT_SHADOW].size  = sizeof(SANE_Word);
  sod[OPT_SHADOW].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_SHADOW].constraint.range      = &u8_range;
  val[OPT_SHADOW].w     = 0;

  if (ms->dev->info.enhance_cap & MI_ENH_CAP_SHADOW) {
    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_ADVANCED;
    sod[OPT_SHADOW].cap    |= SANE_CAP_ADVANCED;
  } else {
    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
    sod[OPT_SHADOW].cap    |= SANE_CAP_INACTIVE;
  }

  sod[OPT_MIDTONE].name  = "midtone";
  sod[OPT_MIDTONE].title = "Midtone Level";
  sod[OPT_MIDTONE].desc  = "Midtone Level";
  sod[OPT_MIDTONE].type  = SANE_TYPE_INT;
  sod[OPT_MIDTONE].unit  = SANE_UNIT_NONE;
  sod[OPT_MIDTONE].size  = sizeof(SANE_Word);
  sod[OPT_MIDTONE].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_MIDTONE].constraint.range      = &u8_range;
  val[OPT_MIDTONE].w     = 128;
  if (ms->midtone_support) {
    sod[OPT_MIDTONE].cap   |= SANE_CAP_ADVANCED;
  } else {
    sod[OPT_MIDTONE].cap   |= SANE_CAP_INACTIVE;
  }
  /* XXXXXXXX is this supported by all scanners??
  if ((strcmp(M_COLOR, val[OPT_MODE].s)) &&
      (strcmp(M_GRAY, val[OPT_MODE].s))) {
    sod[OPT_HIGHLIGHT].cap |= SANE_CAP_INACTIVE;
    sod[OPT_SHADOW].cap    |= SANE_CAP_INACTIVE;
    sod[OPT_MIDTONE].cap   |= SANE_CAP_INACTIVE;
  }
  */

  sod[OPT_GAMMA_GROUP].name  = "";
  sod[OPT_GAMMA_GROUP].title = "Gamma Control";
  sod[OPT_GAMMA_GROUP].desc  = "";
  sod[OPT_GAMMA_GROUP].type  = SANE_TYPE_GROUP;
  if (!(ms->gamma_entries))
    sod[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
  sod[OPT_GAMMA_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  sod[OPT_CUSTOM_GAMMA].name  = "gamma-mode";
  sod[OPT_CUSTOM_GAMMA].title = "Gamma Control Mode";
  sod[OPT_CUSTOM_GAMMA].desc  = "How to specify gamma correction, if at all";
  sod[OPT_CUSTOM_GAMMA].type  = SANE_TYPE_STRING;
  sod[OPT_CUSTOM_GAMMA].size  = max_string_size(gamma_mode_list);
  sod[OPT_CUSTOM_GAMMA].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  sod[OPT_CUSTOM_GAMMA].constraint.string_list = gamma_mode_list;
  if (!(ms->gamma_entries))
    sod[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
  val[OPT_CUSTOM_GAMMA].s = strdup(gamma_mode_list[0]);

  sod[OPT_GAMMA_BIND].name  = SANE_NAME_ANALOG_GAMMA_BIND;
  sod[OPT_GAMMA_BIND].title = SANE_TITLE_ANALOG_GAMMA_BIND;
  sod[OPT_GAMMA_BIND].desc  = SANE_DESC_ANALOG_GAMMA_BIND;
  sod[OPT_GAMMA_BIND].type  = SANE_TYPE_BOOL;
  sod[OPT_GAMMA_BIND].cap   |= SANE_CAP_INACTIVE;
  val[OPT_GAMMA_BIND].w     = SANE_TRUE;

  sod[OPT_ANALOG_GAMMA].name  = SANE_NAME_ANALOG_GAMMA;
  sod[OPT_ANALOG_GAMMA].title = SANE_TITLE_ANALOG_GAMMA;
  sod[OPT_ANALOG_GAMMA].desc  = SANE_DESC_ANALOG_GAMMA;
  sod[OPT_ANALOG_GAMMA].type  = SANE_TYPE_FIXED;
  sod[OPT_ANALOG_GAMMA].unit  = SANE_UNIT_NONE;
  sod[OPT_ANALOG_GAMMA].size  = sizeof(SANE_Word);
  sod[OPT_ANALOG_GAMMA].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_ANALOG_GAMMA].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_ANALOG_GAMMA].constraint.range      = &analog_gamma_range;
  val[OPT_ANALOG_GAMMA].w     = SANE_FIX(1.0);

  sod[OPT_ANALOG_GAMMA_R].name  = SANE_NAME_ANALOG_GAMMA_R;
  sod[OPT_ANALOG_GAMMA_R].title = SANE_TITLE_ANALOG_GAMMA_R;
  sod[OPT_ANALOG_GAMMA_R].desc  = SANE_DESC_ANALOG_GAMMA_R;
  sod[OPT_ANALOG_GAMMA_R].type  = SANE_TYPE_FIXED;
  sod[OPT_ANALOG_GAMMA_R].unit  = SANE_UNIT_NONE;
  sod[OPT_ANALOG_GAMMA_R].size  = sizeof(SANE_Word);
  sod[OPT_ANALOG_GAMMA_R].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_ANALOG_GAMMA_R].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_ANALOG_GAMMA_R].constraint.range      = &analog_gamma_range;
  val[OPT_ANALOG_GAMMA_R].w     = SANE_FIX(1.0);

  sod[OPT_ANALOG_GAMMA_G].name  = SANE_NAME_ANALOG_GAMMA_G;
  sod[OPT_ANALOG_GAMMA_G].title = SANE_TITLE_ANALOG_GAMMA_G;
  sod[OPT_ANALOG_GAMMA_G].desc  = SANE_DESC_ANALOG_GAMMA_G;
  sod[OPT_ANALOG_GAMMA_G].type  = SANE_TYPE_FIXED;
  sod[OPT_ANALOG_GAMMA_G].unit  = SANE_UNIT_NONE;
  sod[OPT_ANALOG_GAMMA_G].size  = sizeof(SANE_Word);
  sod[OPT_ANALOG_GAMMA_G].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_ANALOG_GAMMA_G].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_ANALOG_GAMMA_G].constraint.range      = &analog_gamma_range;
  val[OPT_ANALOG_GAMMA_G].w     = SANE_FIX(1.0);

  sod[OPT_ANALOG_GAMMA_B].name  = SANE_NAME_ANALOG_GAMMA_B;
  sod[OPT_ANALOG_GAMMA_B].title = SANE_TITLE_ANALOG_GAMMA_B;
  sod[OPT_ANALOG_GAMMA_B].desc  = SANE_DESC_ANALOG_GAMMA_B;
  sod[OPT_ANALOG_GAMMA_B].type  = SANE_TYPE_FIXED;
  sod[OPT_ANALOG_GAMMA_B].unit  = SANE_UNIT_NONE;
  sod[OPT_ANALOG_GAMMA_B].size  = sizeof(SANE_Word);
  sod[OPT_ANALOG_GAMMA_B].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_ANALOG_GAMMA_B].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_ANALOG_GAMMA_B].constraint.range      = &analog_gamma_range;
  val[OPT_ANALOG_GAMMA_B].w     = SANE_FIX(1.0);

  sod[OPT_GAMMA_VECTOR].name  = SANE_NAME_GAMMA_VECTOR;
  sod[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
  sod[OPT_GAMMA_VECTOR].desc  = SANE_DESC_GAMMA_VECTOR;
  sod[OPT_GAMMA_VECTOR].type  = SANE_TYPE_INT;
  sod[OPT_GAMMA_VECTOR].unit  = SANE_UNIT_NONE;
  sod[OPT_GAMMA_VECTOR].size  = ms->gamma_entries * sizeof(SANE_Word);
  sod[OPT_GAMMA_VECTOR].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_GAMMA_VECTOR].constraint.range      = &(ms->gamma_entry_range);
  val[OPT_GAMMA_VECTOR].wa     = ms->gray_lut;

  sod[OPT_GAMMA_VECTOR_R].name  = SANE_NAME_GAMMA_VECTOR_R;
  sod[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  sod[OPT_GAMMA_VECTOR_R].desc  = SANE_DESC_GAMMA_VECTOR_R;
  sod[OPT_GAMMA_VECTOR_R].type  = SANE_TYPE_INT;
  sod[OPT_GAMMA_VECTOR_R].unit  = SANE_UNIT_NONE;
  sod[OPT_GAMMA_VECTOR_R].size  = ms->gamma_entries * sizeof(SANE_Word);
  sod[OPT_GAMMA_VECTOR_R].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_GAMMA_VECTOR_R].constraint.range      = &(ms->gamma_entry_range);
  val[OPT_GAMMA_VECTOR_R].wa     = ms->red_lut;

  sod[OPT_GAMMA_VECTOR_G].name  = SANE_NAME_GAMMA_VECTOR_G;
  sod[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  sod[OPT_GAMMA_VECTOR_G].desc  = SANE_DESC_GAMMA_VECTOR_G;
  sod[OPT_GAMMA_VECTOR_G].type  = SANE_TYPE_INT;
  sod[OPT_GAMMA_VECTOR_G].unit  = SANE_UNIT_NONE;
  sod[OPT_GAMMA_VECTOR_G].size  = ms->gamma_entries * sizeof(SANE_Word);
  sod[OPT_GAMMA_VECTOR_G].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_GAMMA_VECTOR_G].constraint.range      = &(ms->gamma_entry_range);
  val[OPT_GAMMA_VECTOR_G].wa     = ms->green_lut;

  sod[OPT_GAMMA_VECTOR_B].name  = SANE_NAME_GAMMA_VECTOR_B;
  sod[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  sod[OPT_GAMMA_VECTOR_B].desc  = SANE_DESC_GAMMA_VECTOR_B;
  sod[OPT_GAMMA_VECTOR_B].type  = SANE_TYPE_INT;
  sod[OPT_GAMMA_VECTOR_B].unit  = SANE_UNIT_NONE;
  sod[OPT_GAMMA_VECTOR_B].size  = ms->gamma_entries * sizeof(SANE_Word);
  sod[OPT_GAMMA_VECTOR_B].cap   |= SANE_CAP_INACTIVE;
  sod[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  sod[OPT_GAMMA_VECTOR_B].constraint.range      = &(ms->gamma_entry_range);
  val[OPT_GAMMA_VECTOR_B].wa     = ms->blue_lut;

  sod[OPT_EXP_RES].name  = "exp_res";
  sod[OPT_EXP_RES].title = "Expanded Resolution";
  sod[OPT_EXP_RES].desc  = "Enable double-resolution scans";
  sod[OPT_EXP_RES].type  = SANE_TYPE_BOOL;
  sod[OPT_EXP_RES].cap   |= SANE_CAP_ADVANCED;
  if (!(ms->dev->info.expanded_resolution))
    sod[OPT_EXP_RES].cap |= SANE_CAP_INACTIVE;
  val[OPT_EXP_RES].w     = SANE_FALSE;

  sod[OPT_CALIB_ONCE].name  = "calib_once";
  sod[OPT_CALIB_ONCE].title = "Calibrate Only Once";
  sod[OPT_CALIB_ONCE].desc  = "Avoid CCD calibration on every scan" \
    "(toggle off/on to cause calibration on next scan)";
  sod[OPT_CALIB_ONCE].type  = SANE_TYPE_BOOL;
  sod[OPT_CALIB_ONCE].cap   |= SANE_CAP_ADVANCED;
  if (!(ms->do_real_calib)) {
    sod[OPT_CALIB_ONCE].cap |= SANE_CAP_INACTIVE;
    val[OPT_CALIB_ONCE].w     = SANE_FALSE;
  } else
    val[OPT_CALIB_ONCE].w     = SANE_TRUE;

  /*
  sod[OPT_].name  = SANE_NAME_;
  sod[OPT_].title = SANE_TITLE_;
  sod[OPT_].desc  = SANE_DESC_;
  sod[OPT_].type  = SANE_TYPE_;
  sod[OPT_].unit  = SANE_UNIT_NONE;
  sod[OPT_].size  = sizeof(SANE_Word);
  sod[OPT_].cap   = 0;
  sod[OPT_].constraint_type = SANE_CONSTRAINT_NONE;
  sod[OPT_].constraint      = ;
  val[OPT_].w     = ;
  */

  DBG(15, "init_options:  done.\n");
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Parse an INQUIRY information block, as returned by scanner       */
/********************************************************************/
static SANE_Status
parse_inquiry(Microtek_Info *mi, unsigned char *result)
{
#if 0
  unsigned char result[0x60] = {
    0x06,0x31,0x23,0x01,0x5b,0x00,0x00,0x00,0x41,0x47,0x46,0x41,0x20,0x20,0x20,0x20,
    0x53,0x74,0x75,0x64,0x69,0x6f,0x53,0x63,0x61,0x6e,0x20,0x49,0x49,0x20,0x20,0x20,
    0x32,0x2e,0x33,0x30,0x53,0x43,0x53,0x49,0x20,0x46,0x2f,0x57,0x56,0x33,0x2e,0x31,
    0x20,0x43,0x54,0x4c,0x35,0x33,0x38,0x30,0x03,0x4f,0x8c,0xc5,0x00,0xee,0x5b,0x43,
    0x01,0x01,0x02,0x00,0x00,0x03,0x00,0x01,0x00,0x4a,0x01,0x04,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff
  };
#endif
  DBG(15, "parse_inquiry...\n");
  strncpy(mi->vendor_id, (char *)&result[8], 8);
  strncpy(mi->model_name, (char *)&result[16], 16);
  strncpy(mi->revision_num, (char *)&result[32], 4);
  strncpy(mi->vendor_string, (char *)&result[36], 20);
  mi->vendor_id[8]   = 0;
  mi->model_name[16] = 0;
  mi->revision_num[4] = 0;
  mi->vendor_string[20] = 0;

  mi->device_type                = (SANE_Byte)(result[0] & 0x1f);
  mi->SCSI_firmware_ver_major    = (SANE_Byte)((result[1] & 0xf0) >> 4);
  mi->SCSI_firmware_ver_minor    = (SANE_Byte)(result[1] & 0x0f);
  mi->scanner_firmware_ver_major = (SANE_Byte)((result[2] & 0xf0) >> 4);
  mi->scanner_firmware_ver_minor = (SANE_Byte)(result[2] & 0x0f);
  mi->response_data_format       = (SANE_Byte)(result[3]);

  mi->res_step                   = (SANE_Byte)(result[56] & 0x03);
  mi->modes                      = (SANE_Byte)(result[57]);
  mi->pattern_count              = (SANE_Int)(result[58] & 0x7f);
  mi->pattern_dwnld              = (SANE_Byte)(result[58] & 0x80) > 0;
  mi->feed_type                  = (SANE_Byte)(result[59] & 0x0F);
  mi->compress_type              = (SANE_Byte)(result[59] & 0x30);
  mi->unit_type                  = (SANE_Byte)(result[59] & 0xC0);

  mi->doc_size_code              = (SANE_Byte)result[60];
  /* we'll compute the max sizes after we know base resolution... */

  /* why are these things set in two places (and probably wrong anyway)? */
  mi->cont_settings              = (SANE_Int)((result[61] & 0xf0) >> 4);
  if ((SANE_Int)(result[72]))
    mi->cont_settings            = (SANE_Int)(result[72]);
  mi->min_contrast = -42;
  mi->max_contrast = (mi->cont_settings * 7) - 49;

  mi->exp_settings               = (SANE_Int)(result[61] & 0x0f);
  if ((SANE_Int)(result[73]))
    mi->exp_settings             = (SANE_Int)(result[73]);
  mi->min_exposure  = -18;
  mi->max_exposure  = (mi->exp_settings * 3) - 21;
#if 0
  mi->contrast_vals              = (SANE_Int)(result[72]);
  mi->min_contrast = -42;
  mi->max_contrast =  49;
  if (mi->contrast_vals)
    mi->max_contrast = (mi->contrast_vals * 7) - 49;

  mi->exposure_vals              = (SANE_Int)(result[73]);
  mi->min_exposure  = -18;
  mi->max_exposure  =  21;
  if (mi->exposure_vals)
    mi->max_exposure  = (mi->exposure_vals * 3) - 21;
#endif

  mi->model_code                 = (SANE_Byte)(result[62]);
  switch (mi->model_code) {
  case 0x16: /* the other ScanMaker 600ZS */
  case 0x50: /* ScanMaker II/IIXE */
  case 0x54: /* ScanMaker IISP    */
  case 0x55: /* ScanMaker IIER    */
  case 0x58: /* ScanMaker IIG     */
  case 0x5a: /* Agfa StudioScan (untested!)    */
  case 0x5f: /* ScanMaker E3      */
  case 0x56: /* ScanMaker A3t     */
  case 0x64: /* ScanMaker E2 (,Vobis RealScan) */
  case 0x65: /* Color PageWiz */
  case 0xC8: /* ScanMaker 600ZS */
    mi->base_resolution = 300;
    break;
  case 0x5b: /* Agfa StudioScan II/IIsi (untested!) */
    mi->base_resolution = 400;
    break;
  case 0x57: /* ScanMaker IIHR    */
  case 0x59: /* ScanMaker III     */
  case 0x5c: /* Agfa Arcus II     */
  case 0x5e: /* Agfa StudioStar   */
  case 0x63: /* ScanMaker E6      */
  case 0x66: /* ScanMaker E6 (new)*/
    mi->base_resolution = 600;
    break;
  case 0x51: /* ScanMaker 45t     */
  case 0x5d: /* Agfa DuoScan      */
    mi->base_resolution = 1000;
    break;
  case 0x52: /* ScanMaker 35t     */
    mi->base_resolution = 1828;
    break;
  case 0x62: /* ScanMaker 35t+, Polaroid 35/LE    */
    mi->base_resolution = 1950;
    break;
  default:
    mi->base_resolution = 300;
    DBG(15, "parse_inquiry:  Unknown base resolution for 0x%x!\n",
	mi->model_code);
    break;
  }

  /* Our max_x,y is in pixels. `Some scanners think in 1/8", though.' */
  /* max pixel is, of course, total - 1                               */
  switch (mi->doc_size_code) {
  case 0x00:
    mi->max_x = 8.5 * mi->base_resolution - 1;
    mi->max_y = 14.0 * mi->base_resolution - 1;
    break;
  case 0x01:
    mi->max_x = 8.5 * mi->base_resolution - 1;
    mi->max_y = 11.0 * mi->base_resolution - 1;
    break;
  case 0x02:
    mi->max_x = 8.5 * mi->base_resolution - 1;
    mi->max_y = 11.69 * mi->base_resolution - 1;
    break;
  case 0x03:
    mi->max_x = 8.5 * mi->base_resolution - 1;
    mi->max_y = 13.0 * mi->base_resolution - 1;
    break;
  case 0x04:
    mi->max_x = 8.0 * mi->base_resolution - 1;
    mi->max_y = 10.0 * mi->base_resolution - 1;
    break;
  case 0x05:
    mi->max_x = 8.3 * mi->base_resolution - 1;
    mi->max_y = 14.0 * mi->base_resolution - 1;
    break;
  case 0x06:
    mi->max_x = 8.3 * mi->base_resolution - 1;
    mi->max_y = 13.5 * mi->base_resolution - 1;
    break;
  case 0x07:
    mi->max_x = 8.0 * mi->base_resolution - 1;
    mi->max_y = 14.0 * mi->base_resolution - 1;
    break;
  case 0x80:
    /* Slide format, size is mm */
    mi->max_x = (35.0 / MM_PER_INCH) * mi->base_resolution - 1;
    mi->max_y = (35.0 / MM_PER_INCH) * mi->base_resolution - 1;
    break;
  case 0x81:
    mi->max_x = 5.0 * mi->base_resolution - 1;
    mi->max_y = 5.0 * mi->base_resolution - 1;
    break;
  case 0x82:
    /* Slide format, size is mm */
    mi->max_x = (36.0 / MM_PER_INCH) * mi->base_resolution - 1;
    mi->max_y = (36.0 / MM_PER_INCH) * mi->base_resolution - 1;
    break;
  default:
    /* Undefined document format code */
      mi->max_x = mi->max_y = 0;
      DBG(15, "parse_inquiry:  Unknown doc_size_code!  0x%x\n",
	  mi->doc_size_code);
  }

  /* create the proper range constraints, given the doc size */
  {
    /* we need base resolution in dots-per-millimeter... */
    float base_res_dpmm = (float) mi->base_resolution / MM_PER_INCH;
    mi->doc_x_range.min = SANE_FIX(0);
    mi->doc_x_range.max = SANE_FIX((float)mi->max_x / base_res_dpmm);
    mi->doc_x_range.quant = SANE_FIX(0);
    mi->doc_y_range.min = SANE_FIX(0);
    mi->doc_y_range.max = SANE_FIX((float)mi->max_y / base_res_dpmm);
    mi->doc_y_range.quant = SANE_FIX(0);
  }

  mi->source_options             = (SANE_Byte)(result[63]);

  mi->expanded_resolution        = (result[64] & 0x01);
  /* my E6 reports exp-res capability incorrectly */
  if ((mi->model_code == 0x66) || (mi->model_code == 0x63)) {
    mi->expanded_resolution = 0xFF;
    DBG(4, "parse_inquiry:  E6 falsely denies expanded resolution.\n");
  }
  /* the StudioScan II(si) does the expanded-mode aspect correction
     within the scanner... (do others too?) */
  if (mi->model_code == 0x5b) {
    DBG(4, "parse_inquiry:  does expanded-mode expansion internally.\n");
    mi->does_expansion = 1;
  } else
    mi->does_expansion = 0;

  mi->enhance_cap                = (result[65] & 0x03);

  /*
  switch (result[66] & 0x0F) {
  case 0x00: mi->max_lookup_size =     0; break;
  case 0x01: mi->max_lookup_size =   256; break;
  case 0x03: mi->max_lookup_size =  1024; break;
  case 0x05: mi->max_lookup_size =  4096; break;
  case 0x09: mi->max_lookup_size = 65536; break;
  default:
    mi->max_lookup_size = 0;
    DBG(15, "parse_inquiry:  Unknown gamma LUT size!  0x%x\n",
	result[66]);
  }
  */

  /* This is not how the vague documentation specifies this register.
     We're going to take it literally here -- i.e. if the bit is
     set, the scanner supports the value, otherwise it doesn't.
     (The docs say all lower values are always supported.  This is
     not the case for the StudioScan IIsi, at least, which only
     specifies 0x02==1024-byte table, and only supports that, too.)

     All-in-all, it doesn't matter, since we take the largest
     allowed LUT size anyway.
  */
  if (result[66] & 0x08)
    mi->max_lookup_size = 65536;
  else if (result[66] & 0x04)
    mi->max_lookup_size = 4096;
  else if (result[66] & 0x02)
    mi->max_lookup_size = 1024;
  else if (result[66] & 0x01)
    mi->max_lookup_size = 256;
  else
    mi->max_lookup_size = 0;

  /* my E6 reports incorrectly */
  if ((mi->model_code == 0x66) || (mi->model_code == 0x63)) {
    mi->max_lookup_size = 1024;
    DBG(4, "parse_inquiry:  E6 falsely denies 1024-byte LUT.\n");
  }

  /*
  switch (result[66] >> 5) {
  case 0x00: mi->max_gamma_val =   255;  mi->gamma_size = 1;  break;
  case 0x01: mi->max_gamma_val =  1023;  mi->gamma_size = 2;  break;
  case 0x02: mi->max_gamma_val =  4095;  mi->gamma_size = 2;  break;
  case 0x03: mi->max_gamma_val = 65535;  mi->gamma_size = 2;  break;
  default:
    mi->max_gamma_val =     0;  mi->gamma_size = 0;
    DBG(15, "parse_inquiry:  Unknown gamma max val!  0x%x\n",
	result[66]);
  }
  */
  switch (result[66] >> 5) {
  case 0x00: mi->max_gamma_bit_depth =  8;  mi->gamma_size = 1;  break;
  case 0x01: mi->max_gamma_bit_depth = 10;  mi->gamma_size = 2;  break;
  case 0x02: mi->max_gamma_bit_depth = 12;  mi->gamma_size = 2;  break;
  case 0x03: mi->max_gamma_bit_depth = 16;  mi->gamma_size = 2;  break;
  default:
    mi->max_gamma_bit_depth =  0;  mi->gamma_size = 0;
    DBG(15, "parse_inquiry:  Unknown gamma max val!  0x%x\n",
	result[66]);
  }

  mi->fast_color_preview         = (SANE_Byte)(result[67] & 0x01);
  mi->xfer_format_select         = (SANE_Byte)(result[68] & 0x01);
  mi->color_sequence             = (SANE_Byte)(result[69] & 0x7f);
  mi->does_3pass                 = (SANE_Byte)(!(result[69] & 0x80));
  mi->does_mode1                 = (SANE_Byte)(result[71] & 0x01);

  mi->bit_formats                = (SANE_Byte)(result[74] & 0x0F);
  mi->extra_cap                  = (SANE_Byte)(result[75] & 0x07);

  /* XXXXXX a quick hack to disable any [pre/real]cal stuff for
     anything but an E6... */
  if (!((mi->model_code == 0x66) || (mi->model_code == 0x63))) {
    mi->extra_cap &= ~MI_EXCAP_DIS_RECAL;
    DBG(4, "parse_inquiry:  Not an E6 -- pretend recal cannot be disabled.\n");
  }

  /* The E2 lies... */
  if (mi->model_code == 0x64) {
    DBG(4, "parse_inquiry:  The E2 lies about it's 3-pass heritage.\n");
    mi->does_3pass = 1;
    mi->modes &= ~MI_MODES_ONEPASS;
  }

  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* Dump all we know about scanner to stderr                         */
/********************************************************************/
static SANE_Status
dump_inquiry(Microtek_Info *mi, unsigned char *result)
{
  int i;

  DBG(15, "dump_inquiry...\n");
  DBG(1, " === SANE/Microtek backend v%d.%d.%d ===\n",
	  MICROTEK_MAJOR, MICROTEK_MINOR, MICROTEK_PATCH);
  DBG(1, "========== Scanner Inquiry Block ========mm\n");
  for (i=0; i<96; ) {
    if (!(i % 16)) MDBG_INIT("");
    MDBG_ADD("%02x ", (int)result[i++]);
    if (!(i % 16)) MDBG_FINISH(1);
  }
  DBG(1, "========== Scanner Inquiry Report ==========\n");
  DBG(1, "===== Scanner ID...\n");
  DBG(1, "Device Type Code: 0x%02x\n", mi->device_type);
  DBG(1, "Model Code: 0x%02x\n", mi->model_code);
  DBG(1, "Vendor Name: '%s'   Model Name: '%s'\n",
	  mi->vendor_id, mi->model_name);
  DBG(1, "Vendor Specific String: '%s'\n", mi->vendor_string);
  DBG(1, "Firmware Rev: '%s'\n", mi->revision_num);
  DBG(1,
	  "SCSI F/W version: %1d.%1d     Scanner F/W version: %1d.%1d\n",
	  mi->SCSI_firmware_ver_major, mi->SCSI_firmware_ver_minor,
	  mi->scanner_firmware_ver_major, mi->scanner_firmware_ver_minor);
  DBG(1, "Response data format: 0x%02x\n", mi->response_data_format);

  DBG(1, "===== Imaging Capabilities...\n");
  DBG(1, "Modes:  %s%s%s%s%s%s%s\n",
	  (mi->modes & MI_MODES_LINEART) ? "Lineart " : "",
	  (mi->modes & MI_MODES_HALFTONE) ? "Halftone " : "",
	  (mi->modes & MI_MODES_GRAY) ? "Gray " : "",
	  (mi->modes & MI_MODES_COLOR) ? "Color " : "",
	  (mi->modes & MI_MODES_TRANSMSV) ? "(X-msv) " : "",
	  (mi->modes & MI_MODES_ONEPASS) ? "(OnePass) " : "",
	  (mi->modes & MI_MODES_NEGATIVE) ? "(Negative) " : "");
  DBG(1,
	  "Resolution Step Sizes: %s%s    Expanded Resolution Support? %s%s\n",
	  (mi->res_step & MI_RESSTEP_1PER) ? "1% " : "",
	  (mi->res_step & MI_RESSTEP_5PER) ? "5%" : "",
	  (mi->expanded_resolution) ? "yes" : "no",
	  (mi->expanded_resolution == 0xFF) ? "(but says no)" : "");
  DBG(1, "Supported Bits Per Sample: %s8 %s%s%s\n",
	  (mi->bit_formats & MI_FMT_CAP_4BPP) ? "4 " : "",
	  (mi->bit_formats & MI_FMT_CAP_10BPP) ? "10 " : "",
	  (mi->bit_formats & MI_FMT_CAP_12BPP) ? "12 " : "",
	  (mi->bit_formats & MI_FMT_CAP_16BPP) ? "16 " : "");
  DBG(1, "Max. document size code: 0x%02x\n",
	  mi->doc_size_code);
  DBG(1, "Max. document size:  %d x %d pixels\n",
	  mi->max_x, mi->max_y);
  DBG(1, "Frame units:  %s%s\n",
	  (mi->unit_type & MI_UNIT_PIXELS) ? "pixels  " : "",
	  (mi->unit_type & MI_UNIT_8TH_INCH) ? "1/8\"'s " : "");
  DBG(1, "# of built-in halftones: %d   Downloadable patterns? %s\n",
	  mi->pattern_count, (mi->pattern_dwnld) ? "Yes" : "No");

  DBG(1, "Data Compression: %s%s\n",
	  (mi->compress_type & MI_COMPRSS_HUFF) ? "huffman " : "",
	  (mi->compress_type & MI_COMPRSS_RD) ? "read-data " : "");
  DBG(1, "Contrast Settings: %d   Exposure Settings: %d\n",
	  mi->cont_settings, mi->exp_settings);
  DBG(1, "Adjustable Shadow/Highlight? %s   Adjustable Midtone? %s\n",
	  (mi->enhance_cap & MI_ENH_CAP_SHADOW) ? "yes" : "no ",
	  (mi->enhance_cap & MI_ENH_CAP_MIDTONE) ? "yes" : "no ");
  DBG(1, "Digital brightness/offset? %s\n",
	  (mi->extra_cap & MI_EXCAP_OFF_CTL) ? "yes" : "no");
  /*
  fprintf(stderr,
	  "Gamma Table Size: %d entries of %d bytes (max. value: %d)\n",
	  mi->max_lookup_size, mi->gamma_size, mi->max_gamma_val);
  */
  DBG(1,
	  "Gamma Table Size: %d entries of %d bytes (max. depth: %d)\n",
	  mi->max_lookup_size, mi->gamma_size, mi->max_gamma_bit_depth);

  DBG(1, "===== Source Options...\n");
  DBG(1, "Feed type:  %s%s   ADF support? %s\n",
	  (mi->feed_type & MI_FEED_FLATBED) ? "flatbed " : "",
	  (mi->feed_type & MI_FEED_EDGEFEED) ? "edge-feed " : "",
	  (mi->feed_type & MI_FEED_AUTOSUPP) ? "yes" : "no");
  DBG(1, "Document Feeder Support? %s   Feeder Backtracking? %s\n",
	  (mi->source_options & MI_SRC_FEED_SUPP) ? "yes" : "no ",
	  (mi->source_options & MI_SRC_FEED_BT) ? "yes" : "no ");
  DBG(1, "Feeder Installed? %s          Feeder Ready? %s\n",
	  (mi->source_options & MI_SRC_HAS_FEED) ? "yes" : "no ",
	  (mi->source_options & MI_SRC_FEED_RDY) ? "yes" : "no ");
  DBG(1, "Transparency Adapter Installed? %s\n",
	  (mi->source_options & MI_SRC_HAS_TRANS) ? "yes" : "no ");
  /* GET_TRANS GET_FEED XXXXXXXXX */
  /* mt_SWslct ???? XXXXXXXXXXX */
  /*#define DOC_ON_FLATBED 0x00
    #define DOC_IN_FEEDER  0x01
    #define TRANSPARENCY   0x10
    */
  DBG(1, "Fast Color Prescan? %s\n",
	  (mi->fast_color_preview) ? "yes" : "no");
  DBG(1, "Selectable Transfer Format? %s\n",
	  (mi->xfer_format_select) ? "yes" : "no");
  MDBG_INIT("Color Transfer Sequence: ");
  switch (mi->color_sequence) {
  case MI_COLSEQ_PLANE:
    MDBG_ADD("plane-by-plane (3-pass)"); break;
  case MI_COLSEQ_PIXEL:
    MDBG_ADD("pixel-by-pixel RGB"); break;
  case MI_COLSEQ_RGB:
    MDBG_ADD("line-by-line, R-G-B sequence"); break;
  case MI_COLSEQ_NONRGB:
    MDBG_ADD("line-by-line, non-sequential with headers"); break;
  case MI_COLSEQ_2PIXEL:
    MDBG_ADD("2pixel-by-2pixel RRGGBB"); break;
  default:
    MDBG_ADD("UNKNOWN CODE (0x%02x)", mi->color_sequence);
  }
  MDBG_FINISH(1);
  /*  if (mi->modes & MI_MODES_ONEPASS) XXXXXXXXXXX */
  DBG(1, "Three pass scan support? %s\n",
	  (mi->does_3pass ? "yes" : "no"));
  DBG(1, "ModeSelect-1 and ModeSense-1 Support? %s\n",
	  (mi->does_mode1) ? "yes" : "no");
  DBG(1, "Can Disable Linearization Table? %s\n",
	  (mi->extra_cap & MI_EXCAP_DIS_LNTBL) ? "yes" : "no");
  DBG(1, "Can Disable Start-of-Scan Recalibration? %s\n",
	  (mi->extra_cap & MI_EXCAP_DIS_RECAL) ? "yes" : "no");

  DBG(1, "Internal expanded expansion? %s\n",
	  mi->does_expansion ? "yes" : "no");
  /*
    fprintf(stderr, "cntr_vals = %d, min_cntr = %d, max_cntr = %d\n",
    cntr_vals, min_cntr, max_cntr);
    fprintf(stderr, "exp_vals = %d, min_exp = %d, max_exp = %d\n",
    exp_vals, min_exp, max_exp);
    */
  DBG(1, "====== End of Scanner Inquiry Report =======\n");
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Dump all we know about some unknown scanner to stderr            */
/********************************************************************/
static SANE_Status
dump_suspect_inquiry(unsigned char *result)
{
  int i;
  char vendor_id[64], model_name[64], revision_num[16];
  SANE_Byte device_type, model_code;
  SANE_Byte SCSI_firmware_ver_major, SCSI_firmware_ver_minor;
  SANE_Byte scanner_firmware_ver_major, scanner_firmware_ver_minor;
  SANE_Byte response_data_format;

  DBG(15, "dump_suspect_inquiry...\n");
  DBG(1, " === SANE/Microtek backend v%d.%d.%d ===\n",
	  MICROTEK_MAJOR, MICROTEK_MINOR, MICROTEK_PATCH);
  DBG(1, "========== Scanner Inquiry Block ========mm\n");
  for (i=0; i<96; ) {
    if (!(i % 16)) MDBG_INIT("");
    MDBG_ADD("%02x ", (int)result[i++]);
    if (!(i % 16)) MDBG_FINISH(1);
  }
#if 0
  for (i=0; i<96; i++) {
    if (!(i % 16) && (i)) fprintf(stderr, "\n");
    fprintf(stderr, "%02x ", (int)result[i]);
  }
  fprintf(stderr, "\n\n");
#endif
  strncpy(vendor_id, (char *)&result[8], 8);
  strncpy(model_name, (char *)&result[16], 16);
  strncpy(revision_num, (char *)&result[32], 4);
  vendor_id[8]    = 0;
  model_name[16]  = 0;
  revision_num[5] = 0;
  device_type                = (SANE_Byte)(result[0] & 0x1f);
  SCSI_firmware_ver_major    = (SANE_Byte)((result[1] & 0xf0) >> 4);
  SCSI_firmware_ver_minor    = (SANE_Byte)(result[1] & 0x0f);
  scanner_firmware_ver_major = (SANE_Byte)((result[2] & 0xf0) >> 4);
  scanner_firmware_ver_minor = (SANE_Byte)(result[2] & 0x0f);
  response_data_format       = (SANE_Byte)(result[3]);
  model_code                 = (SANE_Byte)(result[62]);

  DBG(1, "========== Scanner Inquiry Report ==========\n");
  DBG(1, "===== Scanner ID...\n");
  DBG(1, "Device Type Code: 0x%02x\n", device_type);
  DBG(1, "Model Code: 0x%02x\n", model_code);
  DBG(1, "Vendor Name: '%s'   Model Name: '%s'\n",
	  vendor_id, model_name);
  DBG(1, "Firmware Rev: '%s'\n", revision_num);
  DBG(1,
	  "SCSI F/W version: %1d.%1d     Scanner F/W version: %1d.%1d\n",
	  SCSI_firmware_ver_major, SCSI_firmware_ver_minor,
	  scanner_firmware_ver_major, scanner_firmware_ver_minor);
  DBG(1, "Response data format: 0x%02x\n", response_data_format);
  DBG(1, "====== End of Scanner Inquiry Report =======\n");
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Determine if device is a Microtek Scanner (from INQUIRY info)    */
/********************************************************************/
static SANE_Status
id_microtek(uint8_t *result, char **model_string)
{
  SANE_Byte device_type, response_data_format;
  int forcewarn = 0;

  DBG(15, "id_microtek...\n");
  /* check device type first... */
  device_type = (SANE_Byte)(result[0] & 0x1f);
  if (device_type != 0x06) {
    DBG(15, "id_microtek:  not even a scanner:  dev_type = %d\n",
	device_type);
    return SANE_STATUS_INVAL;
  }
  if (!(strncmp("MICROTEK", (char *)&(result[8]), 8)) ||
      !(strncmp("MII SC31", (char *)&(result[8]), 8)) ||  /* the IISP */
      !(strncmp("MII SC21", (char *)&(result[8]), 8)) ||  /* the 600ZS */
      !(strncmp("MII SC23", (char *)&(result[8]), 8)) ||  /* the other 600ZS */
      !(strncmp("MII SC25", (char *)&(result[8]), 8)) ||  /* some other 600GS */
      !(strncmp("AGFA    ", (char *)&(result[8]), 8)) ||  /* Arcus II */
      !(strncmp("Microtek", (char *)&(result[8]), 8)) ||  /* some 35t+'s */
      !(strncmp("Polaroid", (char *)&(result[8]), 8)) ||  /* SprintScan 35LE */
      !(strncmp("        ", (char *)&(result[8]), 8)) ) {
    switch (result[62]) {
    case 0x16 :
      *model_string = "ScanMaker 600ZS";    break;
    case 0x50 :
      *model_string = "ScanMaker II/IIXE";  break;
    case 0x51 :
      *model_string = "ScanMaker 45t";      break;
    case 0x52 :
      *model_string = "ScanMaker 35t";      break;
    case 0x54 :
      *model_string = "ScanMaker IISP";     break;
    case 0x55 :
      *model_string = "ScanMaker IIER";     break;
    case 0x56 :
      *model_string = "ScanMaker A3t";      break;
    case 0x57 :
      *model_string = "ScanMaker IIHR";     break;
    case 0x58 :
      *model_string = "ScanMaker IIG";      break;
    case 0x59 :
      *model_string = "ScanMaker III";      break;
    case 0x5A :
      *model_string = "Agfa StudioScan";    break;
    case 0x5B :
      *model_string = "Agfa StudioScan II"; break;
    case 0x5C :
      *model_string = "Agfa Arcus II";      break;
    case 0x5f :
      *model_string = "ScanMaker E3";       break;
    case 0x62 :
      if (!(strncmp("Polaroid", (char *)&(result[8]), 8)))
	*model_string = "Polaroid SprintScan 35/LE";
      else
	*model_string = "ScanMaker 35t+";
      break;
    case 0x63 :
    case 0x66 :
      *model_string = "ScanMaker E6";       break;
    case 0x64 : /* and "Vobis RealScan" */
      *model_string = "ScanMaker E2";       break;
    case 0x65:
      *model_string = "Color PageWiz";      break;
    case 0xC8:
      *model_string = "ScanMaker 600ZS";    break;
      /* the follow are listed in the docs, but are otherwise a mystery... */
    case 0x5D:
      *model_string = "Agfa DuoScan";  forcewarn = 1; break;
    case 0x5E:
      *model_string = "SS3";      forcewarn = 1; break;
    case 0x60:
      *model_string = "HR1";      forcewarn = 1; break;
    case 0x61:
      *model_string = "45T+";     forcewarn = 1; break;
    case 0x67:
      *model_string = "TR3";      forcewarn = 1; break;
    default :
      /* this might be a newer scanner, which uses the SCSI II command set. */
      /* that's unfortunate, but we'll warn the user anyway....             */
      response_data_format = (SANE_Byte)(result[3]);
      if (response_data_format == 0x02) {
	DBG(15, "id_microtek:  (uses new SCSI II command set)\n");
	if (DBG_LEVEL >= 15) {
	  DBG(1, "\n");
	  DBG(1, "\n");
	  DBG(1, "\n");
	  DBG(1, "========== Congratulations! ==========\n");
	  DBG(1, "You appear to be the proud owner of a \n");
	  DBG(1, "brand-new Microtek scanner, which uses\n");
	  DBG(1, "a new SCSI II command set.            \n");
	  DBG(1, "\n");
	  DBG(1, "Try the `microtek2' backend instead.  \n");
	  DBG(1, "\n");
	  DBG(1, "\n");
	  DBG(1, "\n");
	}
      }
      return SANE_STATUS_INVAL;
    }
    if (forcewarn) {
      /* force debugging on, to encourage user to send in a report */
#ifndef NDEBUG
      DBG_LEVEL = 1;
#endif
      DBG(1, "\n");
      DBG(1, "\n");
      DBG(1, "\n");
      DBG(1, "========== Congratulations! ==========\n");
      DBG(1, "Your scanner appears to be supported  \n");
      DBG(1, "by the microtek backend.  However, it \n");
      DBG(1, "has never been tried before, and some \n");
      DBG(1, "parameters are bound to be wrong.     \n");
      DBG(1, "\n");
      DBG(1, "Please send the scanner inquiry log in\n");
      DBG(1, "its entirety to mtek-bugs@mir.com and \n");
      DBG(1, "include a description of the scanner, \n");
      DBG(1, "including the base optical resolution.\n");
      DBG(1, "\n");
      DBG(1, "You'll find complete instructions for \n");
      DBG(1, "submitting an error/debug log in the  \n");
      DBG(1, "'sane-microtek' man-page.             \n");
      DBG(1, "\n");
      DBG(1, "\n");
      DBG(1, "\n");
    }
    return SANE_STATUS_GOOD;
  }
  DBG(15, "id_microtek:  not microtek:  %d, %d, %d\n",
      strncmp("MICROTEK", (char *)&(result[8]), 8),
      strncmp("        ", (char *)&(result[8]), 8),
      result[62]);
  return SANE_STATUS_INVAL;
}



/********************************************************************/
/* Try to attach a device as a Microtek scanner                     */
/********************************************************************/
static SANE_Status
attach_scanner(const char *devicename, Microtek_Device **devp)
{
  Microtek_Device *dev;
  int sfd;
  size_t size;
  unsigned char result[0x60];
  SANE_Status status;
  char *model_string;
  uint8_t inquiry[] = { 0x12, 0, 0, 0, 0x60, 0 };

  DBG(15,"attach_scanner:  %s\n", devicename);
  /* check if device is already known... */
  for (dev = first_dev; dev; dev = dev->next) {
    if (strcmp(dev->sane.name, devicename) == 0) {
      if (devp) *devp = dev;
      return SANE_STATUS_GOOD;
    }
  }

  /* open scsi device... */
  DBG(20, "attach_scanner:  opening %s\n", devicename);
  if (sanei_scsi_open(devicename, &sfd, sense_handler, NULL) != 0) {
    DBG(20, "attach_scanner:  open failed\n");
    return SANE_STATUS_INVAL;
  }

  /* say hello... */
  DBG(20, "attach_scanner:  sending INQUIRY\n");
  size = sizeof(result);
  status = sanei_scsi_cmd(sfd, inquiry, sizeof(inquiry), result, &size);
  sanei_scsi_close (sfd);
  if (status != SANE_STATUS_GOOD || size != 0x60) {
    DBG(20, "attach_scanner:  inquiry failed (%s)\n", sane_strstatus (status));
    return status;
  }

  if (id_microtek(result, &model_string) != SANE_STATUS_GOOD) {
      DBG(15, "attach_scanner:  device doesn't look like a Microtek scanner.");
      if (DBG_LEVEL >= 5) dump_suspect_inquiry(result);
      return SANE_STATUS_INVAL;
  }

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

  parse_inquiry(&(dev->info), result);
  if (DBG_LEVEL > 0) dump_inquiry(&(dev->info), result);

  /* initialize dev structure */
  dev->sane.name   = strdup(devicename);
  dev->sane.vendor = "Microtek";
  dev->sane.model  = strdup(model_string);
  dev->sane.type   = "flatbed scanner";

  /* link into device list... */
  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;
  if (devp) *devp = dev;

  DBG(15, "attach_scanner:  happy.\n");

  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Attach a scanner (convenience wrapper for find_scanners...)      */
/********************************************************************/
static SANE_Status
attach_one (const char *dev)
{
  attach_scanner (dev, 0);
  return SANE_STATUS_GOOD;
}




/********************************************************************/
/* End a scan, and clean up afterwards                              */
/********************************************************************/
static SANE_Status end_scan(Microtek_Scanner *s, SANE_Status ostat)
{
  SANE_Status status;

  DBG(15, "end_scan...\n");
  if (s->scanning) {
    s->scanning = SANE_FALSE;
    /* stop the scanner */
    if (s->scan_started) {
      status = stop_scan(s);
      if (status != SANE_STATUS_GOOD)
	DBG(23, "end_scan:  OY! on stop_scan\n");
      s->scan_started = SANE_FALSE;
    }
    /* close the SCSI device */
    if (s->sfd != -1) {
      sanei_scsi_close(s->sfd);
      s->sfd = -1;
    }
    /* free the buffers we malloc'ed */
    if (s->scsi_buffer != NULL) {
      free(s->scsi_buffer);
      s->scsi_buffer = NULL;
    }
    if (s->rb != NULL) {
      ring_free(s->rb);
      s->rb = NULL;
    }
  }
  /* if this -was- pass 3, or cancel, then we must be done */
  if ((s->this_pass == 3) || (s->cancel))
    s->this_pass = 0;
  return ostat;
}



/********************************************************************/
/********************************************************************/
/***** Scan-time operations                                     *****/
/********************************************************************/
/********************************************************************/


/* number of lines of calibration data returned by scanner */
#define STRIPS 12  /* well, that's what it seems to be for the E6 */


/* simple comparison for the qsort below */

static int comparo(const void *a, const void *b)
{
  return (*(const int *)a - *(const int *)b);
}


/* extract values from scanlines and sort */

static void sort_values(int *result, uint8_t *scanline[], int pix)
{
  int i;
  for (i=0; i<STRIPS; i++) result[i] = (scanline[i])[pix];
  qsort(result, STRIPS, sizeof(result[0]), comparo);
}


/********************************************************************/
/* Calculate the calibration data.                                  */
/*  This seems to be, for each pixel of each R/G/B ccd, the average */
/*  of the STRIPS# values read by the scanner, presumably off some  */
/*  blank spot under the cover.                                     */
/*  The raw scanner data does indeed resemble the intensity profile */
/*  of a lamp.                                                      */
/*  The sort is used to calc the median, which is used to remove    */
/*  outliers in the data; maybe from dust under the cover?          */
/********************************************************************/


static void calc_calibration(uint8_t *caldata, uint8_t *scanline[],
			     int pixels)
{
  int i,j;
  int sorted[STRIPS];

  DBG(23, ".calc_calibration...\n");
  for (i=0; i<pixels; i++) {
    int q1, q3;
    int bot, top;
    int sum = 0;
    int count = 0;

    sort_values(sorted, scanline, i);
    q1 = sorted[STRIPS / 4];       /* first quartile */
    q3 = sorted[STRIPS * 3 / 4];   /* third quartile */
    bot = q1 - 3 * (q3 - q1) / 2;  /* quick'n'easy bounds */
    top = q3 + 3 * (q3 - q1) / 2;

    for (j=0; j<STRIPS; j++) {
      if ((sorted[j] >= bot) && (sorted[j] <= top)) {
	sum += sorted[j];
	count++;
      }
    }
    if (count)
      caldata[i] = (sum + (count / 2)) / count;
    else {
      DBG(23, "zero: i=%d b/t=%d/%d ", i, bot, top);
      if (DBG_LEVEL >= 23) {
	MDBG_INIT("");
	for (j=0; j<STRIPS; j++) MDBG_ADD(" %3d", sorted[j]);
	MDBG_FINISH(23);
      }
      caldata[i] = 0;
    }
  }
}



/********************************************************************/
/* Calibrate scanner CCD, the "real" way.                           */
/*  This stuff is not documented in the command set, but this is    */
/*  what Microtek's TWAIN driver seems to do, more or less, on an   */
/*  E6 at least.  What other scanners will do this???               */
/********************************************************************/


static SANE_Status do_real_calibrate(Microtek_Scanner *s)
{
  SANE_Status status, statusA;
  SANE_Int busy, linewidth, lines;
  size_t buffsize;
  uint8_t *input, *scanline[STRIPS], *combuff;
  uint8_t letter;
  int i, spot;
  int nmax, ntoget, nleft;

  DBG(10, "do_real_calibrate...\n");

  /* tell scanner to read it's little chart */
  if ((status = start_calibration(s)) != SANE_STATUS_GOOD) return status;
  if ((status = get_scan_status(s, &busy, &linewidth, &lines))
      != SANE_STATUS_GOOD) {
    DBG(23, "do_real_cal:  get_scan_status failed!\n");
    return status;
  }
  /* make room for data in and data out */
  input = calloc(STRIPS * 3 * linewidth, sizeof(input[0]));
  combuff = calloc(linewidth + 6, sizeof(combuff[0]));
  if ((input == NULL) || (combuff == NULL)) {
    DBG(23, "do_real_cal:  bad calloc %p %p\n", input, combuff);
    free(input);
    free(combuff);
    return SANE_STATUS_NO_MEM;
  }
  /* read STRIPS lines of R, G, B ccd data */
  nmax = SCSI_BUFF_SIZE / (3 * linewidth);
  DBG(23, "do_real_cal:  getting data (max=%d)\n", nmax);
  for (nleft = STRIPS, spot=0;
       nleft > 0;
       nleft -= ntoget, spot += buffsize) {
    ntoget = (nleft > nmax) ? nmax : nleft;
    buffsize = ntoget * 3 * linewidth;
    DBG(23, "...nleft %d  toget %d  size %lu  spot %d  input+spot %p\n",
	nleft, ntoget, (u_long) buffsize, spot, input+spot);
    if ((statusA = read_scan_data(s, ntoget, input+spot, &buffsize))
	!= SANE_STATUS_GOOD) {
      DBG(23, "...read scan failed\n");
      break;
    }
  }
  status = stop_scan(s);
  if ((statusA != SANE_STATUS_GOOD) || (status != SANE_STATUS_GOOD)) {
    free(input);
    free(combuff);
    return ((statusA != SANE_STATUS_GOOD) ? statusA : status);
  }
  /* calculate calibration data for each element and download */
  for (letter = 'R'; letter != 'X'; ) {
    DBG(23, "do_real_calibrate:  working on %c\n", letter);
    for (spot=0, i=0; spot < linewidth * STRIPS * 3; spot += linewidth) {
      if (input[spot+1] == letter) {
	DBG(23, "   found %d (at %d)\n", i, spot);
	if (i >= STRIPS) {
	  DBG(23, "WHOA!!!  %i have already been found!\n", i);
	  break;
	}
	scanline[i] = &(input[spot+2]);
	i++;
      }
    }
    calc_calibration(combuff + 8, scanline, linewidth - 2);
    if ((status = download_calibration(s, combuff, letter, linewidth))
	!= SANE_STATUS_GOOD) {
      DBG(23, "...download_calibration failed\n");
      free(input);
      free(combuff);
      return status;
    }
    switch (letter) {
    case 'R': letter = 'G'; break;
    case 'G': letter = 'B'; break;
    case 'B':
    default:  letter = 'X'; break;
    }
  }
  /* clean up */
  free(input);
  free(combuff);
  return SANE_STATUS_GOOD;
}




/********************************************************************/
/* Cause scanner to calibrate, but don't really scan anything       */
/*           (i.e. do everything but read data)                     */
/********************************************************************/
static SANE_Status do_precalibrate(SANE_Handle handle)
{
  Microtek_Scanner *s = handle;
  SANE_Status status, statusA;
  SANE_Int busy, linewidth, lines;

  DBG(10, "do_precalibrate...\n");

  if ((status = wait_ready(s)) != SANE_STATUS_GOOD) return status;
  {
    SANE_Int y1 = s->y1;
    SANE_Int y2 = s->y2;
    /* some small range, but large enough to cause the scanner
       to think it'll scan *something*... */
    s->y1 = 0;
    s->y2 =
      (s->resolution > s->dev->info.base_resolution) ?
      4 : 4 * s->dev->info.base_resolution / s->resolution;
    status = scanning_frame(s);
    s->y1 = y1;
    s->y2 = y2;
    if (status != SANE_STATUS_GOOD) return status;
  }

  if (s->dev->info.source_options &
      (MI_SRC_FEED_BT | MI_SRC_HAS_TRANS |
       MI_SRC_FEED_SUPP | MI_SRC_HAS_FEED)) { /* ZZZZZZZZZZZ */
    if ((status = accessory(s)) != SANE_STATUS_GOOD) return status;
  }
  if ((status = mode_select(s)) != SANE_STATUS_GOOD) return status;
  /* why would we even try if this were not true?... */
  /*if (s->dev->info.extra_cap & MI_EXCAP_DIS_RECAL) */
  {
    SANE_Bool allow_calibrate = s->allow_calibrate;
    s->allow_calibrate = SANE_TRUE;
    status = mode_select_1(s);
    s->allow_calibrate = allow_calibrate;
    if (status != SANE_STATUS_GOOD) return status;
  }

  if ((status = wait_ready(s)) != SANE_STATUS_GOOD) return status;
  if ((status = start_scan(s))     != SANE_STATUS_GOOD) return status;
  if ((statusA = get_scan_status(s, &busy,
				&linewidth, &lines)) != SANE_STATUS_GOOD) {
    DBG(10, "do_precalibrate:  get_scan_status fails\n");
  }
  if ((status = stop_scan(s)) != SANE_STATUS_GOOD) return status;
  if ((status = wait_ready(s)) != SANE_STATUS_GOOD) return status;
  DBG(10, "do_precalibrate done.\n");
  if (statusA != SANE_STATUS_GOOD)
    return statusA;
  else
    return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Calibrate scanner, if necessary; record results               */
/********************************************************************/
static SANE_Status finagle_precal(SANE_Handle handle)
{
  Microtek_Scanner *s = handle;
  SANE_Status status;
  int match;

  /* try to check if scanner has been reset  */
  /* if so, calibrate it
     (either for real, or via a fake scan, with calibration */
  /* (but only bother if you *could* disable calibration) */
  DBG(23, "finagle_precal...\n");
  if ((s->do_clever_precal) || (s->do_real_calib)) {
    if ((status = compare_mode_sense(s, &match)) != SANE_STATUS_GOOD)
      return status;
    if (((s->do_real_calib) && (!s->calib_once)) || /* user want recal */
	(!match) ||                             /* or, possible reset  */
	((s->mode == MS_MODE_COLOR) &&          /* or, other weirdness */
	 (s->precal_record < MS_PRECAL_COLOR)) ||
	((s->mode == MS_MODE_COLOR) &&
	 (s->expandedresolution) &&
	 (s->precal_record < MS_PRECAL_EXP_COLOR))) {
      DBG(23, "finagle_precal:  must precalibrate!\n");
      s->precal_record = MS_PRECAL_NONE;
      if (s->do_real_calib) {    /* do a real calibration if allowed */
	if ((status = do_real_calibrate(s)) != SANE_STATUS_GOOD)
	  return status;
      } else if (s->do_clever_precal) {/* otherwise do the fake-scan version */
	if ((status = do_precalibrate(s)) != SANE_STATUS_GOOD)
	  return status;
      }
      if (s->mode == MS_MODE_COLOR) {
	if (s->expandedresolution)
	  s->precal_record = MS_PRECAL_EXP_COLOR;
	else
	  s->precal_record = MS_PRECAL_COLOR;
      } else
	s->precal_record = MS_PRECAL_GRAY;
    } else
      DBG(23, "finagle_precal:  no precalibrate necessary.\n");
  }
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* Set pass-dependent parameters (for 3-pass color scans)           */
/********************************************************************/
static void
set_pass_parameters (SANE_Handle handle)
{
  Microtek_Scanner *s = handle;

  if (s->threepasscolor) {
    s->this_pass += 1;
    DBG(23, "set_pass_parameters:  three-pass, on %d\n", s->this_pass);
    switch (s->this_pass) {
    case 1:
      s->filter = MS_FILT_RED;
      s->params.format = SANE_FRAME_RED;
      s->params.last_frame = SANE_FALSE;
      break;
    case 2:
      s->filter = MS_FILT_GREEN;
      s->params.format = SANE_FRAME_GREEN;
      s->params.last_frame = SANE_FALSE;
      break;
    case 3:
      s->filter = MS_FILT_BLUE;
      s->params.format = SANE_FRAME_BLUE;
      s->params.last_frame = SANE_TRUE;
      break;
    default:
      s->filter = MS_FILT_CLEAR;
      DBG(23, "set_pass_parameters:  What?!? pass %d = filter?\n",
	  s->this_pass);
      break;
    }
  } else
    s->this_pass = 0;
}



/********************************************************************/
/********************************************************************/
/***** Packing functions                                        *****/
/*****    ...process raw scanner bytes, and shove into          *****/
/*****        the ring buffer                                   *****/
/********************************************************************/
/********************************************************************/


/********************************************************************/
/* Process flat (byte-by-byte) data                                 */
/********************************************************************/
static SANE_Status pack_flat_data(Microtek_Scanner *s, size_t nlines)
{
  SANE_Status status;
  ring_buffer *rb = s->rb;
  size_t nbytes = nlines * rb->bpl;

  size_t start = (rb->head_complete + rb->complete_count) % rb->size;
  size_t max_xfer =
    (start < rb->head_complete) ?
    (rb->head_complete - start) :
    (rb->size - start + rb->head_complete);
  size_t length = MIN(nbytes, max_xfer);

  if (nbytes > max_xfer) {
    DBG(23, "pack_flat: must expand ring, %lu + %lu\n",
	(u_long)rb->size, (u_long)(nbytes - max_xfer));
    status = ring_expand(rb, (nbytes - max_xfer));
    if (status != SANE_STATUS_GOOD) return status;
  }

  if (s->doexpansion) {
    unsigned int line, bit;
    SANE_Byte *sb, *db, byte;

    size_t pos;

    sb = s->scsi_buffer;
    db = rb->base;
    pos = start;

    if (!(s->multibit)) {
      for (line=0; line<nlines; line++) {
	size_t i;
	double x1, x2, n1, n2;
	for (i = 0, x1 = 0.0, x2 = s->exp_aspect, n1 = 0.0, n2 = floor(x2);
	     i < rb->bpl;
	     i++) {
	  byte = 0;
	  for (bit=0;
	       bit < 8;
	       bit++, x1 = x2, n1 = n2, x2 += s->exp_aspect, n2 = floor(x2)) {
	    /* #define getbit(byte, index) (((byte)>>(index))&1) */
	    byte |=
	      ((
		(x2 == n2) ?
		(((sb[(int)n1 / 8])>>(7 - ((int)n1) % 8))&1) :
		(((double)(((sb[(int)n1/8])>>(7-(int)n1%8))&1) * (n2 - x1) +
		  (double)(((sb[(int)n2/8])>>(7-(int)n2%8))&1) * (x2 - n2)
		  ) / s->exp_aspect)
		) > 0.5) << (7 - bit);
	  }
	  db[pos] = byte;
	  if (++pos >= rb->size) pos = 0;
	}
	sb += s->pixel_bpl;
      }
    } else { /* multibit scan (8 is assumed!) */
      for (line=0; line<nlines; line++) {
	double x1, x2, n1, n2;
	int i;
	for (i = 0, x1 = 0.0, x2 = s->exp_aspect, n1 = 0.0, n2 = floor(x2);
	     i < s->dest_ppl;
	     i++, x1 = x2, n1 = n2, x2 += s->exp_aspect, n2 = floor(x2)) {
	  db[pos] =
	    (x2 == n2) ?
	    sb[(int)n1] :
	    (int)(((double)sb[(int)n1] * (n2 - x1) +
		   (double)sb[(int)n2] * (x2 - n2)) / s->exp_aspect);
	  if (++pos >= rb->size) pos = 0;
	}
	sb += s->pixel_bpl;
      }
    }
  } else {
    /* adjust for rollover!!! */
    if ((start + length) < rb->size) {
      memcpy(rb->base + start, s->scsi_buffer, length);
    } else {
      size_t chunk1 = rb->size - start;
      size_t chunk2 = length - chunk1;
      memcpy(rb->base + start, s->scsi_buffer, chunk1);
      memcpy(rb->base, s->scsi_buffer + chunk1, chunk2);
    }
  }
  rb->complete_count += length;
  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Process sequential R-G-B scan lines (who uses this??? )          */
/********************************************************************/
static SANE_Status
pack_seqrgb_data (Microtek_Scanner *s, size_t nlines)
{
  ring_buffer *rb = s->rb;
  unsigned int seg;
  SANE_Byte *db = rb->base;
  SANE_Byte *sb = s->scsi_buffer;
  size_t completed;
  size_t spot;
  SANE_Byte id;

  {
    size_t ar, ag, ab; /* allowed additions */
    size_t dr, dg, db; /* additions which will occur */
    SANE_Status status;

    dr = dg = db = nlines * rb->bpl;
    ar = rb->size - (rb->complete_count + rb->red_extra * 3);
    ag = rb->size - (rb->complete_count + rb->green_extra * 3);
    ab = rb->size - (rb->complete_count + rb->blue_extra * 3);
    DBG(23, "pack_seq:  dr/ar: %lu/%lu  dg/ag: %lu/%lu  db/ab: %lu/%lu\n",
	(u_long)dr, (u_long)ar,
	(u_long)dg, (u_long)ag,
	(u_long)db, (u_long)ab);
    if ((dr > ar) ||
	(dg > ag) ||
	(db > ab)) {
      size_t increase = 0;
      if (dr > ar) increase = (dr - ar);
      if (dg > ag) increase = MAX(increase, (dg - ag));
      if (db > ab) increase = MAX(increase, (db - ab));
      DBG(23, "pack_seq: must expand ring, %lu + %lu\n",
	  (u_long)rb->size, (u_long)increase);
      status = ring_expand(rb, increase);
      if (status != SANE_STATUS_GOOD) return status;
    }
  }

  for (seg = 0, id = 0; seg < nlines * 3; seg++, id = (id+1)%3) {
    switch (id) {
    case 0: spot = rb->tail_red;  break;
    case 1: spot = rb->tail_green;  break;
    case 2: spot = rb->tail_blue;  break;
    default:
      DBG(18, "pack_seq:  missing scanline RGB header!\n");
      return SANE_STATUS_IO_ERROR;
    }

    if (s->doexpansion) {
      int i;
      double x1, x2, n1, n2;
      for (i = 0, x1 = 0.0, x2 = s->exp_aspect, n1 = 0.0, n2 = floor(x2);
	   i < s->dest_ppl;
	   i++, x1 = x2, n1 = n2, x2 += s->exp_aspect, n2 = floor(x2)) {
	db[spot] =
	  (x2 == n2) ?
	  sb[(int)n1] :
	  (int)(((double)sb[(int)n1] * (n2 - x1) +
		 (double)sb[(int)n2] * (x2 - n2)) / s->exp_aspect);
	if ((spot += 3) >= rb->size) spot -= rb->size;
      }
      sb += s->ppl;
    } else {
      size_t i;
      for (i=0; i < rb->ppl; i++) {
	db[spot] = *sb;
	sb++;
	if ((spot += 3) >= rb->size) spot -= rb->size;
      }
    }

    switch (id) {
    case 0: rb->tail_red   = spot; rb->red_extra   += rb->ppl; break;
    case 1: rb->tail_green = spot; rb->green_extra += rb->ppl; break;
    case 2: rb->tail_blue  = spot; rb->blue_extra  += rb->ppl; break;
    }
  }

  completed = MIN(rb->red_extra, MIN(rb->green_extra, rb->blue_extra));
  rb->complete_count += completed * 3;  /* 3 complete bytes per pixel! */
  rb->red_extra   -= completed;
  rb->green_extra -= completed;
  rb->blue_extra  -= completed;

  DBG(18, "pack_seq:  extra r: %lu  g: %lu  b: %lu\n",
      (u_long)rb->red_extra,
      (u_long)rb->green_extra,
      (u_long)rb->blue_extra);
  DBG(18, "pack_seq:  completed: %lu  complete: %lu\n",
      (u_long)completed, (u_long)rb->complete_count);

  return SANE_STATUS_GOOD;
}


/********************************************************************/
/* Process non-sequential R,G, and B scan-lines                     */
/********************************************************************/
static SANE_Status
pack_goofyrgb_data(Microtek_Scanner *s, size_t nlines)
{
  ring_buffer *rb = s->rb;
  unsigned int seg; /* , i;*/
  SANE_Byte *db;
  SANE_Byte *sb = s->scsi_buffer;
  size_t completed;
  size_t spot;
  SANE_Byte id;

  /* prescan to decide if ring should be expanded */
  {
    size_t ar, ag, ab; /* allowed additions */
    size_t dr, dg, db; /* additions which will occur */
    SANE_Status status;
    SANE_Byte *pt;

    for (dr = dg = db = 0, seg = 0, pt = s->scsi_buffer + 1;
	 seg < nlines * 3;
	 seg++, pt += s->ppl + 2) {
      switch (*pt) {
      case 'R': dr += rb->bpl;  break;
      case 'G': dg += rb->bpl;  break;
      case 'B': db += rb->bpl;  break;
      }
    }
    ar = rb->size - (rb->complete_count + rb->red_extra * 3);
    ag = rb->size - (rb->complete_count + rb->green_extra * 3);
    ab = rb->size - (rb->complete_count + rb->blue_extra * 3);
    DBG(23, "pack_goofy:  dr/ar: %lu/%lu  dg/ag: %lu/%lu  db/ab: %lu/%lu\n",
	(u_long)dr, (u_long)ar,
	(u_long)dg, (u_long)ag,
	(u_long)db, (u_long)ab);
    /* >, or >= ???????? */
    if ((dr > ar) ||
	(dg > ag) ||
	(db > ab)) {
      size_t increase = 0;
      if (dr > ar) increase = (dr - ar);
      if (dg > ag) increase = MAX(increase, (dg - ag));
      if (db > ab) increase = MAX(increase, (db - ab));
      DBG(23, "pack_goofy: must expand ring, %lu + %lu\n",
	  (u_long)rb->size, (u_long)increase);
      status = ring_expand(rb, increase);
      if (status != SANE_STATUS_GOOD) return status;
    }
  }

  db = rb->base;
  for (seg = 0; seg < nlines * 3; seg++) {
    sb++; /* skip first byte in line (two byte header) */
    id = *sb;
    switch (id) {
    case 'R': spot = rb->tail_red;  break;
    case 'G': spot = rb->tail_green;  break;
    case 'B': spot = rb->tail_blue;  break;
    default:
      DBG(18, "pack_goofy:  missing scanline RGB header!\n");
      return SANE_STATUS_IO_ERROR;
    }
    sb++; /* skip the other header byte */

    if (s->doexpansion) {
      int i;
      double x1, x2, n1, n2;
      for (i = 0, x1 = 0.0, x2 = s->exp_aspect, n1 = 0.0, n2 = floor(x2);
	   i < s->dest_ppl;
	   i++, x1 = x2, n1 = n2, x2 += s->exp_aspect, n2 = floor(x2)) {
	db[spot] =
	  (x2 == n2) ?
	  sb[(int)n1] :
	  (int)(((double)sb[(int)n1] * (n2 - x1) +
		 (double)sb[(int)n2] * (x2 - n2)) / s->exp_aspect);
	if ((spot += 3) >= rb->size) spot -= rb->size;
      }
      sb += s->ppl;
    } else {
      unsigned int i;
      for (i=0; i < rb->ppl; i++) {
	db[spot] = *sb;
	sb++;
	if ((spot += 3) >= rb->size) spot -= rb->size;
      }
    }
    switch (id) {
    case 'R': rb->tail_red   = spot; rb->red_extra   += rb->ppl; break;
    case 'G': rb->tail_green = spot; rb->green_extra += rb->ppl; break;
    case 'B': rb->tail_blue  = spot; rb->blue_extra  += rb->ppl; break;
    }
  }

  completed = MIN(rb->red_extra, MIN(rb->green_extra, rb->blue_extra));
  rb->complete_count += completed * 3;  /* 3 complete bytes per pixel! */
  rb->red_extra   -= completed;
  rb->green_extra -= completed;
  rb->blue_extra  -= completed;

  DBG(18, "pack_goofy:  extra r: %lu  g: %lu  b: %lu\n",
      (u_long)rb->red_extra,
      (u_long)rb->green_extra,
      (u_long)rb->blue_extra);
  DBG(18, "pack_goofy:  completed: %lu  complete: %lu\n",
      (u_long)completed, (u_long)rb->complete_count);

  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* Process R1R2-G1G2-B1B2 double pixels (AGFA StudioStar)           */
/********************************************************************/

static SANE_Status
pack_seq2r2g2b_data(Microtek_Scanner *s, size_t nlines)
{
  SANE_Status status;
  ring_buffer *rb = s->rb;
  size_t nbytes = nlines * rb->bpl;

  size_t start = (rb->head_complete + rb->complete_count) % rb->size;
  size_t max_xfer =
    (start < rb->head_complete) ?
    (rb->head_complete - start) :
    (rb->size - start + rb->head_complete);
  size_t length = MIN(nbytes, max_xfer);

  if (nbytes > max_xfer) {
    DBG(23, "pack_2r2g2b: must expand ring, %lu + %lu\n",
	(u_long)rb->size, (u_long)(nbytes - max_xfer));
    status = ring_expand(rb, (nbytes - max_xfer));
    if (status != SANE_STATUS_GOOD) return status;
  }
  {
    unsigned int line;
    int p;
    size_t pos = start;
    SANE_Byte *sb = s->scsi_buffer;
    SANE_Byte *db = rb->base;

    for (line = 0; line < nlines; line++) {
      for (p = 0; p < s->dest_ppl; p += 2){
	/* first pixel */
	db[pos] = sb[0];
	if (++pos >= rb->size) pos = 0; /* watch out for ringbuff end? */
	db[pos] = sb[2];
	if (++pos >= rb->size) pos = 0;
	db[pos] = sb[4];
	if (++pos >= rb->size) pos = 0;
	/* second pixel */
	db[pos] = sb[1];
	if (++pos >= rb->size) pos = 0;
	db[pos] = sb[3];
	if (++pos >= rb->size) pos = 0;
	db[pos] = sb[5];
	if (++pos >= rb->size) pos = 0;
	sb += 6;
      }
    }
  }
  rb->complete_count += length;
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/********************************************************************/
/***** the basic scanning chunks for sane_read()                *****/
/********************************************************************/
/********************************************************************/


/********************************************************************/
/* Request bytes from scanner (and put in scsi_buffer)              */
/********************************************************************/
static SANE_Status
read_from_scanner (Microtek_Scanner *s, int *nlines)
{
  SANE_Status status;
  SANE_Int busy, linewidth, remaining;
  size_t buffsize;

  DBG(23, "read_from_scanner...\n");
  if (s->unscanned_lines > 0) {
    status = get_scan_status(s, &busy, &linewidth, &remaining);
    if (status != SANE_STATUS_GOOD) {
      DBG(18, "read_from_scanner:  bad get_scan_status!\n");
      return status;
    }
    DBG(18, "read_from_scanner: gss busy, linewidth, remaining:  %d, %d, %d\n",
	busy, linewidth, remaining);
  } else {
    DBG(18, "read_from_scanner: no gss/no unscanned\n");
    remaining = 0;
  }

  *nlines = MIN(remaining, s->max_scsi_lines);
  DBG(18, "sane_read:  max_scsi: %d, rem: %d, nlines: %d\n",
      s->max_scsi_lines, remaining, *nlines);

  /* grab them bytes! (only if the scanner still has bytes to give...) */
  if (*nlines > 0) {
    buffsize = *nlines * (s->pixel_bpl + s->header_bpl);/* == "* linewidth" */
    status = read_scan_data(s, *nlines, s->scsi_buffer, &buffsize);
    if (status != SANE_STATUS_GOOD) {
      DBG(18, "sane_read:  bad read_scan_data!\n");
      return status;
    }
    s->unscanned_lines -= *nlines;
    DBG(18, "sane_read:  buffsize: %lu,  unscanned: %d\n",
        (u_long) buffsize, s->unscanned_lines);
  }
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* Process scanner bytes, and shove in ring_buffer                  */
/********************************************************************/
static SANE_Status
pack_into_ring(Microtek_Scanner *s, int nlines)
{
  SANE_Status status;

  DBG(23, "pack_into_ring...\n");
  switch (s->line_format) {
  case MS_LNFMT_FLAT:
    status = pack_flat_data(s, nlines);  break;
  case MS_LNFMT_SEQ_RGB:
    status = pack_seqrgb_data(s, nlines); break;
  case MS_LNFMT_GOOFY_RGB:
    status = pack_goofyrgb_data(s, nlines); break;
  case MS_LNFMT_SEQ_2R2G2B:
    status = pack_seq2r2g2b_data(s, nlines); break;
  default:
    status = SANE_STATUS_JAMMED;
  }
  return status;
}



/********************************************************************/
/* Pack processed image bytes into frontend destination buffer      */
/********************************************************************/
static SANE_Int
pack_into_dest(SANE_Byte *dest_buffer, size_t dest_length, ring_buffer *rb)
{
  size_t ret_length = MIN(rb->complete_count, dest_length);

  DBG(23, "pack_into_dest...\n");
  DBG(23, "pack_into_dest:  rl: %lu  sz: %lu  hc: %lu\n",
      (u_long)ret_length, (u_long)rb->size, (u_long)rb->head_complete);
  /* adjust for rollover!!! */
  if ((rb->head_complete + ret_length) < rb->size) {
    memcpy(dest_buffer, rb->base + rb->head_complete, ret_length);
    rb->head_complete += ret_length;
  } else {
    size_t chunk1  = rb->size - rb->head_complete;
    size_t chunk2 = ret_length - chunk1;
    memcpy(dest_buffer, rb->base + rb->head_complete, chunk1);
    memcpy(dest_buffer + chunk1, rb->base, chunk2);
    rb->head_complete = chunk2;
  }
  rb->complete_count -= ret_length;
  return ret_length;
}



/********************************************************************/
/********************************************************************/
/****** "Registered" SANE API Functions *****************************/
/********************************************************************/
/********************************************************************/


/********************************************************************/
/* sane_init()                                                      */
/********************************************************************/
SANE_Status
sane_init(SANE_Int *version_code, SANE_Auth_Callback authorize)
{
  char dev_name[PATH_MAX];
  size_t len;
  FILE *fp;

  authorize = authorize;
  DBG_INIT();
  DBG(1, "sane_init:  MICROTEK says hello! (v%d.%d.%d)\n",
      MICROTEK_MAJOR, MICROTEK_MINOR, MICROTEK_PATCH);
  /* return the SANE version we got compiled under */
  if (version_code)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, 0);

  /* parse config file */
  fp = sanei_config_open (MICROTEK_CONFIG_FILE);
  if (!fp) {
    /* default to /dev/scanner instead of insisting on config file */
    DBG(1, "sane_init:  missing config file '%s'\n", MICROTEK_CONFIG_FILE);
    attach_scanner("/dev/scanner", 0);
    return SANE_STATUS_GOOD;
  }
  while (sanei_config_read(dev_name, sizeof (dev_name), fp)) {
    DBG(23, "sane_init:  config-> %s\n", dev_name);
    if (dev_name[0] == '#') continue;	/* ignore comments */
    if (!(strncmp("noprecal", dev_name, 8))) {
      DBG(23,
	  "sane_init:  Clever Precalibration will be forcibly disabled...\n");
      inhibit_clever_precal = SANE_TRUE;
      continue;
    }
    if (!(strncmp("norealcal", dev_name, 9))) {
      DBG(23,
	  "sane_init:  Real calibration will be forcibly disabled...\n");
      inhibit_real_calib = SANE_TRUE;
      continue;
    }
    len = strlen (dev_name);
    if (!len) continue;			/* ignore empty lines */
    sanei_config_attach_matching_devices (dev_name, attach_one);
    }
  fclose (fp);
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* sane_get_devices                                                 */
/********************************************************************/
SANE_Status
sane_get_devices(const SANE_Device ***device_list,
		 SANE_Bool local_only)
{
  Microtek_Device *dev;
  int i;

  local_only = local_only;
  DBG(10, "sane_get_devices\n");
  /* we keep an internal copy */
  if (devlist)
    free(devlist);  /* hmm, free it if we want a new one, I guess.  YYYYY*/

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

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

  *device_list = devlist;
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* sane_open                                                        */
/********************************************************************/
SANE_Status
sane_open(SANE_String_Const devicename,
	  SANE_Handle *handle)
{
  Microtek_Scanner *scanner;
  Microtek_Device *dev;
  SANE_Status status;

  DBG(10, "sane_open\n");
  /* find device... */
  DBG(23, "sane_open:  find device...\n");
  if (devicename[0]) {
    for (dev = first_dev; dev; dev = dev->next) {
      if (strcmp(dev->sane.name, devicename) == 0)
	break;
    }
    if (!dev) {  /* not in list, try manually... */
      status = attach_scanner(devicename, &dev);
      if (status != SANE_STATUS_GOOD) return status;
    }
  } else {  /* no device specified, so use first */
    dev = first_dev;
  }
  if (!dev) return SANE_STATUS_INVAL;

  /* create a scanner... */
  DBG(23, "sane_open:  create scanner...\n");
  scanner = malloc(sizeof(*scanner));
  if (!scanner) return SANE_STATUS_NO_MEM;
  memset(scanner, 0, sizeof(*scanner));

  /* initialize scanner dependent stuff */
  DBG(23, "sane_open:  initialize scanner dependent stuff...\n");
  /* ZZZZZZZZZZZZZZ */
  scanner->unit_type =
    (dev->info.unit_type & MI_UNIT_PIXELS) ? MS_UNIT_PIXELS : MS_UNIT_18INCH;
  scanner->res_type =
    (dev->info.res_step & MI_RESSTEP_1PER) ? MS_RES_1PER : MS_RES_5PER;
  scanner->midtone_support =
    (dev->info.enhance_cap & MI_ENH_CAP_MIDTONE) ? SANE_TRUE : SANE_FALSE;
  scanner->paper_length =
    (scanner->unit_type == MS_UNIT_PIXELS) ?
    dev->info.max_y :
    (SANE_Int)((double)dev->info.max_y * 8.0 /
	       (double)dev->info.base_resolution);
  /*
    (SANE_Int)(SANE_UNFIX(dev->info.max_y) * dev->info.base_resolution) :
    (SANE_Int)(SANE_UNFIX(dev->info.max_y) * 8);
    ZZZZZZZ */
  scanner->bright_r = 0;
  scanner->bright_g = 0;
  scanner->bright_b = 0;

  /* calibration shenanigans */
  if ((dev->info.extra_cap & MI_EXCAP_DIS_RECAL) &&
      (!(inhibit_real_calib))) {
    DBG(23, "sane_open:  Real calibration enabled.\n");
    scanner->allow_calibrate = SANE_FALSE;
    scanner->do_real_calib = SANE_TRUE;
    scanner->do_clever_precal = SANE_FALSE;
  } else if ((dev->info.extra_cap & MI_EXCAP_DIS_RECAL) &&
	     (!(inhibit_clever_precal))) {
    DBG(23, "sane_open:  Clever precalibration enabled.\n");
    scanner->allow_calibrate = SANE_FALSE;
    scanner->do_real_calib = SANE_FALSE;
    scanner->do_clever_precal = SANE_TRUE;
  } else {
    DBG(23, "sane_open:  All calibration routines disabled.\n");
    scanner->allow_calibrate = SANE_TRUE;
    scanner->do_real_calib = SANE_FALSE;
    scanner->do_clever_precal = SANE_FALSE;
  }

  scanner->onepass = (dev->info.modes & MI_MODES_ONEPASS);
  scanner->allowbacktrack = SANE_TRUE;  /* ??? XXXXXXX */
  scanner->reversecolors = SANE_FALSE;
  scanner->fastprescan = SANE_FALSE;
  scanner->bits_per_color = 8;

  /* init gamma tables */
  if (dev->info.max_lookup_size) {
    int j, v, max_entry;
    DBG(23, "sane_open:  init gamma tables...\n");
    scanner->gamma_entries = dev->info.max_lookup_size;
    scanner->gamma_entry_size = dev->info.gamma_size;
    scanner->gamma_bit_depth = dev->info.max_gamma_bit_depth;
    max_entry = (1 << scanner->gamma_bit_depth) - 1;
    scanner->gamma_entry_range.min = 0;
    scanner->gamma_entry_range.max = max_entry;
    scanner->gamma_entry_range.quant = 1;

    scanner->gray_lut  = calloc(scanner->gamma_entries,
			       sizeof(scanner->gray_lut[0]));
    scanner->red_lut   = calloc(scanner->gamma_entries,
			       sizeof(scanner->red_lut[0]));
    scanner->green_lut = calloc(scanner->gamma_entries,
			       sizeof(scanner->green_lut[0]));
    scanner->blue_lut  = calloc(scanner->gamma_entries,
			       sizeof(scanner->blue_lut[0]));
    if ((scanner->gray_lut == NULL) ||
	(scanner->red_lut == NULL) ||
	(scanner->green_lut == NULL) ||
	(scanner->blue_lut == NULL)) {
      DBG(23, "sane_open:  unable to allocate space for %d-entry LUT's;\n",
	  scanner->gamma_entries);
      DBG(23, "            so, gamma tables now DISABLED.\n");
      free(scanner->gray_lut);
      free(scanner->red_lut);
      free(scanner->green_lut);
      free(scanner->blue_lut);
    }
    for (j=0; j<scanner->gamma_entries; j += scanner->gamma_entry_size) {
      v = (SANE_Int)
	((double) j * (double) max_entry /
	 ((double) scanner->gamma_entries - 1.0) + 0.5);
      scanner->gray_lut[j] = v;
      scanner->red_lut[j] = v;
      scanner->green_lut[j] = v;
      scanner->blue_lut[j] = v;
    }
  } else {
    DBG(23, "sane_open:  NO gamma tables.  (max size = %lu)\n",
	(u_long)dev->info.max_lookup_size);
    scanner->gamma_entries = 0;
    scanner->gray_lut  = NULL;
    scanner->red_lut   = NULL;
    scanner->green_lut = NULL;
    scanner->blue_lut  = NULL;
  }

  DBG(23, "sane_open:  init pass-time variables...\n");
  scanner->scanning = SANE_FALSE;
  scanner->this_pass = 0;
  scanner->sfd = -1;
  scanner->dev = dev;
  scanner->sense_flags = 0;
  scanner->scan_started = SANE_FALSE;
  scanner->woe = SANE_FALSE;
  scanner->cancel = SANE_FALSE;

  DBG(23, "sane_open:  init clever cache...\n");
  /* clear out that clever cache, so it doesn't match anything */
  {
    int j;
    for (j=0; j<10; j++)
      scanner->mode_sense_cache[j] = 0;
    scanner->precal_record = MS_PRECAL_NONE;
  }

  DBG(23, "sane_open:  initialize options:  \n");
  if ((status = init_options(scanner)) != SANE_STATUS_GOOD) return status;

  scanner->next = first_handle;
  first_handle = scanner;
  *handle = scanner;
  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* sane_close                                                       */
/********************************************************************/
void
sane_close (SANE_Handle handle)
{
  Microtek_Scanner *ms = handle;

  DBG(10, "sane_close...\n");
  /* free malloc'ed stuff (strdup counts too!) */
  free((void *) ms->sod[OPT_MODE].constraint.string_list);
  free((void *) ms->sod[OPT_SOURCE].constraint.string_list);
  free(ms->val[OPT_MODE].s);
  free(ms->val[OPT_HALFTONE_PATTERN].s);
  free(ms->val[OPT_SOURCE].s);
  free(ms->val[OPT_CUSTOM_GAMMA].s);
  free(ms->gray_lut);
  free(ms->red_lut);
  free(ms->green_lut);
  free(ms->blue_lut);
  /* remove Scanner from linked list */
  if (first_handle == ms)
    first_handle = ms->next;
  else {
    Microtek_Scanner *ts = first_handle;
    while ((ts != NULL) && (ts->next != ms)) ts = ts->next;
    ts->next = ts->next->next; /* == ms->next */
  }
  /* finally, say goodbye to the Scanner */
  free(ms);
}



/********************************************************************/
/* sane_get_option_descriptor                                       */
/********************************************************************/
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle,
			    SANE_Int option)
{
  Microtek_Scanner *scanner = handle;

  DBG(96, "sane_get_option_descriptor (%d)...\n", option);
  if ((unsigned)option >= NUM_OPTIONS) return NULL;
  return &(scanner->sod[option]);
}



/********************************************************************/
/* sane_control_option                                              */
/********************************************************************/
SANE_Status
sane_control_option (SANE_Handle handle,
		     SANE_Int option,
		     SANE_Action action,
		     void *value,
		     SANE_Int *info)
{
  Microtek_Scanner *scanner = handle;
  SANE_Option_Descriptor *sod;
  Option_Value  *val;
  SANE_Status status;

  DBG(96, "sane_control_option (opt=%d,act=%d,val=%p,info=%p)\n",
      option, action, value, (void*) info);

  sod = scanner->sod;
  val = scanner->val;

  /* no changes while in mid-pass! */
  if (scanner->scanning) return SANE_STATUS_DEVICE_BUSY;
  /* and... no changes while in middle of three-pass series! */
  if (scanner->this_pass != 0) return SANE_STATUS_DEVICE_BUSY;

  if ( ((option >= NUM_OPTIONS) || (option < 0)) ||
       (!SANE_OPTION_IS_ACTIVE(scanner->sod[option].cap)) )
    return SANE_STATUS_INVAL;

  if (info) *info = 0;

  /* choose by action */
  switch (action) {

  case SANE_ACTION_GET_VALUE:
    switch (option) {
      /* word options... */
    case OPT_RESOLUTION:
    case OPT_SPEED:
    case OPT_BACKTRACK:
    case OPT_NEGATIVE:
    case OPT_PREVIEW:
    case OPT_TL_X:
    case OPT_TL_Y:
    case OPT_BR_X:
    case OPT_BR_Y:
    case OPT_EXPOSURE:
    case OPT_BRIGHTNESS:
    case OPT_CONTRAST:
    case OPT_HIGHLIGHT:
    case OPT_SHADOW:
    case OPT_MIDTONE:
    case OPT_GAMMA_BIND:
    case OPT_ANALOG_GAMMA:
    case OPT_ANALOG_GAMMA_R:
    case OPT_ANALOG_GAMMA_G:
    case OPT_ANALOG_GAMMA_B:
    case OPT_EXP_RES:
    case OPT_CALIB_ONCE:
      *(SANE_Word *)value = val[option].w;
      return SANE_STATUS_GOOD;
      /* 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(value, val[option].wa, sod[option].size);
      return SANE_STATUS_GOOD;
      /* string options... */
    case OPT_MODE:
    case OPT_HALFTONE_PATTERN:
    case OPT_CUSTOM_GAMMA:
    case OPT_SOURCE:
      strcpy(value, val[option].s);
      return SANE_STATUS_GOOD;
      /* others.... */
    case OPT_NUM_OPTS:
      *(SANE_Word *) value = NUM_OPTIONS;
      return SANE_STATUS_GOOD;
    default:
      return SANE_STATUS_INVAL;
    }
    break;

  case SANE_ACTION_SET_VALUE: {
    status = sanei_constrain_value(sod + option, value, info);
    if (status != SANE_STATUS_GOOD)
      return status;

    switch (option) {
      /* set word options... */
    case OPT_TL_X:
    case OPT_TL_Y:
    case OPT_BR_X:
    case OPT_BR_Y:
    case OPT_RESOLUTION:
      if (info)
	*info |= SANE_INFO_RELOAD_PARAMS;
      // fall through
    case OPT_SPEED:
    case OPT_PREVIEW:
    case OPT_BACKTRACK:
    case OPT_NEGATIVE:
    case OPT_EXPOSURE:
    case OPT_BRIGHTNESS:
    case OPT_CONTRAST:
    case OPT_ANALOG_GAMMA:
    case OPT_ANALOG_GAMMA_R:
    case OPT_ANALOG_GAMMA_G:
    case OPT_ANALOG_GAMMA_B:
      val[option].w = *(SANE_Word *)value;
      return SANE_STATUS_GOOD;

    case OPT_HIGHLIGHT:
    case OPT_SHADOW:
    case OPT_MIDTONE:
      val[option].w = *(SANE_Word *)value;
      /* we need to (silently) make sure shadow <= midtone <= highlight */
      if (scanner->midtone_support) {
	if (val[OPT_SHADOW].w > val[OPT_MIDTONE].w) {
	  if (option == OPT_SHADOW)
	    val[OPT_SHADOW].w = val[OPT_MIDTONE].w;
	  else
	    val[OPT_MIDTONE].w = val[OPT_SHADOW].w;
	}
	if (val[OPT_HIGHLIGHT].w < val[OPT_MIDTONE].w) {
	  if (option == OPT_HIGHLIGHT)
	    val[OPT_HIGHLIGHT].w = val[OPT_MIDTONE].w;
	  else
	    val[OPT_MIDTONE].w = val[OPT_HIGHLIGHT].w;
	}
      } else {
	if (val[OPT_SHADOW].w > val[OPT_HIGHLIGHT].w) {
	  if (option == OPT_SHADOW)
	    val[OPT_SHADOW].w = val[OPT_HIGHLIGHT].w;
	  else
	    val[OPT_HIGHLIGHT].w = val[OPT_SHADOW].w;
	}
      }
      return SANE_STATUS_GOOD;

    case OPT_EXP_RES:
      if (val[option].w != *(SANE_Word *) value) {
	val[option].w = *(SANE_Word *)value;
	if (info) *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	if (val[OPT_EXP_RES].w) {
	  sod[OPT_RESOLUTION].constraint.range = &(scanner->exp_res_range);
	  val[OPT_RESOLUTION].w *= 2;
	} else {
	  sod[OPT_RESOLUTION].constraint.range = &(scanner->res_range);
	  val[OPT_RESOLUTION].w /= 2;
	}
      }
      return SANE_STATUS_GOOD;

    case OPT_CALIB_ONCE:
      val[option].w = *(SANE_Word *)value;
      /* toggling off and on should force a recalibration... */
      if (!(val[option].w)) scanner->precal_record = MS_PRECAL_NONE;
      return SANE_STATUS_GOOD;

    case OPT_GAMMA_BIND:
    case OPT_CUSTOM_GAMMA:
      if (option == OPT_GAMMA_BIND) {
	if (val[option].w != *(SANE_Word *) value)
	  if (info) *info |= SANE_INFO_RELOAD_OPTIONS;
	val[option].w = *(SANE_Word *) value;
      } else if (option == OPT_CUSTOM_GAMMA) {
	if (val[option].s) {
	  if (strcmp(value, val[option].s))
	    if (info) *info |= SANE_INFO_RELOAD_OPTIONS;
	  free(val[option].s);
	}
	val[option].s = strdup(value);
      }
      if ( !(strcmp(val[OPT_CUSTOM_GAMMA].s, M_NONE)) ||
	   !(strcmp(val[OPT_CUSTOM_GAMMA].s, M_SCALAR)) ) {
	sod[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	sod[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	sod[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	sod[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
      }
      if ( !(strcmp(val[OPT_CUSTOM_GAMMA].s, M_NONE)) ||
	   !(strcmp(val[OPT_CUSTOM_GAMMA].s, M_TABLE)) ) {
	sod[OPT_ANALOG_GAMMA].cap |= SANE_CAP_INACTIVE;
	sod[OPT_ANALOG_GAMMA_R].cap |= SANE_CAP_INACTIVE;
	sod[OPT_ANALOG_GAMMA_G].cap |= SANE_CAP_INACTIVE;
	sod[OPT_ANALOG_GAMMA_B].cap |= SANE_CAP_INACTIVE;
      }
      if (!(strcmp(val[OPT_CUSTOM_GAMMA].s, M_SCALAR))) {
	if (val[OPT_GAMMA_BIND].w == SANE_TRUE) {
	  sod[OPT_ANALOG_GAMMA].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_R].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_G].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_B].cap |= SANE_CAP_INACTIVE;
	} else {
	  sod[OPT_ANALOG_GAMMA].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_R].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_G].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_ANALOG_GAMMA_B].cap &= ~SANE_CAP_INACTIVE;
	}
      }
      if (!(strcmp(val[OPT_CUSTOM_GAMMA].s, M_TABLE))) {
	if (val[OPT_GAMMA_BIND].w == SANE_TRUE) {
	  sod[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
	} else {
	  sod[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
	  sod[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
	}
      }
      if (!(strcmp(val[OPT_CUSTOM_GAMMA].s, M_NONE)))
	sod[OPT_GAMMA_BIND].cap |= SANE_CAP_INACTIVE;
      else if (!(strcmp(val[OPT_MODE].s, M_COLOR)))
	sod[OPT_GAMMA_BIND].cap &= ~SANE_CAP_INACTIVE;
      return SANE_STATUS_GOOD;


    case OPT_MODE:
      if (val[option].s) {
	if (strcmp(val[option].s, value))
	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	free(val[option].s);
      }
      val[option].s = strdup(value);
      if (strcmp(val[option].s, M_HALFTONE)) {
	sod[OPT_HALFTONE_PATTERN].cap |= SANE_CAP_INACTIVE;
      } else {
	sod[OPT_HALFTONE_PATTERN].cap &= ~SANE_CAP_INACTIVE;
      }
      if (strcmp(val[option].s, M_COLOR)) { /* not color */
        /*val[OPT_GAMMA_BIND].w = SANE_TRUE;*/
	DBG(23, "FLIP ma LID!  bind is %d\n", val[OPT_GAMMA_BIND].w);
	{
	  SANE_Bool Trueness = SANE_TRUE;
	  SANE_Status status;
	  status = sane_control_option(handle,
				       OPT_GAMMA_BIND,
				       SANE_ACTION_SET_VALUE,
				       &Trueness,
				       NULL);
	  DBG(23, "stat is: %d\n", status);
	}
	DBG(23, "LID be FLIPPED!  bind is %d\n", val[OPT_GAMMA_BIND].w);
	sod[OPT_GAMMA_BIND].cap |= SANE_CAP_INACTIVE;
	/*	sod[OPT_FORCE_3PASS].cap |= SANE_CAP_INACTIVE;*/
      } else {
	sod[OPT_GAMMA_BIND].cap &= ~SANE_CAP_INACTIVE;
	/*	if (scanner->dev->info.modes & MI_MODES_ONEPASS)
	  sod[OPT_FORCE_3PASS].cap &= ~SANE_CAP_INACTIVE;*/
      }
      return SANE_STATUS_GOOD;

    case OPT_HALFTONE_PATTERN:
    case OPT_SOURCE:
      if (val[option].s) free(val[option].s);
      val[option].s = strdup(value);
      return SANE_STATUS_GOOD;
    case OPT_GAMMA_VECTOR:
    case OPT_GAMMA_VECTOR_R:
    case OPT_GAMMA_VECTOR_G:
    case OPT_GAMMA_VECTOR_B:
      memcpy(val[option].wa, value, sod[option].size);
      return SANE_STATUS_GOOD;
    default:
      return SANE_STATUS_INVAL;
    }
  }
  break;

  case SANE_ACTION_SET_AUTO:
    return SANE_STATUS_UNSUPPORTED;	/* We are DUMB. */
  }
  return SANE_STATUS_GOOD;
}



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

  DBG(23, "sane_get_parameters...\n");

  if (!s->scanning) {
    /* decipher scan mode */
    if (!(strcmp(s->val[OPT_MODE].s, M_LINEART)))
      s->mode = MS_MODE_LINEART;
    else if (!(strcmp(s->val[OPT_MODE].s, M_HALFTONE)))
      s->mode = MS_MODE_HALFTONE;
    else if (!(strcmp(s->val[OPT_MODE].s, M_GRAY)))
      s->mode = MS_MODE_GRAY;
    else if (!(strcmp(s->val[OPT_MODE].s, M_COLOR)))
      s->mode = MS_MODE_COLOR;

    if (s->mode == MS_MODE_COLOR) {
      if (s->onepass) {
	/* regular one-pass */
	DBG(23, "sane_get_parameters:  regular 1-pass color\n");
	s->threepasscolor = SANE_FALSE;
	s->onepasscolor = SANE_TRUE;
	s->color_seq = s->dev->info.color_sequence;
      } else { /* 3-pass scanner */
	DBG(23, "sane_get_parameters:  regular 3-pass color\n");
	s->threepasscolor = SANE_TRUE;
	s->onepasscolor = SANE_FALSE;
	s->color_seq = s->dev->info.color_sequence;
      }
    } else { /* not color! */
	DBG(23, "sane_get_parameters:  non-color\n");
	s->threepasscolor = SANE_FALSE;
	s->onepasscolor = SANE_FALSE;
	s->color_seq = s->dev->info.color_sequence;
    }

    s->transparency = !(strcmp(s->val[OPT_SOURCE].s, M_TRANS));
    s->useADF = !(strcmp(s->val[OPT_SOURCE].s, M_AUTOFEED));
    /* disallow exp. res. during preview scan XXXXXXXXXXX */
    /*s->expandedresolution =
      (s->val[OPT_EXP_RES].w) && !(s->val[OPT_PREVIEW].w);*/
    s->expandedresolution = (s->val[OPT_EXP_RES].w);
    s->doexpansion = (s->expandedresolution && !(s->dev->info.does_expansion));

    if (s->res_type == MS_RES_1PER) {
      s->resolution = (SANE_Int)(SANE_UNFIX(s->val[OPT_RESOLUTION].w));
      s->resolution_code =
	0xFF & ((s->resolution * 100) /
		s->dev->info.base_resolution /
		(s->expandedresolution ? 2 : 1));
      DBG(23, "sane_get_parameters:  res_code = %d (%2x)\n",
	      s->resolution_code, s->resolution_code);
    } else {
      DBG(23, "sane_get_parameters:  5 percent!!!\n");
      /* XXXXXXXXXXXXX */
    }

    s->calib_once = s->val[OPT_CALIB_ONCE].w;

    s->reversecolors = s->val[OPT_NEGATIVE].w;
    s->prescan = s->val[OPT_PREVIEW].w;
    s->exposure = (s->val[OPT_EXPOSURE].w / 3) + 7;
    s->contrast = (s->val[OPT_CONTRAST].w / 7) + 7;
    s->velocity  = s->val[OPT_SPEED].w;
    s->shadow    = s->val[OPT_SHADOW].w;
    s->highlight = s->val[OPT_HIGHLIGHT].w;
    s->midtone   = s->val[OPT_MIDTONE].w;
    if (SANE_OPTION_IS_ACTIVE(s->sod[OPT_BRIGHTNESS].cap)) {
#if 1  /* this is _not_ what the docs specify! */
      if (s->val[OPT_BRIGHTNESS].w >= 0)
	s->bright_r = (SANE_Byte) (s->val[OPT_BRIGHTNESS].w);
      else
	s->bright_r = (SANE_Byte) (0x80 | (- s->val[OPT_BRIGHTNESS].w));
#else
      s->bright_r = (SANE_Byte) (s->val[OPT_BRIGHTNESS].w);
#endif
      s->bright_g = s->bright_b = s->bright_r;
      DBG(23, "bright_r of %d set to 0x%0x\n",
	  s->val[OPT_BRIGHTNESS].w, s->bright_r);
    } else {
      s->bright_r = s->bright_g = s->bright_b = 0;
    }
    /* figure out halftone pattern selection... */
    if (s->mode == MS_MODE_HALFTONE) {
      int i = 0;
      while ((halftone_mode_list[i] != NULL) &&
	     (strcmp(halftone_mode_list[i], s->val[OPT_HALFTONE_PATTERN].s)))
	i++;
      s->pattern = ((i < s->dev->info.pattern_count) ? i : 0);
    } else
      s->pattern = 0;



    {
      /* need to 'round' things properly!  XXXXXXXX */
      SANE_Int widthpix;
      double dots_per_mm = s->resolution / MM_PER_INCH;
      double units_per_mm =
	(s->unit_type == MS_UNIT_18INCH) ?
	(8.0 / MM_PER_INCH) :                       /* 1/8 inches */
	(s->dev->info.base_resolution / MM_PER_INCH);   /* pixels     */

      DBG(23, "sane_get_parameters:  dots_per_mm:  %f\n", dots_per_mm);
      DBG(23, "sane_get_parameters:  units_per_mm:  %f\n", units_per_mm);

      /* calculate frame coordinates...
       *  scanner coords are in 'units' -- pixels or 1/8"
       *  option coords are MM
       */
      s->x1 = (SANE_Int)(SANE_UNFIX(s->val[OPT_TL_X].w) * units_per_mm + 0.5);
      s->y1 = (SANE_Int)(SANE_UNFIX(s->val[OPT_TL_Y].w) * units_per_mm + 0.5);
      s->x2 = (SANE_Int)(SANE_UNFIX(s->val[OPT_BR_X].w) * units_per_mm + 0.5);
      s->y2 = (SANE_Int)(SANE_UNFIX(s->val[OPT_BR_Y].w) * units_per_mm + 0.5);
      /* bug out if length or width is <= zero... */
      if ((s->x1 >= s->x2) || (s->y1 >= s->y2))
	return SANE_STATUS_INVAL;

      /* these are just an estimate... (but *should* be completely accurate)
       * real values come from scanner after sane_start.
       */
      if (s->unit_type == MS_UNIT_18INCH) {
	/* who *knows* what happens */
	widthpix =
	  (SANE_Int)((double)(s->x2 - s->x1 + 1) / 8.0 *
		     (double)s->resolution);
	s->params.lines =
	  (SANE_Int)((double)(s->y2 - s->y1 + 1) / 8.0 *
		     (double)s->resolution);
      } else {
	/* calculate pixels per scanline returned by scanner... */
	/* scanner (E6 at least) always seems to return
	   an -even- number of -bytes- */
	if (s->resolution <= s->dev->info.base_resolution)
	  widthpix =
	    (SANE_Int)((double)(s->x2 - s->x1 + 1) *
		       (double)(s->resolution) /
		       (double)(s->dev->info.base_resolution));
	else
	  widthpix = (s->x2 - s->x1 + 1);
	if ((s->mode == MS_MODE_LINEART) ||
	    (s->mode == MS_MODE_HALFTONE)) {
	  DBG(23, "WIDTHPIX:  before: %d", widthpix);
	  widthpix = ((widthpix / 8) & ~0x1) * 8;
	  DBG(23, "after: %d", widthpix);
	} else {
	  widthpix = widthpix & ~0x1;
	}
	DBG(23, "WIDTHPIX:  before exp: %d\n", widthpix);
	/* ok, now fix up expanded-mode conversions */
	if (s->resolution > s->dev->info.base_resolution)
	  widthpix = (SANE_Int) ((double)widthpix *
				 (double)s->resolution /
				 (double)s->dev->info.base_resolution);
	s->params.pixels_per_line = widthpix;
	s->params.lines =
	  (SANE_Int)((double)(s->y2 - s->y1 + 1) *
		     (double)(s->resolution) /
		     (double)(s->dev->info.base_resolution));
      }
    }

    switch (s->mode) {
    case MS_MODE_LINEART:
    case MS_MODE_HALFTONE:
      s->multibit = SANE_FALSE;
      s->params.format = SANE_FRAME_GRAY;
      s->params.depth = 1;
      s->filter = MS_FILT_CLEAR;
      s->params.bytes_per_line = s->params.pixels_per_line / 8;
      break;
    case MS_MODE_GRAY:
      s->multibit = SANE_TRUE;
      s->params.format = SANE_FRAME_GRAY;
      s->params.depth = s->bits_per_color;
      s->filter = MS_FILT_CLEAR;
      s->params.bytes_per_line = s->params.pixels_per_line;
      break;
    case MS_MODE_COLOR:
      s->multibit = SANE_TRUE;
      if (s->onepasscolor) { /* a single-pass color scan */
	s->params.format = SANE_FRAME_RGB;
	s->params.depth = s->bits_per_color;
	s->filter = MS_FILT_CLEAR;
	s->params.bytes_per_line = s->params.pixels_per_line * 3;
      } else { /* a three-pass color scan */
	s->params.depth = s->bits_per_color;
	/* this will be correctly set in sane_start */
	s->params.format = SANE_FRAME_RED;
	s->params.bytes_per_line = s->params.pixels_per_line;
      }
      break;
    }

    DBG(23, "sane_get_parameters:  lines: %d  ppl: %d  bpl: %d\n",
	s->params.lines, s->params.pixels_per_line, s->params.bytes_per_line);

    /* also fixed in sane_start for multi-pass scans */
    s->params.last_frame = SANE_TRUE;  /* ?? XXXXXXXX */
  }

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

  return SANE_STATUS_GOOD;
}



/********************************************************************/
/* sane_start                                                       */
/********************************************************************/
static SANE_Status
sane_start_guts (SANE_Handle handle)
{
  Microtek_Scanner *s = handle;
  SANE_Status status;
  SANE_Int busy, linewidth;

  DBG(10, "sane_start...\n");

  if (s->sfd != -1) {
    DBG(23, "sane_start:  sfd already set!\n");
    return SANE_STATUS_DEVICE_BUSY;
  }

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

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

  status = sanei_scsi_open(s->dev->sane.name,
			   &(s->sfd),
			   sense_handler,
			   &(s->sense_flags));
  if (status != SANE_STATUS_GOOD) {
    DBG(10, "sane_start: open of %s failed: %s\n",
	s->dev->sane.name, sane_strstatus (status));
    s->sfd = -1;
    return end_scan(s, status);
  }

  if ((status = wait_ready(s)) != SANE_STATUS_GOOD) return end_scan(s, status);

  if ((status = finagle_precal(s)) != SANE_STATUS_GOOD)
    return end_scan(s, status);

  if ((status = scanning_frame(s)) != SANE_STATUS_GOOD) return end_scan(s, status);
  if (s->dev->info.source_options &
      (MI_SRC_FEED_BT | MI_SRC_HAS_TRANS |
       MI_SRC_FEED_SUPP | MI_SRC_HAS_FEED)) { /* ZZZZZZZZZZZ */
    if ((status = accessory(s)) != SANE_STATUS_GOOD) return end_scan(s, status);
    /* if SWslct ????  XXXXXXXXXXXXXXX */
  }
  if ((status = download_gamma(s)) != SANE_STATUS_GOOD)
    return end_scan(s, status);
  if ((status = mode_select(s)) != SANE_STATUS_GOOD)
    return end_scan(s, status);
  if (s->dev->info.does_mode1) {
    if ((status = mode_select_1(s)) != SANE_STATUS_GOOD)
      return end_scan(s, status);
  }
  if ((s->do_clever_precal) || (s->do_real_calib)) {
    if ((status = save_mode_sense(s)) != SANE_STATUS_GOOD)
      return end_scan(s, status);
  }
  if ((status = wait_ready(s)) != SANE_STATUS_GOOD) return end_scan(s, status);
  s->scan_started = SANE_TRUE;
  if ((status = start_scan(s)) != SANE_STATUS_GOOD) return end_scan(s, status);
  if ((status = get_scan_status(s, &busy,
				&linewidth, &(s->unscanned_lines))) !=
      SANE_STATUS_GOOD) {
    DBG(10, "sane_start:  get_scan_status fails\n");
    return end_scan(s, status);
  }
  /* check for a bizarre linecount */
  if ((s->unscanned_lines < 0) ||
      (s->unscanned_lines >
       (s->params.lines * 2 * (s->expandedresolution ? 2 : 1)))) {
    DBG(10, "sane_start:  get_scan_status returns weird line count %d\n",
	s->unscanned_lines);
    return end_scan(s, SANE_STATUS_DEVICE_BUSY);
  }


  /* figure out image format parameters */
  switch (s->mode) {
  case MS_MODE_LINEART:
  case MS_MODE_HALFTONE:
    s->pixel_bpl = linewidth;
    s->header_bpl = 0;
    s->ppl = linewidth * 8;
    s->planes = 1;
    s->line_format = MS_LNFMT_FLAT;
    break;
  case MS_MODE_GRAY:
    if (s->bits_per_color < 8) {
      s->pixel_bpl = linewidth;
      s->ppl = linewidth * (8 / s->bits_per_color);
    }	else {
      s->pixel_bpl = linewidth * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth;
    }
    s->header_bpl = 0;
    s->planes = 1;
    s->line_format = MS_LNFMT_FLAT;
    break;
  case MS_MODE_COLOR:
    switch (s->color_seq) {
    case MI_COLSEQ_PLANE:
      s->pixel_bpl = linewidth * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth;
      s->header_bpl = 0;
      s->planes = 1;
      s->line_format = MS_LNFMT_FLAT;
      break;
    case MI_COLSEQ_NONRGB:
      s->pixel_bpl = (linewidth - 2) * 3 * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth - 2;
      s->header_bpl = 2 * 3;
      s->planes = 3;
      s->line_format = MS_LNFMT_GOOFY_RGB;
      break;
    case MI_COLSEQ_PIXEL:
      s->pixel_bpl = linewidth * 3 * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth;
      s->header_bpl = 0;
      s->planes = 3;
      s->line_format = MS_LNFMT_FLAT;
      break;
    case MI_COLSEQ_2PIXEL:
      s->pixel_bpl = linewidth * 3 * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth;
      s->header_bpl = 0;
      s->planes = 3;
      s->line_format = MS_LNFMT_SEQ_2R2G2B;
      break;
    case MI_COLSEQ_RGB:
      s->pixel_bpl = linewidth * 3 * ((s->bits_per_color + 7) / 8);
      s->ppl = linewidth;
      s->header_bpl = 0;
      s->planes = 3;
      s->line_format = MS_LNFMT_SEQ_RGB;
      break;
    default:
      DBG(10, "sane_start:  Unknown color_sequence: %d\n",
	  s->dev->info.color_sequence);
      return end_scan(s, SANE_STATUS_INVAL);
    }
    break;
  default:
    DBG(10, "sane_start:  Unknown scan mode: %d\n", s->mode);
    return end_scan(s, SANE_STATUS_INVAL);
  }

  if ((s->doexpansion) &&
      (s->resolution > s->dev->info.base_resolution)) {
    s->dest_ppl = (int) ((double)s->ppl *
			 (double)s->resolution /
			 (double)s->dev->info.base_resolution);
    /*+ 0.5 XXXXXX */
    s->exp_aspect = (double)s->ppl / (double)s->dest_ppl;
    s->dest_pixel_bpl = (int) ceil((double)s->pixel_bpl / s->exp_aspect);
    /*s->exp_aspect =
      (double) s->dev->info.base_resolution / (double) s->resolution;*/
    /* s->dest_pixel_bpl = s->pixel_bpl / s->exp_aspect;
       s->dest_ppl = s->ppl / s->exp_aspect;*/
    /*s->dest_ppl = s->ppl / s->exp_aspect;
      s->dest_pixel_bpl = (int) ceil((double)s->dest_ppl *
      (double)s->pixel_bpl /
      (double)s->ppl);*/
  } else {
    s->exp_aspect = 1.0;
    s->dest_pixel_bpl = s->pixel_bpl;
    s->dest_ppl = s->ppl;
  }

  s->params.lines = s->unscanned_lines;
  s->params.pixels_per_line = s->dest_ppl;
  s->params.bytes_per_line = s->dest_pixel_bpl;

  /* calculate maximum line capacity of SCSI buffer */
  s->max_scsi_lines = SCSI_BUFF_SIZE / (s->pixel_bpl + s->header_bpl);
  if (s->max_scsi_lines < 1) {
    DBG(10, "sane_start:  SCSI buffer smaller that one scan line!\n");
    return end_scan(s, SANE_STATUS_NO_MEM);
  }

  s->scsi_buffer = (uint8_t *) malloc(SCSI_BUFF_SIZE * sizeof(uint8_t));
  if (s->scsi_buffer == NULL) return SANE_STATUS_NO_MEM;

  /* what's a good initial size for this? */
  s->rb = ring_alloc(s->max_scsi_lines * s->dest_pixel_bpl,
      s->dest_pixel_bpl, s->dest_ppl);

  s->undelivered_bytes = s->unscanned_lines * s->dest_pixel_bpl;

  DBG(23, "Scan Param:\n");
  DBG(23, "pix bpl: %d    hdr bpl: %d   ppl: %d\n",
      s->pixel_bpl, s->header_bpl, s->ppl);
  DBG(23, "undel bytes: %d   unscan lines: %d   planes: %d\n",
      s->undelivered_bytes, s->unscanned_lines, s->planes);
  DBG(23, "dest bpl: %d   dest ppl: %d  aspect: %f\n",
      s->dest_pixel_bpl, s->dest_ppl, s->exp_aspect);

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_start (SANE_Handle handle)
{
  Microtek_Scanner *s = handle;
  SANE_Status status;

  s->woe = SANE_TRUE;
  status = sane_start_guts(handle);
  s->woe = SANE_FALSE;
  return status;
}



/********************************************************************/
/* sane_read                                                        */
/********************************************************************/
static SANE_Status
sane_read_guts (SANE_Handle handle, SANE_Byte *dest_buffer,
		SANE_Int dest_length, SANE_Int *ret_length)
{
  Microtek_Scanner *s = handle;
  SANE_Status status;
  int nlines;
  ring_buffer *rb = s->rb;

  DBG(10, "sane_read...\n");

  *ret_length = 0; /* default: no data */
  /* we have been cancelled... */
  if (s->cancel) return end_scan(s, SANE_STATUS_CANCELLED);
  /* we're not really scanning!... */
  if (!(s->scanning)) return SANE_STATUS_INVAL;
  /* we are done scanning... */
  if (s->undelivered_bytes <= 0) return end_scan(s, SANE_STATUS_EOF);

  /* get more bytes if our ring is empty... */
  while (rb->complete_count == 0) {
    if ((status = read_from_scanner(s, &nlines)) != SANE_STATUS_GOOD) {
      DBG(18, "sane_read:  read_from_scanner failed.\n");
      return end_scan(s, status);
    }
    if ((status = pack_into_ring(s, nlines)) != SANE_STATUS_GOOD) {
      DBG(18, "sane_read:  pack_into_ring failed.\n");
      return end_scan(s, status);
    }
  }
  /* return some data to caller */
  *ret_length = pack_into_dest(dest_buffer, dest_length, rb);
  s->undelivered_bytes -= *ret_length;

  if (s->cancel) return end_scan(s, SANE_STATUS_CANCELLED);

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_read (SANE_Handle handle, SANE_Byte *dest_buffer,
	   SANE_Int dest_length, SANE_Int *ret_length)
{
  Microtek_Scanner *s = handle;
  SANE_Status status;

  s->woe = SANE_TRUE;
  status = sane_read_guts(handle, dest_buffer, dest_length, ret_length);
  s->woe = SANE_FALSE;
  return status;
}



/********************************************************************/
/* sane_exit                                                        */
/********************************************************************/
void
sane_exit (void)
{
  Microtek_Device *next;

  DBG(10, "sane_exit...\n");
  /* close all leftover Scanners */
  /*(beware of how sane_close interacts with linked list) */
  while (first_handle != NULL)
    sane_close(first_handle);
  /* free up device list */
  while (first_dev != NULL) {
    next = first_dev->next;
    free((void *) first_dev->sane.name);
    free((void *) first_dev->sane.model);
    free(first_dev);
    first_dev = next;
  }
  /* the devlist allocated by sane_get_devices */
  free(devlist);
  DBG(10, "sane_exit:  MICROTEK says goodbye.\n");
}



/********************************************************************/
/* sane_cancel                                                      */
/********************************************************************/
void
sane_cancel (SANE_Handle handle)
{
  Microtek_Scanner *ms = handle;
  DBG(10, "sane_cancel...\n");
  ms->cancel = SANE_TRUE;
  if (!(ms->woe)) end_scan(ms, SANE_STATUS_CANCELLED);
}



/********************************************************************/
/* sane_set_io_mode                                                 */
/********************************************************************/
SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  DBG(10, "sane_set_io_mode...\n");
  handle = handle;
  if (non_blocking)
    return SANE_STATUS_UNSUPPORTED;
  else
    return SANE_STATUS_GOOD;
}



/********************************************************************/
/* sane_get_select_fd                                               */
/********************************************************************/
SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  DBG(10, "sane_get_select_fd...\n");
  handle = handle, fd = fd;
  return SANE_STATUS_UNSUPPORTED;
}
