/* sane - Scanner Access Now Easy.

   Copyright (C) 2002-2006 Henning Meier-Geinitz <henning@meier-geinitz.de>
   Changes according to the sanei_thread usage by
                                         Gerhard Jaeger <gerhard@gjaeger.de>

   This file is part of the SANE package.

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

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

   You should have received a copy of the GNU General Public License
   along with this program.  If not, 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 backend is for testing frontends.
*/

#define BUILD 28

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

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>

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

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

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

#include "test.h"

#include "test-picture.c"

#define TEST_CONFIG_FILE "test.conf"

static SANE_Bool inited = SANE_FALSE;
static SANE_Device **sane_device_list = 0;
static Test_Device *first_test_device = 0;

static SANE_Range geometry_range = {
  SANE_FIX (0.0),
  SANE_FIX (200.0),
  SANE_FIX (1.0)
};

static SANE_Range resolution_range = {
  SANE_FIX (1.0),
  SANE_FIX (1200.0),
  SANE_FIX (1.0)
};

static SANE_Range ppl_loss_range = {
  0,
  128,
  1
};

static SANE_Range read_limit_size_range = {
  1,
  64 * 1024,			/* 64 KB ought to be enough for everyone :-) */
  1
};

static SANE_Range read_delay_duration_range = {
  1000,
  200 * 1000,			/* 200 msec */
  1000
};

static SANE_Range int_constraint_range = {
  4,
  192,
  2
};

static SANE_Range gamma_range = {
  0,
  255,
  1
};

static SANE_Range fixed_constraint_range = {
  SANE_FIX (-42.17),
  SANE_FIX (32767.9999),
  SANE_FIX (2.0)
};

static SANE_String_Const mode_list[] = {
  SANE_VALUE_SCAN_MODE_GRAY,
  SANE_VALUE_SCAN_MODE_COLOR,
  0
};

static SANE_String_Const order_list[] = {
  "RGB", "RBG", "GBR", "GRB", "BRG", "BGR",
  0
};

static SANE_String_Const test_picture_list[] = {
  SANE_I18N ("Solid black"), SANE_I18N ("Solid white"),
  SANE_I18N ("Color pattern"), SANE_I18N ("Grid"),
  0
};

static SANE_String_Const read_status_code_list[] = {
  SANE_I18N ("Default"), "SANE_STATUS_UNSUPPORTED", "SANE_STATUS_CANCELLED",
  "SANE_STATUS_DEVICE_BUSY", "SANE_STATUS_INVAL", "SANE_STATUS_EOF",
  "SANE_STATUS_JAMMED", "SANE_STATUS_NO_DOCS", "SANE_STATUS_COVER_OPEN",
  "SANE_STATUS_IO_ERROR", "SANE_STATUS_NO_MEM", "SANE_STATUS_ACCESS_DENIED",
  0
};

static SANE_Int depth_list[] = {
  3, 1, 8, 16
};

static SANE_Int int_constraint_word_list[] = {
  9, -42, -8, 0, 17, 42, 256, 65536, 256 * 256 * 256, 256 * 256 * 256 * 64
};

static SANE_Int fixed_constraint_word_list[] = {
  4, SANE_FIX (-32.7), SANE_FIX (12.1), SANE_FIX (42.0), SANE_FIX (129.5)
};

static SANE_String_Const string_constraint_string_list[] = {
  SANE_I18N ("First entry"), SANE_I18N ("Second entry"),
  SANE_I18N
    ("This is the very long third entry. Maybe the frontend has an idea how to "
     "display it"),
  0
};

static SANE_String_Const string_constraint_long_string_list[] = {
  SANE_I18N ("First entry"), SANE_I18N ("Second entry"), "3", "4", "5", "6",
  "7", "8", "9", "10",
  "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22",
  "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34",
  "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46",
  0
};

static SANE_Int int_array[] = {
  -17, 0, -5, 42, 91, 256 * 256 * 256 * 64
};

static SANE_Int int_array_constraint_range[] = {
  48, 6, 4, 92, 190, 16
};

#define GAMMA_RED_SIZE 256
#define GAMMA_GREEN_SIZE 256
#define GAMMA_BLUE_SIZE 256
#define GAMMA_ALL_SIZE 4096
static SANE_Int gamma_red[GAMMA_RED_SIZE]; // initialized in init_options()
static SANE_Int gamma_green[GAMMA_GREEN_SIZE];
static SANE_Int gamma_blue[GAMMA_BLUE_SIZE];
static SANE_Int gamma_all[GAMMA_ALL_SIZE];

static void
init_gamma_table(SANE_Int *tablePtr, SANE_Int count, SANE_Int max)
{
  for (int i=0; i<count; ++i) {
    tablePtr[i] = (SANE_Int)(((double)i * max)/(double)count);
  }
}

static void
print_gamma_table(SANE_Int *tablePtr, SANE_Int count)
{
  char str[200];
  str[0] = '\0';
  DBG (5, "Gamma Table Size: %d\n", count);
  for (int i=0; i<count; ++i) {
    if (i%16 == 0 && strlen(str) > 0) {
      DBG (5, "%s\n", str);
      str[0] = '\0';
    }
    sprintf (str + strlen(str), " %04X", tablePtr[i]);
  }
  if (strlen(str) > 0) {
    DBG (5, "%s\n", str);
  }
}


static SANE_Int int_array_constraint_word_list[] = {
  -42, 0, -8, 17, 42, 42
};

static SANE_String_Const source_list[] = {
  SANE_I18N ("Flatbed"), SANE_I18N ("Automatic Document Feeder"),
  0
};

static double random_factor;	/* use for fuzzyness of parameters */

/* initial values. Initial string values are set in sane_init() */
static SANE_Word init_number_of_devices = 2;
static SANE_Fixed init_tl_x = SANE_FIX (0.0);
static SANE_Fixed init_tl_y = SANE_FIX (0.0);
static SANE_Fixed init_br_x = SANE_FIX (80.0);
static SANE_Fixed init_br_y = SANE_FIX (100.0);
static SANE_Word init_resolution = 50;
static SANE_String init_mode = NULL;
static SANE_Word init_depth = 8;
static SANE_Bool init_hand_scanner = SANE_FALSE;
static SANE_Bool init_three_pass = SANE_FALSE;
static SANE_String init_three_pass_order = NULL;
static SANE_String init_scan_source = NULL;
static SANE_String init_test_picture = NULL;
static SANE_Bool init_invert_endianess = SANE_FALSE;
static SANE_Bool init_read_limit = SANE_FALSE;
static SANE_Word init_read_limit_size = 1;
static SANE_Bool init_read_delay = SANE_FALSE;
static SANE_Word init_read_delay_duration = 1000;
static SANE_String init_read_status_code = NULL;
static SANE_Bool init_fuzzy_parameters = SANE_FALSE;
static SANE_Word init_ppl_loss = 0;
static SANE_Bool init_non_blocking = SANE_FALSE;
static SANE_Bool init_select_fd = SANE_FALSE;
static SANE_Bool init_enable_test_options = SANE_FALSE;
static SANE_String init_string = NULL;
static SANE_String init_string_constraint_string_list = NULL;
static SANE_String init_string_constraint_long_string_list = NULL;

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

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

static void
swap_double (double *a, double *b)
{
  double c;

  c = *a;
  *a = *b;
  *b = c;

  return;
}

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

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

static SANE_Bool
check_handle (SANE_Handle handle)
{
  Test_Device *test_device = first_test_device;

  while (test_device)
    {
      if (test_device == (Test_Device *) handle)
	return SANE_TRUE;
      test_device = test_device->next;
    }
  return SANE_FALSE;
}

static void
cleanup_options (Test_Device * test_device)
{
  DBG (2, "cleanup_options: test_device=%p\n", (void *) test_device);

  free(test_device->val[opt_mode].s);
  test_device->val[opt_mode].s = NULL;

  free(test_device->val[opt_three_pass_order].s);
  test_device->val[opt_three_pass_order].s = NULL;

  free(test_device->val[opt_scan_source].s);
  test_device->val[opt_scan_source].s = NULL;

  free(test_device->val[opt_test_picture].s);
  test_device->val[opt_test_picture].s = NULL;

  free(test_device->val[opt_read_status_code].s);
  test_device->val[opt_read_status_code].s = NULL;

  free(test_device->val[opt_string].s);
  test_device->val[opt_string].s = NULL;

  free(test_device->val[opt_string_constraint_string_list].s);
  test_device->val[opt_string_constraint_string_list].s = NULL;

  free(test_device->val[opt_string_constraint_long_string_list].s);
  test_device->val[opt_string_constraint_long_string_list].s = NULL;

  test_device->options_initialized = SANE_FALSE;
}

static SANE_Status
init_options (Test_Device * test_device)
{
  SANE_Option_Descriptor *od;

  DBG (2, "init_options: test_device=%p\n", (void *) test_device);

  /* opt_num_opts */
  od = &test_device->opt[opt_num_opts];
  od->name = "";
  od->title = SANE_TITLE_NUM_OPTIONS;
  od->desc = SANE_DESC_NUM_OPTIONS;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_num_opts].w = num_options;

  test_device->loaded[opt_num_opts] = 1;

  /* opt_mode_group */
  od = &test_device->opt[opt_mode_group];
  od->name = "";
  od->title = SANE_I18N ("Scan Mode");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = 0;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_mode_group].w = 0;

  /* opt_mode */
  od = &test_device->opt[opt_mode];
  od->name = SANE_NAME_SCAN_MODE;
  od->title = SANE_TITLE_SCAN_MODE;
  od->desc = SANE_DESC_SCAN_MODE;
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (mode_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = mode_list;
  test_device->val[opt_mode].s = malloc (od->size);
  if (!test_device->val[opt_mode].s)
    goto fail;
  strcpy (test_device->val[opt_mode].s, init_mode);

  /* opt_depth */
  od = &test_device->opt[opt_depth];
  od->name = SANE_NAME_BIT_DEPTH;
  od->title = SANE_TITLE_BIT_DEPTH;
  od->desc = SANE_DESC_BIT_DEPTH;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  od->constraint.word_list = depth_list;
  test_device->val[opt_depth].w = init_depth;

  /* opt_hand_scanner */
  od = &test_device->opt[opt_hand_scanner];
  od->name = "hand-scanner";
  od->title = SANE_I18N ("Hand-scanner simulation");
  od->desc = SANE_I18N ("Simulate a hand-scanner.  Hand-scanners do not "
			"know the image height a priori.  Instead, they "
			"return a height of -1.  Setting this option allows one "
			"to test whether a frontend can handle this "
			"correctly.  This option also enables a fixed width "
			"of 11 cm.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_hand_scanner].w = init_hand_scanner;

  /* opt_three_pass */
  od = &test_device->opt[opt_three_pass];
  od->name = "three-pass";
  od->title = SANE_I18N ("Three-pass simulation");
  od->desc = SANE_I18N ("Simulate a three-pass scanner. In color mode, three "
			"frames are transmitted.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (strcmp (init_mode, SANE_VALUE_SCAN_MODE_COLOR) != 0)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_three_pass].w = init_three_pass;

  /* opt_three_pass_order */
  od = &test_device->opt[opt_three_pass_order];
  od->name = "three-pass-order";
  od->title = SANE_I18N ("Set the order of frames");
  od->desc = SANE_I18N ("Set the order of frames in three-pass color mode.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (order_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (strcmp (init_mode, SANE_VALUE_SCAN_MODE_COLOR) != 0)
    od->cap |= SANE_CAP_INACTIVE;
  if (!init_three_pass)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = order_list;
  test_device->val[opt_three_pass_order].s = malloc (od->size);
  if (!test_device->val[opt_three_pass_order].s)
    goto fail;
  strcpy (test_device->val[opt_three_pass_order].s, init_three_pass_order);

  /* opt_resolution */
  od = &test_device->opt[opt_resolution];
  od->name = SANE_NAME_SCAN_RESOLUTION;
  od->title = SANE_TITLE_SCAN_RESOLUTION;
  od->desc = SANE_DESC_SCAN_RESOLUTION;
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_DPI;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &resolution_range;
  test_device->val[opt_resolution].w = init_resolution;

  /* opt_scan_source */
  od = &test_device->opt[opt_scan_source];
  od->name = SANE_NAME_SCAN_SOURCE;
  od->title = SANE_TITLE_SCAN_SOURCE;
  od->desc = SANE_I18N("If Automatic Document Feeder is selected, the feeder will be 'empty' after 10 scans.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (source_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = source_list;
  test_device->val[opt_scan_source].s = malloc (od->size);
  if (!test_device->val[opt_scan_source].s)
    goto fail;
  strcpy (test_device->val[opt_scan_source].s, init_scan_source);

  /* opt_special_group */
  od = &test_device->opt[opt_special_group];
  od->name = "";
  od->title = SANE_I18N ("Special Options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = 0;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_special_group].w = 0;

  /* opt_test_picture */
  od = &test_device->opt[opt_test_picture];
  od->name = "test-picture";
  od->title = SANE_I18N ("Select the test picture");
  od->desc =
    SANE_I18N ("Select the kind of test picture. Available options:\n"
	       "Solid black: fills the whole scan with black.\n"
	       "Solid white: fills the whole scan with white.\n"
	       "Color pattern: draws various color test patterns "
	       "depending on the mode.\n"
	       "Grid: draws a black/white grid with a width and "
	       "height of 10 mm per square.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (test_picture_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = test_picture_list;
  test_device->val[opt_test_picture].s = malloc (od->size);
  if (!test_device->val[opt_test_picture].s)
    goto fail;
  strcpy (test_device->val[opt_test_picture].s, init_test_picture);

  /* opt_invert_endianness */
  od = &test_device->opt[opt_invert_endianess];
  od->name = "invert-endianess";
  od->title = SANE_I18N ("Invert endianness");
  od->desc = SANE_I18N ("Exchange upper and lower byte of image data in 16 "
			"bit modes. This option can be used to test the 16 "
			"bit modes of frontends, e.g. if the frontend uses "
			"the correct endianness.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_invert_endianess].w = init_invert_endianess;

  /* opt_read_limit */
  od = &test_device->opt[opt_read_limit];
  od->name = "read-limit";
  od->title = SANE_I18N ("Read limit");
  od->desc = SANE_I18N ("Limit the amount of data transferred with each "
			"call to sane_read().");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_read_limit].w = init_read_limit;

  /* opt_read_limit_size */
  od = &test_device->opt[opt_read_limit_size];
  od->name = "read-limit-size";
  od->title = SANE_I18N ("Size of read-limit");
  od->desc = SANE_I18N ("The (maximum) amount of data transferred with each "
			"call to sane_read().");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (!init_read_limit)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &read_limit_size_range;
  test_device->val[opt_read_limit_size].w = init_read_limit_size;

  /* opt_read_delay */
  od = &test_device->opt[opt_read_delay];
  od->name = "read-delay";
  od->title = SANE_I18N ("Read delay");
  od->desc = SANE_I18N ("Delay the transfer of data to the pipe.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_read_delay].w = init_read_delay;

  /* opt_read_delay_duration */
  od = &test_device->opt[opt_read_delay_duration];
  od->name = "read-delay-duration";
  od->title = SANE_I18N ("Duration of read-delay");
  od->desc = SANE_I18N ("How long to wait after transferring each buffer of "
			"data through the pipe.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_MICROSECOND;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (!init_read_delay)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &read_delay_duration_range;
  test_device->val[opt_read_delay_duration].w = init_read_delay_duration;

  /* opt_read_status_code */
  od = &test_device->opt[opt_read_status_code];
  od->name = "read-return-value";
  od->title = SANE_I18N ("Return-value of sane_read");
  od->desc =
    SANE_I18N ("Select the return-value of sane_read(). \"Default\" "
	       "is the normal handling for scanning. All other status "
	       "codes are for testing how the frontend handles them.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (read_status_code_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = read_status_code_list;
  test_device->val[opt_read_status_code].s = malloc (od->size);
  if (!test_device->val[opt_read_status_code].s)
    goto fail;
  strcpy (test_device->val[opt_read_status_code].s, init_read_status_code);

  /* opt_ppl_loss */
  od = &test_device->opt[opt_ppl_loss];
  od->name = "ppl-loss";
  od->title = SANE_I18N ("Loss of pixels per line");
  od->desc =
    SANE_I18N ("The number of pixels that are wasted at the end of each "
	       "line.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_PIXEL;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &ppl_loss_range;
  test_device->val[opt_ppl_loss].w = init_ppl_loss;

  /* opt_fuzzy_parameters */
  od = &test_device->opt[opt_fuzzy_parameters];
  od->name = "fuzzy-parameters";
  od->title = SANE_I18N ("Fuzzy parameters");
  od->desc = SANE_I18N ("Return fuzzy lines and bytes per line when "
			"sane_parameters() is called before sane_start().");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_fuzzy_parameters].w = init_fuzzy_parameters;

  /* opt_non_blocking */
  od = &test_device->opt[opt_non_blocking];
  od->name = "non-blocking";
  od->title = SANE_I18N ("Use non-blocking IO");
  od->desc = SANE_I18N ("Use non-blocking IO for sane_read() if supported "
			"by the frontend.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_non_blocking].w = init_non_blocking;

  /* opt_select_fd */
  od = &test_device->opt[opt_select_fd];
  od->name = "select-fd";
  od->title = SANE_I18N ("Offer select file descriptor");
  od->desc = SANE_I18N ("Offer a select filedescriptor for detecting if "
			"sane_read() will return data.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_select_fd].w = init_select_fd;

  /* opt_enable_test_options */
  od = &test_device->opt[opt_enable_test_options];
  od->name = "enable-test-options";
  od->title = SANE_I18N ("Enable test options");
  od->desc = SANE_I18N ("Enable various test options. This is for testing "
			"the ability of frontends to view and modify all the "
			"different SANE option types.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_enable_test_options].w = init_enable_test_options;

  /* opt_print_options */
  od = &test_device->opt[opt_print_options];
  od->name = "print-options";
  od->title = SANE_I18N ("Print options");
  od->desc = SANE_I18N ("Print a list of all options.");
  od->type = SANE_TYPE_BUTTON;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.string_list = 0;
  test_device->val[opt_print_options].w = 0;

  /* opt_geometry_group */
  od = &test_device->opt[opt_geometry_group];
  od->name = "";
  od->title = SANE_I18N ("Geometry");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = 0;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_geometry_group].w = 0;

  /* opt_tl_x */
  od = &test_device->opt[opt_tl_x];
  od->name = SANE_NAME_SCAN_TL_X;
  od->title = SANE_TITLE_SCAN_TL_X;
  od->desc = SANE_DESC_SCAN_TL_X;
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_MM;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &geometry_range;
  test_device->val[opt_tl_x].w = init_tl_x;

  /* opt_tl_y */
  od = &test_device->opt[opt_tl_y];
  od->name = SANE_NAME_SCAN_TL_Y;
  od->title = SANE_TITLE_SCAN_TL_Y;
  od->desc = SANE_DESC_SCAN_TL_Y;
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_MM;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &geometry_range;
  test_device->val[opt_tl_y].w = init_tl_y;

  /* opt_br_x */
  od = &test_device->opt[opt_br_x];
  od->name = SANE_NAME_SCAN_BR_X;
  od->title = SANE_TITLE_SCAN_BR_X;
  od->desc = SANE_DESC_SCAN_BR_X;
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_MM;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &geometry_range;
  test_device->val[opt_br_x].w = init_br_x;

  /* opt_br_y */
  od = &test_device->opt[opt_br_y];
  od->name = SANE_NAME_SCAN_BR_Y;
  od->title = SANE_TITLE_SCAN_BR_Y;
  od->desc = SANE_DESC_SCAN_BR_Y;
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_MM;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &geometry_range;
  test_device->val[opt_br_y].w = init_br_y;

  /* opt_bool_group */
  od = &test_device->opt[opt_bool_group];
  od->name = "";
  od->title = SANE_I18N ("Bool test options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_group].w = 0;

  /* opt_bool_soft_select_soft_detect */
  od = &test_device->opt[opt_bool_soft_select_soft_detect];
  od->name = "bool-soft-select-soft-detect";
  od->title = SANE_I18N ("(1/6) Bool soft select soft detect");
  od->desc =
    SANE_I18N ("(1/6) Bool test option that has soft select and soft "
	       "detect (and advanced) capabilities. That's just a "
	       "normal bool option.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_soft_select_soft_detect].w = SANE_FALSE;

  /* opt_bool_hard_select_soft_detect */
  od = &test_device->opt[opt_bool_hard_select_soft_detect];
  od->name = "bool-hard-select-soft-detect";
  od->title = SANE_I18N ("(2/6) Bool hard select soft detect");
  od->desc =
    SANE_I18N ("(2/6) Bool test option that has hard select and soft "
	       "detect (and advanced) capabilities. That means the "
	       "option can't be set by the frontend but by the user "
	       "(e.g. by pressing a button at the device).");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_hard_select_soft_detect].w = SANE_FALSE;

  /* opt_bool_hard_select */
  od = &test_device->opt[opt_bool_hard_select];
  od->name = "bool-hard-select";
  od->title = SANE_I18N ("(3/6) Bool hard select");
  od->desc = SANE_I18N ("(3/6) Bool test option that has hard select "
			"(and advanced) capabilities. That means the option "
			"can't be set by the frontend but by the user "
			"(e.g. by pressing a button at the device) and can't "
			"be read by the frontend.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_hard_select].w = SANE_FALSE;

  /* opt_bool_soft_detect */
  od = &test_device->opt[opt_bool_soft_detect];
  od->name = "bool-soft-detect";
  od->title = SANE_I18N ("(4/6) Bool soft detect");
  od->desc = SANE_I18N ("(4/6) Bool test option that has soft detect "
			"(and advanced) capabilities. That means the option "
			"is read-only.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_soft_detect].w = SANE_FALSE;

  /* opt_bool_soft_select_soft_detect_emulated */
  od = &test_device->opt[opt_bool_soft_select_soft_detect_emulated];
  od->name = "bool-soft-select-soft-detect-emulated";
  od->title = SANE_I18N ("(5/6) Bool soft select soft detect emulated");
  od->desc = SANE_I18N ("(5/6) Bool test option that has soft select, soft "
			"detect, and emulated (and advanced) capabilities.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED
    | SANE_CAP_EMULATED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_soft_select_soft_detect_emulated].w = SANE_FALSE;

  /* opt_bool_soft_select_soft_detect_auto */
  od = &test_device->opt[opt_bool_soft_select_soft_detect_auto];
  od->name = "bool-soft-select-soft-detect-auto";
  od->title = SANE_I18N ("(6/6) Bool soft select soft detect auto");
  od->desc = SANE_I18N ("(6/6) Bool test option that has soft select, soft "
			"detect, and automatic (and advanced) capabilities. "
			"This option can be automatically set by the backend.");
  od->type = SANE_TYPE_BOOL;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED
    | SANE_CAP_AUTOMATIC;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_bool_soft_select_soft_detect_auto].w = SANE_FALSE;

  /* opt_int_group */
  od = &test_device->opt[opt_int_group];
  od->name = "";
  od->title = SANE_I18N ("Int test options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_int_group].w = 0;

  /* opt_int */
  od = &test_device->opt[opt_int];
  od->name = "int";
  od->title = SANE_I18N ("(1/6) Int");
  od->desc = SANE_I18N ("(1/6) Int test option with no unit and no "
			"constraint set.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_int].w = 42;

  /* opt_int_constraint_range */
  od = &test_device->opt[opt_int_constraint_range];
  od->name = "int-constraint-range";
  od->title = SANE_I18N ("(2/6) Int constraint range");
  od->desc = SANE_I18N ("(2/6) Int test option with unit pixel and "
			"constraint range set. Minimum is 4, maximum 192, and "
			"quant is 2.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_PIXEL;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &int_constraint_range;
  test_device->val[opt_int_constraint_range].w = 26;

  /* opt_int_constraint_word_list */
  od = &test_device->opt[opt_int_constraint_word_list];
  od->name = "int-constraint-word-list";
  od->title = SANE_I18N ("(3/6) Int constraint word list");
  od->desc = SANE_I18N ("(3/6) Int test option with unit bits and "
			"constraint word list set.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_BIT;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  od->constraint.word_list = int_constraint_word_list;
  test_device->val[opt_int_constraint_word_list].w = 42;

  /* opt_int_array */
  od = &test_device->opt[opt_int_array];
  od->name = "int-constraint-array";
  od->title = SANE_I18N ("(4/6) Int array");
  od->desc = SANE_I18N ("(4/6) Int test option with unit mm and using "
			"an array without constraints.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_MM;
  od->size = 6 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_int_array].wa = &int_array[0];

  /* opt_int_array_constraint_range */
  od = &test_device->opt[opt_int_array_constraint_range];
  od->name = "int-constraint-array-constraint-range";
  od->title = SANE_I18N ("(5/6) Int array constraint range");
  od->desc = SANE_I18N ("(5/6) Int test option with unit dpi and using "
			"an array with a range constraint. Minimum is 4, "
			"maximum 192, and quant is 2.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_DPI;
  od->size = 6 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &int_constraint_range;
  test_device->val[opt_int_array_constraint_range].wa =
    &int_array_constraint_range[0];

  /* opt_gamma_red */
  init_gamma_table(gamma_red, GAMMA_RED_SIZE, gamma_range.max);
  od = &test_device->opt[opt_gamma_red];
  od->name = SANE_NAME_GAMMA_VECTOR_R;
  od->title = SANE_TITLE_GAMMA_VECTOR_R;
  od->desc = SANE_DESC_GAMMA_VECTOR_R;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = 256 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &gamma_range;
  test_device->val[opt_gamma_red].wa = &gamma_red[0];

  /* opt_gamma_green */
  init_gamma_table(gamma_green, GAMMA_GREEN_SIZE, gamma_range.max);
  od = &test_device->opt[opt_gamma_green];
  od->name = SANE_NAME_GAMMA_VECTOR_G;
  od->title = SANE_TITLE_GAMMA_VECTOR_G;
  od->desc = SANE_DESC_GAMMA_VECTOR_G;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = 256 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &gamma_range;
  test_device->val[opt_gamma_green].wa = &gamma_green[0];

  /* opt_gamma_blue */
  init_gamma_table(gamma_blue, GAMMA_BLUE_SIZE, gamma_range.max);
  od = &test_device->opt[opt_gamma_blue];
  od->name = SANE_NAME_GAMMA_VECTOR_B;
  od->title = SANE_TITLE_GAMMA_VECTOR_B;
  od->desc = SANE_DESC_GAMMA_VECTOR_B;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = 256 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &gamma_range;
  test_device->val[opt_gamma_blue].wa = &gamma_blue[0];

  /* opt_gamma_all */
  init_gamma_table(gamma_all, GAMMA_ALL_SIZE, gamma_range.max);
  print_gamma_table(gamma_all, GAMMA_ALL_SIZE);
  od = &test_device->opt[opt_gamma_all];
  od->name = SANE_NAME_GAMMA_VECTOR;
  od->title = SANE_TITLE_GAMMA_VECTOR;
  od->desc = SANE_DESC_GAMMA_VECTOR;
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_NONE;
  od->size = GAMMA_ALL_SIZE * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &gamma_range;
  test_device->val[opt_gamma_all].wa = &gamma_all[0];

  /* opt_int_array_constraint_word_list */
  od = &test_device->opt[opt_int_array_constraint_word_list];
  od->name = "int-constraint-array-constraint-word-list";
  od->title = SANE_I18N ("(6/6) Int array constraint word list");
  od->desc = SANE_I18N ("(6/6) Int test option with unit percent and using "
			"an array with a word list constraint.");
  od->type = SANE_TYPE_INT;
  od->unit = SANE_UNIT_PERCENT;
  od->size = 6 * sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  od->constraint.word_list = int_constraint_word_list;
  test_device->val[opt_int_array_constraint_word_list].wa =
    &int_array_constraint_word_list[0];

  /* opt_fixed_group */
  od = &test_device->opt[opt_fixed_group];
  od->name = "";
  od->title = SANE_I18N ("Fixed test options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = SANE_CAP_ADVANCED;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_fixed_group].w = 0;

  /* opt_fixed */
  od = &test_device->opt[opt_fixed];
  od->name = "fixed";
  od->title = SANE_I18N ("(1/3) Fixed");
  od->desc = SANE_I18N ("(1/3) Fixed test option with no unit and no "
			"constraint set.");
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_fixed].w = SANE_FIX (42.0);

  /* opt_fixed_constraint_range */
  od = &test_device->opt[opt_fixed_constraint_range];
  od->name = "fixed-constraint-range";
  od->title = SANE_I18N ("(2/3) Fixed constraint range");
  od->desc = SANE_I18N ("(2/3) Fixed test option with unit microsecond and "
			"constraint range set. Minimum is -42.17, maximum "
			"32767.9999, and quant is 2.0.");
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_MICROSECOND;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_RANGE;
  od->constraint.range = &fixed_constraint_range;
  test_device->val[opt_fixed_constraint_range].w = SANE_FIX (41.83);

  /* opt_fixed_constraint_word_list */
  od = &test_device->opt[opt_fixed_constraint_word_list];
  od->name = "fixed-constraint-word-list";
  od->title = SANE_I18N ("(3/3) Fixed constraint word list");
  od->desc = SANE_I18N ("(3/3) Fixed test option with no unit and "
			"constraint word list set.");
  od->type = SANE_TYPE_FIXED;
  od->unit = SANE_UNIT_NONE;
  od->size = sizeof (SANE_Word);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT | SANE_CAP_ADVANCED;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  od->constraint.word_list = fixed_constraint_word_list;
  test_device->val[opt_fixed_constraint_word_list].w = SANE_FIX (42.0);

  /* opt_string_group */
  od = &test_device->opt[opt_string_group];
  od->name = "";
  od->title = SANE_I18N ("String test options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = 0;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_string_group].w = 0;

  /* opt_string */
  od = &test_device->opt[opt_string];
  od->name = "string";
  od->title = SANE_I18N ("(1/3) String");
  od->desc = SANE_I18N ("(1/3) String test option without constraint.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = strlen (init_string) + 1;
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.string_list = 0;
  test_device->val[opt_string].s = malloc (od->size);
  if (!test_device->val[opt_string].s)
    goto fail;
  strcpy (test_device->val[opt_string].s, init_string);

  /* opt_string_constraint_string_list */
  od = &test_device->opt[opt_string_constraint_string_list];
  od->name = "string-constraint-string-list";
  od->title = SANE_I18N ("(2/3) String constraint string list");
  od->desc = SANE_I18N ("(2/3) String test option with string list "
			"constraint.");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (string_constraint_string_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = string_constraint_string_list;
  test_device->val[opt_string_constraint_string_list].s = malloc (od->size);
  if (!test_device->val[opt_string_constraint_string_list].s)
    goto fail;
  strcpy (test_device->val[opt_string_constraint_string_list].s,
	  init_string_constraint_string_list);

  /* opt_string_constraint_long_string_list */
  od = &test_device->opt[opt_string_constraint_long_string_list];
  od->name = "string-constraint-long-string-list";
  od->title = SANE_I18N ("(3/3) String constraint long string list");
  od->desc = SANE_I18N ("(3/3) String test option with string list "
			"constraint. Contains some more entries...");
  od->type = SANE_TYPE_STRING;
  od->unit = SANE_UNIT_NONE;
  od->size = max_string_size (string_constraint_long_string_list);
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  od->constraint.string_list = string_constraint_long_string_list;
  test_device->val[opt_string_constraint_long_string_list].s =
    malloc (od->size);
  if (!test_device->val[opt_string_constraint_long_string_list].s)
    goto fail;
  strcpy (test_device->val[opt_string_constraint_long_string_list].s,
	  init_string_constraint_long_string_list);

  /* opt_button_group */
  od = &test_device->opt[opt_button_group];
  od->name = "";
  od->title = SANE_I18N ("Button test options");
  od->desc = "";
  od->type = SANE_TYPE_GROUP;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = 0;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.range = 0;
  test_device->val[opt_button_group].w = 0;

  /* opt_button */
  od = &test_device->opt[opt_button];
  od->name = "button";
  od->title = SANE_I18N ("(1/1) Button");
  od->desc = SANE_I18N ("(1/1) Button test option. Prints some text...");
  od->type = SANE_TYPE_BUTTON;
  od->unit = SANE_UNIT_NONE;
  od->size = 0;
  od->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  if (init_enable_test_options == SANE_FALSE)
    od->cap |= SANE_CAP_INACTIVE;
  od->constraint_type = SANE_CONSTRAINT_NONE;
  od->constraint.string_list = 0;
  test_device->val[opt_button].w = 0;

  return SANE_STATUS_GOOD;

fail:
  cleanup_options (test_device);
  return SANE_STATUS_NO_MEM;
}

static void
cleanup_initial_string_values ()
{
  // Cleanup backing memory for initial values of string options.
  free (init_mode);
  init_mode = NULL;
  free (init_three_pass_order);
  init_three_pass_order = NULL;
  free (init_scan_source);
  init_scan_source = NULL;
  free (init_test_picture);
  init_test_picture = NULL;
  free (init_read_status_code);
  init_read_status_code = NULL;
  free (init_string);
  init_string = NULL;
  free (init_string_constraint_string_list);
  init_string_constraint_string_list = NULL;
  free (init_string_constraint_long_string_list);
  init_string_constraint_long_string_list = NULL;
}

static void
cleanup_test_device (Test_Device * test_device)
{
  DBG (2, "cleanup_test_device: test_device=%p\n", (void *) test_device);
  if (test_device->options_initialized)
    cleanup_options (test_device);
  if (test_device->name)
    free (test_device->name);
  free (test_device);
}

static SANE_Status
read_option (SANE_String line, SANE_String option_string,
	     parameter_type p_type, void *value)
{
  SANE_String_Const cp;
  SANE_Char *word, *end;

  word = 0;

  cp = sanei_config_get_string (line, &word);

  if (!word)
    return SANE_STATUS_INVAL;

  if (strcmp (word, option_string) != 0)
    {
      free(word);
      return SANE_STATUS_INVAL;
    }

  free (word);
  word = 0;

  switch (p_type)
    {
    case param_none:
      return SANE_STATUS_GOOD;
    case param_bool:
      {
	cp = sanei_config_get_string (cp, &word);
	if (!word)
	  return SANE_STATUS_INVAL;
	if (strlen (word) == 0)
	  {
	    DBG (3, "read_option: option `%s' requires parameter\n",
		 option_string);
	    return SANE_STATUS_INVAL;
	  }
	if (strcmp (word, "true") != 0 && strcmp (word, "false") != 0)
	  {
	    DBG (3, "read_option: option `%s' requires parameter "
		 "`true' or `false'\n", option_string);
	    return SANE_STATUS_INVAL;
	  }
	else if (strcmp (word, "true") == 0)
	  *(SANE_Bool *) value = SANE_TRUE;
	else
	  *(SANE_Bool *) value = SANE_FALSE;
	DBG (3, "read_option: set option `%s' to %s\n", option_string,
	     *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	break;
      }
    case param_int:
      {
	SANE_Int int_value;

	cp = sanei_config_get_string (cp, &word);
	if (!word)
	  return SANE_STATUS_INVAL;
	errno = 0;
	int_value = (SANE_Int) strtol (word, &end, 0);
	if (end == word)
	  {
	    DBG (3, "read_option: option `%s' requires parameter\n",
		 option_string);
	    return SANE_STATUS_INVAL;
	  }
	else if (errno)
	  {
	    DBG (3, "read_option: option `%s': can't parse parameter `%s' "
		 "(%s)\n", option_string, word, strerror (errno));
	    return SANE_STATUS_INVAL;
	  }
	else
	  {
	    DBG (3, "read_option: set option `%s' to %d\n", option_string,
		 int_value);
	    *(SANE_Int *) value = int_value;
	  }
	break;
      }
    case param_fixed:
      {
	double double_value;
	SANE_Fixed fixed_value;

	cp = sanei_config_get_string (cp, &word);
	if (!word)
	  return SANE_STATUS_INVAL;
	errno = 0;
	double_value = strtod (word, &end);
	if (end == word)
	  {
	    DBG (3, "read_option: option `%s' requires parameter\n",
		 option_string);
	    return SANE_STATUS_INVAL;
	  }
	else if (errno)
	  {
	    DBG (3, "read_option: option `%s': can't parse parameter `%s' "
		 "(%s)\n", option_string, word, strerror (errno));
	    return SANE_STATUS_INVAL;
	  }
	else
	  {
	    DBG (3, "read_option: set option `%s' to %.0f\n", option_string,
		 double_value);
	    fixed_value = SANE_FIX (double_value);
	    *(SANE_Fixed *) value = fixed_value;
	  }
	break;
      }
    case param_string:
      {
	cp = sanei_config_get_string (cp, &word);
	if (!word)
	  return SANE_STATUS_INVAL;
	if (strlen (word) == 0)
	  {
	    DBG (3, "read_option: option `%s' requires parameter\n",
		 option_string);
	    return SANE_STATUS_INVAL;
	  }
	else
	  {
	    DBG (3, "read_option: set option `%s' to `%s'\n", option_string,
		 word);
	    if (*(SANE_String *) value)
	      free (*(SANE_String *) value);
	    *(SANE_String *) value = strdup (word);
	    if (!*(SANE_String *) value)
	      return SANE_STATUS_NO_MEM;
	  }
	break;
      }
    default:
      DBG (1, "read_option: unknown param_type %d\n", p_type);
      return SANE_STATUS_INVAL;
    }				/* switch */

  if (word)
    free (word);
  return SANE_STATUS_GOOD;
}

static SANE_Status
reader_process (Test_Device * test_device, SANE_Int fd)
{
  SANE_Status status;
  SANE_Word byte_count = 0, bytes_total;
  SANE_Byte *buffer = 0;
  ssize_t bytes_written = 0;
  size_t buffer_size = 0, write_count = 0;

  DBG (2, "(child) reader_process: test_device=%p, fd=%d\n",
       (void *) test_device, fd);

  bytes_total = test_device->lines * test_device->bytes_per_line;
  status = init_picture_buffer (test_device, &buffer, &buffer_size);
  if (status != SANE_STATUS_GOOD)
    return status;

  DBG (2, "(child) reader_process: buffer=%p, buffersize=%lu\n",
       buffer, (u_long) buffer_size);

  while (byte_count < bytes_total)
    {
      if (write_count == 0)
	{
	  write_count = buffer_size;
	  if (byte_count + (SANE_Word) write_count > bytes_total)
	    write_count = bytes_total - byte_count;

	  if (test_device->val[opt_read_delay].w == SANE_TRUE)
	    usleep (test_device->val[opt_read_delay_duration].w);
	}
      bytes_written = write (fd, buffer, write_count);
      if (bytes_written < 0)
	{
	  DBG (1, "(child) reader_process: write returned %s\n",
	       strerror (errno));
	  return SANE_STATUS_IO_ERROR;
	}
      byte_count += bytes_written;
      DBG (4, "(child) reader_process: wrote %ld bytes of %lu (%d total)\n",
	   (long) bytes_written, (u_long) write_count, byte_count);
      write_count -= bytes_written;
    }

  free (buffer);

  if (sanei_thread_is_forked ())
    {
	  DBG (4, "(child) reader_process: finished,  wrote %d bytes, expected %d "
       "bytes, now waiting\n", byte_count, bytes_total);
	  while (SANE_TRUE)
	    sleep (10);
	  DBG (4, "(child) reader_process: this should have never happened...");
	  close (fd);
    }
  else
    {
	  DBG (4, "(child) reader_process: finished,  wrote %d bytes, expected %d "
       "bytes\n", byte_count, bytes_total);
    }
  return SANE_STATUS_GOOD;
}

/*
 * this code either runs in child or thread context...
 */
static int
reader_task (void *data)
{
  SANE_Status status;
  struct SIGACTION act;
  struct Test_Device *test_device = (struct Test_Device *) data;

  DBG (2, "reader_task started\n");
  if (sanei_thread_is_forked ())
    {
      DBG (3, "reader_task started (forked)\n");
      close (test_device->pipe);
      test_device->pipe = -1;

    }
  else
    {
      DBG (3, "reader_task started (as thread)\n");
    }

  memset (&act, 0, sizeof (act));
  sigaction (SIGTERM, &act, 0);

  status = reader_process (test_device, test_device->reader_fds);
  DBG (2, "(child) reader_task: reader_process finished (%s)\n",
       sane_strstatus (status));
  return (int) status;
}

static SANE_Status
finish_pass (Test_Device * test_device)
{
  SANE_Status return_status = SANE_STATUS_GOOD;

  DBG (2, "finish_pass: test_device=%p\n", (void *) test_device);
  test_device->scanning = SANE_FALSE;
  if (test_device->pipe >= 0)
    {
      DBG (2, "finish_pass: closing pipe\n");
      close (test_device->pipe);
      DBG (2, "finish_pass: pipe closed\n");
      test_device->pipe = -1;
    }
  if (sanei_thread_is_valid (test_device->reader_pid))
    {
      int status;
      SANE_Pid pid;

      DBG (2, "finish_pass: terminating reader process %ld\n",
	   (long) test_device->reader_pid);
      sanei_thread_kill (test_device->reader_pid);
      pid = sanei_thread_waitpid (test_device->reader_pid, &status);
      if (!sanei_thread_is_valid (pid))
	{
	  DBG (1,
	       "finish_pass: sanei_thread_waitpid failed, already terminated? (%s)\n",
	       strerror (errno));
	}
      else
	{
	  DBG (2, "finish_pass: reader process terminated with status: %s\n",
	       sane_strstatus (status));
	}
      sanei_thread_invalidate (test_device->reader_pid);
    }
  /* this happens when running in thread context... */
  if (test_device->reader_fds >= 0)
    {
      DBG (2, "finish_pass: closing reader pipe\n");
      close (test_device->reader_fds);
      DBG (2, "finish_pass: reader pipe closed\n");
      test_device->reader_fds = -1;
    }
  return return_status;
}

static void
print_options (Test_Device * test_device)
{
  SANE_Option_Descriptor *od;
  SANE_Word option_number;
  SANE_Char caps[1024];

  for (option_number = 0; option_number < num_options; option_number++)
    {
      od = &test_device->opt[option_number];
      DBG (0, "-----> number: %d\n", option_number);
      DBG (0, "         name: `%s'\n", od->name);
      DBG (0, "        title: `%s'\n", od->title);
      DBG (0, "  description: `%s'\n", od->desc);
      DBG (0, "         type: %s\n",
	   od->type == SANE_TYPE_BOOL ? "SANE_TYPE_BOOL" :
	   od->type == SANE_TYPE_INT ? "SANE_TYPE_INT" :
	   od->type == SANE_TYPE_FIXED ? "SANE_TYPE_FIXED" :
	   od->type == SANE_TYPE_STRING ? "SANE_TYPE_STRING" :
	   od->type == SANE_TYPE_BUTTON ? "SANE_TYPE_BUTTON" :
	   od->type == SANE_TYPE_GROUP ? "SANE_TYPE_GROUP" : "unknown");
      DBG (0, "         unit: %s\n",
	   od->unit == SANE_UNIT_NONE ? "SANE_UNIT_NONE" :
	   od->unit == SANE_UNIT_PIXEL ? "SANE_UNIT_PIXEL" :
	   od->unit == SANE_UNIT_BIT ? "SANE_UNIT_BIT" :
	   od->unit == SANE_UNIT_MM ? "SANE_UNIT_MM" :
	   od->unit == SANE_UNIT_DPI ? "SANE_UNIT_DPI" :
	   od->unit == SANE_UNIT_PERCENT ? "SANE_UNIT_PERCENT" :
	   od->unit == SANE_UNIT_MICROSECOND ? "SANE_UNIT_MICROSECOND" :
	   "unknown");
      DBG (0, "         size: %d\n", od->size);
      caps[0] = '\0';
      if (od->cap & SANE_CAP_SOFT_SELECT)
	strcat (caps, "SANE_CAP_SOFT_SELECT ");
      if (od->cap & SANE_CAP_HARD_SELECT)
	strcat (caps, "SANE_CAP_HARD_SELECT ");
      if (od->cap & SANE_CAP_SOFT_DETECT)
	strcat (caps, "SANE_CAP_SOFT_DETECT ");
      if (od->cap & SANE_CAP_EMULATED)
	strcat (caps, "SANE_CAP_EMULATED ");
      if (od->cap & SANE_CAP_AUTOMATIC)
	strcat (caps, "SANE_CAP_AUTOMATIC ");
      if (od->cap & SANE_CAP_INACTIVE)
	strcat (caps, "SANE_CAP_INACTIVE ");
      if (od->cap & SANE_CAP_ADVANCED)
	strcat (caps, "SANE_CAP_ADVANCED ");
      DBG (0, " capabilities: %s\n", caps);
      DBG (0, "constraint type: %s\n",
	   od->constraint_type == SANE_CONSTRAINT_NONE ?
	   "SANE_CONSTRAINT_NONE" :
	   od->constraint_type == SANE_CONSTRAINT_RANGE ?
	   "SANE_CONSTRAINT_RANGE" :
	   od->constraint_type == SANE_CONSTRAINT_WORD_LIST ?
	   "SANE_CONSTRAINT_WORD_LIST" :
	   od->constraint_type == SANE_CONSTRAINT_STRING_LIST ?
	   "SANE_CONSTRAINT_STRING_LIST" : "unknown");
    }
}

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


SANE_Status
sane_init (SANE_Int * __sane_unused__ version_code, SANE_Auth_Callback __sane_unused__ authorize)
{
  FILE *fp;
  SANE_Int linenumber;
  SANE_Char line[PATH_MAX], *word = NULL;
  SANE_String_Const cp;
  SANE_Device *sane_device;
  Test_Device *test_device, *previous_device;
  SANE_Int num;

  DBG_INIT ();
  sanei_thread_init ();

  test_device = 0;
  previous_device = 0;

  DBG (1, "sane_init: SANE test backend version %d.%d.%d from %s\n", SANE_CURRENT_MAJOR,
       SANE_CURRENT_MINOR, BUILD, PACKAGE_STRING);

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

  if (inited)
    DBG (3, "sane_init: warning: already inited\n");

  // Setup initial values of string options. Call free initially in case we've
  // already called sane_init and these values are already non-null.
  free (init_mode);
  init_mode = strdup (SANE_VALUE_SCAN_MODE_GRAY);
  if (!init_mode)
    goto fail;

  free (init_three_pass_order);
  init_three_pass_order = strdup ("RGB");
  if (!init_three_pass_order)
    goto fail;

  free (init_scan_source);
  init_scan_source = strdup ("Flatbed");
  if (!init_scan_source)
    goto fail;

  free (init_test_picture);
  init_test_picture = strdup ("Solid black");
  if (!init_test_picture)
    goto fail;

  free (init_read_status_code);
  init_read_status_code = strdup ("Default");
  if (!init_read_status_code)
    goto fail;

  free (init_string);
  init_string = strdup ("This is the contents of the string option. "
    "Fill some more words to see how the frontend behaves.");
  if (!init_string)
    goto fail;

  free (init_string_constraint_string_list);
  init_string_constraint_string_list = strdup ("First entry");
  if (!init_string_constraint_string_list)
    goto fail;

  free (init_string_constraint_long_string_list);
  init_string_constraint_long_string_list = strdup ("First entry");
  if (!init_string_constraint_long_string_list)
    goto fail;

  fp = sanei_config_open (TEST_CONFIG_FILE);
  if (fp)
    {
      linenumber = 0;
      DBG (4, "sane_init: reading config file `%s'\n", TEST_CONFIG_FILE);
      while (sanei_config_read (line, sizeof (line), fp))
	{
	  if (word)
	    free (word);
	  word = NULL;
	  linenumber++;

	  cp = sanei_config_get_string (line, &word);
	  if (!word || cp == line)
	    {
	      DBG (5,
		   "sane_init: config file line %3d: ignoring empty line\n",
		   linenumber);
	      continue;
	    }
	  if (word[0] == '#')
	    {
	      DBG (5,
		   "sane_init: config file line %3d: ignoring comment line\n",
		   linenumber);
	      continue;
	    }

	  DBG (5, "sane_init: config file line %3d: `%s'\n",
	       linenumber, line);
	  if (read_option (line, "number_of_devices", param_int,
			   &init_number_of_devices) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "mode", param_string,
			   &init_mode) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "hand-scanner", param_bool,
			   &init_hand_scanner) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "three-pass", param_bool,
			   &init_three_pass) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "three-pass-order", param_string,
			   &init_three_pass_order) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "resolution_min", param_fixed,
			   &resolution_range.min) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "resolution_max", param_fixed,
			   &resolution_range.max) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "resolution_quant", param_fixed,
			   &resolution_range.quant) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "resolution", param_fixed,
			   &init_resolution) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "depth", param_int,
			   &init_depth) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "scan-source", param_string,
			   &init_scan_source) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "test-picture", param_string,
			   &init_test_picture) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "invert-endianess", param_bool,
			   &init_invert_endianess) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "read-limit", param_bool,
			   &init_read_limit) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "read-limit-size", param_int,
			   &init_read_limit_size) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "read-delay", param_bool,
			   &init_read_delay) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "read-delay-duration", param_int,
			   &init_read_delay_duration) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "read-status-code", param_string,
			   &init_read_status_code) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "ppl-loss", param_int,
			   &init_ppl_loss) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "fuzzy-parameters", param_bool,
			   &init_fuzzy_parameters) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "non-blocking", param_bool,
			   &init_non_blocking) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "select-fd", param_bool,
			   &init_select_fd) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "enable-test-options", param_bool,
			   &init_enable_test_options) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "geometry_min", param_fixed,
			   &geometry_range.min) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "geometry_max", param_fixed,
			   &geometry_range.max) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "geometry_quant", param_fixed,
			   &geometry_range.quant) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "tl_x", param_fixed,
			   &init_tl_x) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "tl_y", param_fixed,
			   &init_tl_y) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "br_x", param_fixed,
			   &init_br_x) == SANE_STATUS_GOOD)
	    continue;
	  if (read_option (line, "br_y", param_fixed,
			   &init_br_y) == SANE_STATUS_GOOD)
	    continue;

	  DBG (3, "sane-init: I don't know how to handle option `%s'\n",
	       word);
	}			/* while */
      if (word)
	free (word);
      fclose (fp);
    }				/* if */
  else
    {
      DBG (3, "sane_init: couldn't find config file (%s), using default "
	   "settings\n", TEST_CONFIG_FILE);
    }

  /* create devices */
  sane_device_list =
    malloc ((init_number_of_devices + 1) * sizeof (sane_device));
  if (!sane_device_list)
    goto fail;
  for (num = 0; num < init_number_of_devices; num++)
    {
      SANE_Char number_string[PATH_MAX];

      test_device = calloc (sizeof (*test_device), 1);
      if (!test_device)
	goto fail_device;
      test_device->sane.vendor = "Noname";
      test_device->sane.type = "virtual device";
      test_device->sane.model = "frontend-tester";
      snprintf (number_string, sizeof (number_string), "%d", num);
      number_string[sizeof (number_string) - 1] = '\0';
      test_device->name = strdup (number_string);
      if (!test_device->name)
	goto fail_name;
      test_device->sane.name = test_device->name;
      if (previous_device)
	previous_device->next = test_device;
      previous_device = test_device;
      if (num == 0)
	first_test_device = test_device;
      sane_device_list[num] = &test_device->sane;
      test_device->open = SANE_FALSE;
      test_device->eof = SANE_FALSE;
      test_device->scanning = SANE_FALSE;
      test_device->cancelled = SANE_FALSE;
      test_device->options_initialized = SANE_FALSE;
      sanei_thread_initialize (test_device->reader_pid);
      test_device->pipe = -1;
      DBG (4, "sane_init: new device: `%s' is a %s %s %s\n",
	   test_device->sane.name, test_device->sane.vendor,
	   test_device->sane.model, test_device->sane.type);
    }
  test_device->next = 0;
  sane_device_list[num] = 0;
  srand (time (NULL));
  random_factor = ((double) rand ()) / RAND_MAX + 0.5;
  inited = SANE_TRUE;
  return SANE_STATUS_GOOD;

fail_name:
  // test_device refers to the last device we were creating, which has not
  // yet been added to the linked list of devices.
  free (test_device);
fail_device:
  // Now, iterate through the linked list of devices to clean up any successful
  // devices.
  test_device = first_test_device;
  while (test_device)
    {
      previous_device = test_device;
      test_device = test_device->next;
      cleanup_test_device (previous_device);
    }
  free (sane_device_list);
fail:
  cleanup_initial_string_values ();
  return SANE_STATUS_NO_MEM;
}

void
sane_exit (void)
{
  Test_Device *test_device, *previous_device;

  DBG (2, "sane_exit\n");
  if (!inited)
    {
      DBG (1, "sane_exit: not inited, call sane_init() first\n");
      return;
    }

  test_device = first_test_device;
  while (test_device)
    {
      DBG (4, "sane_exit: freeing device %s\n", test_device->name);
      previous_device = test_device;
      test_device = test_device->next;
      cleanup_test_device (previous_device);
    }
  DBG (4, "sane_exit: freeing device list\n");
  if (sane_device_list)
    free (sane_device_list);
  sane_device_list = NULL;
  first_test_device = NULL;

  cleanup_initial_string_values ();
  inited = SANE_FALSE;
  return;
}


SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{

  DBG (2, "sane_get_devices: device_list=%p, local_only=%d\n",
       (void *) device_list, local_only);
  if (!inited)
    {
      DBG (1, "sane_get_devices: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }

  if (!device_list)
    {
      DBG (1, "sane_get_devices: device_list == 0\n");
      return SANE_STATUS_INVAL;
    }
  *device_list = (const SANE_Device **) sane_device_list;
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devicename, SANE_Handle * handle)
{
  Test_Device *test_device = first_test_device;
  SANE_Status status;

  DBG (2, "sane_open: devicename = \"%s\", handle=%p\n",
       devicename, (void *) handle);
  if (!inited)
    {
      DBG (1, "sane_open: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }

  if (!handle)
    {
      DBG (1, "sane_open: handle == 0\n");
      return SANE_STATUS_INVAL;
    }

  if (!devicename || strlen (devicename) == 0)
    {
      DBG (2, "sane_open: device name NULL or empty\n");
    }
  else
    {
      for (test_device = first_test_device; test_device;
	   test_device = test_device->next)
	{
	  if (strcmp (devicename, test_device->name) == 0)
	    break;
	}
    }
  if (!test_device)
    {
      DBG (1, "sane_open: device `%s' not found\n", devicename);
      return SANE_STATUS_INVAL;
    }
  if (test_device->open)
    {
      DBG (1, "sane_open: device `%s' already open\n", devicename);
      return SANE_STATUS_DEVICE_BUSY;
    }
  DBG (2, "sane_open: opening device `%s', handle = %p\n", test_device->name,
       (void *) test_device);
  test_device->open = SANE_TRUE;
  *handle = test_device;

  if (!test_device->options_initialized) {
    status = init_options (test_device);
    if (status != SANE_STATUS_GOOD)
      return status;
    test_device->options_initialized = SANE_TRUE;
  }

  test_device->open = SANE_TRUE;
  test_device->scanning = SANE_FALSE;
  test_device->cancelled = SANE_FALSE;
  test_device->eof = SANE_FALSE;
  test_device->bytes_total = 0;
  test_device->pass = 0;
  test_device->number_of_scans = 0;

  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  Test_Device *test_device = handle;

  DBG (2, "sane_close: handle=%p\n", (void *) handle);
  if (!inited)
    {
      DBG (1, "sane_close: not inited, call sane_init() first\n");
      return;
    }

  if (!check_handle (handle))
    {
      DBG (1, "sane_close: handle %p unknown\n", (void *) handle);
      return;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_close: handle %p not open\n", (void *) handle);
      return;
    }
  test_device->open = SANE_FALSE;
  return;
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  Test_Device *test_device = handle;

  DBG (4, "sane_get_option_descriptor: handle=%p, option = %d\n",
       (void *) handle, option);
  if (!inited)
    {
      DBG (1, "sane_get_option_descriptor: not inited, call sane_init() "
	   "first\n");
      return 0;
    }

  if (!check_handle (handle))
    {
      DBG (1, "sane_get_option_descriptor: handle %p unknown\n",
	   (void *) handle);
      return 0;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_get_option_descriptor: not open\n");
      return 0;
    }
  if (option < 0 || option >= num_options)
    {
      DBG (3, "sane_get_option_descriptor: option < 0 || "
	   "option > num_options\n");
      return 0;
    }

  test_device->loaded[option] = 1;

  return &test_device->opt[option];
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option, SANE_Action action,
		     void *value, SANE_Int * info)
{
  Test_Device *test_device = handle;
  SANE_Int myinfo = 0;
  SANE_Status status;

  DBG (4, "sane_control_option: handle=%p, opt=%d, act=%d, val=%p, info=%p\n",
       (void *) handle, option, action, (void *) value, (void *) info);
  if (!inited)
    {
      DBG (1, "sane_control_option: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }

  if (!check_handle (handle))
    {
      DBG (1, "sane_control_option: handle %p unknown\n", (void *) handle);
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_control_option: not open\n");
      return SANE_STATUS_INVAL;
    }
  if (test_device->scanning)
    {
      DBG (1, "sane_control_option: is scanning\n");
      return SANE_STATUS_INVAL;
    }
  if (option < 0 || option >= num_options)
    {
      DBG (1, "sane_control_option: option < 0 || option > num_options\n");
      return SANE_STATUS_INVAL;
    }

  if (!test_device->loaded[option])
    {
      DBG (1, "sane_control_option: option not loaded\n");
      return SANE_STATUS_INVAL;
    }

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

  if (test_device->opt[option].type == SANE_TYPE_GROUP)
    {
      DBG (1, "sane_control_option: option is a group\n");
      return SANE_STATUS_INVAL;
    }

  switch (action)
    {
    case SANE_ACTION_SET_AUTO:
      if (!SANE_OPTION_IS_SETTABLE (test_device->opt[option].cap))
	{
	  DBG (1, "sane_control_option: option is not setable\n");
	  return SANE_STATUS_INVAL;
	}
      if (!(test_device->opt[option].cap & SANE_CAP_AUTOMATIC))
	{
	  DBG (1, "sane_control_option: option is not automatically "
	       "setable\n");
	  return SANE_STATUS_INVAL;
	}
      switch (option)
	{
	case opt_bool_soft_select_soft_detect_auto:
	  test_device->val[option].w = SANE_TRUE;
	  DBG (4, "sane_control_option: set option %d (%s) automatically "
	       "to %s\n", option, test_device->opt[option].name,
	       test_device->val[option].w == SANE_TRUE ? "true" : "false");
	  break;

	default:
	  DBG (1, "sane_control_option: trying to automatically set "
	       "unexpected option\n");
	  return SANE_STATUS_INVAL;
	}
      break;

    case SANE_ACTION_SET_VALUE:
      if (!SANE_OPTION_IS_SETTABLE (test_device->opt[option].cap))
	{
	  DBG (1, "sane_control_option: option is not setable\n");
	  return SANE_STATUS_INVAL;
	}
      status = sanei_constrain_value (&test_device->opt[option],
				      value, &myinfo);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (3, "sane_control_option: sanei_constrain_value returned %s\n",
	       sane_strstatus (status));
	  return status;
	}
      switch (option)
	{
	case opt_tl_x:		/* Fixed with parameter reloading */
	case opt_tl_y:
	case opt_br_x:
	case opt_br_y:
	case opt_resolution:
	  if (test_device->val[option].w == *(SANE_Fixed *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Fixed *) value;
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  DBG (4, "sane_control_option: set option %d (%s) to %.0f %s\n",
	       option, test_device->opt[option].name,
	       SANE_UNFIX (*(SANE_Fixed *) value),
	       test_device->opt[option].unit == SANE_UNIT_MM ? "mm" : "dpi");
	  break;
	case opt_fixed:	/* Fixed */
	case opt_fixed_constraint_range:
	  if (test_device->val[option].w == *(SANE_Fixed *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Fixed *) value;
	  DBG (4, "sane_control_option: set option %d (%s) to %.0f\n",
	       option, test_device->opt[option].name,
	       SANE_UNFIX (*(SANE_Fixed *) value));
	  break;
	case opt_read_limit_size:	/* Int */
	case opt_ppl_loss:
	case opt_read_delay_duration:
	case opt_int:
	case opt_int_constraint_range:
	  if (test_device->val[option].w == *(SANE_Int *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Int *) value;
	  DBG (4, "sane_control_option: set option %d (%s) to %d\n",
	       option, test_device->opt[option].name, *(SANE_Int *) value);
	  break;
	case opt_fuzzy_parameters:	/* Bool with parameter reloading */
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_invert_endianess:	/* Bool */
	case opt_non_blocking:
	case opt_select_fd:
	case opt_bool_soft_select_soft_detect:
	case opt_bool_soft_select_soft_detect_auto:
	case opt_bool_soft_select_soft_detect_emulated:
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_depth:	/* Word list with parameter and options reloading */
	  if (test_device->val[option].w == *(SANE_Int *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Int *) value;
	  if (test_device->val[option].w == 16)
	    test_device->opt[opt_invert_endianess].cap &= ~SANE_CAP_INACTIVE;
	  else
	    test_device->opt[opt_invert_endianess].cap |= SANE_CAP_INACTIVE;

	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  DBG (4, "sane_control_option: set option %d (%s) to %d\n",
	       option, test_device->opt[option].name, *(SANE_Int *) value);
	  break;
	case opt_three_pass_order:	/* String list with parameter reload */
	  if (strcmp (test_device->val[option].s, value) == 0)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  strcpy (test_device->val[option].s, (SANE_String) value);
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name, (SANE_String) value);
	  break;
	case opt_int_constraint_word_list:	/* Word list */
	case opt_fixed_constraint_word_list:
	  if (test_device->val[option].w == *(SANE_Int *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Int *) value;
	  DBG (4, "sane_control_option: set option %d (%s) to %d\n",
	       option, test_device->opt[option].name, *(SANE_Int *) value);
	  break;
	case opt_read_status_code:	/* String (list) */
	case opt_test_picture:
	case opt_string:
	case opt_string_constraint_string_list:
	case opt_string_constraint_long_string_list:
	case opt_scan_source:
	  if (strcmp (test_device->val[option].s, value) == 0)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  strcpy (test_device->val[option].s, (SANE_String) value);
	  DBG (4, "sane_control_option: set option %d (%s) to `%s'\n",
	       option, test_device->opt[option].name, (SANE_String) value);
	  break;
	case opt_int_array:	/* Word array */
	case opt_int_array_constraint_range:
	case opt_gamma_red:
	case opt_gamma_green:
	case opt_gamma_blue:
	case opt_gamma_all:
	case opt_int_array_constraint_word_list:
	  memcpy (test_device->val[option].wa, value,
		  test_device->opt[option].size);
	  DBG (4, "sane_control_option: set option %d (%s) to %p\n",
	       option, test_device->opt[option].name, (void *) value);
	  if (option == opt_gamma_all) {
	      print_gamma_table(gamma_all, GAMMA_ALL_SIZE);
	  }
	  if (option == opt_gamma_red) {
	      print_gamma_table(gamma_red, GAMMA_RED_SIZE);
	  }
	  break;
	  /* options with side-effects */
	case opt_print_options:
	  DBG (4, "sane_control_option: set option %d (%s)\n",
	       option, test_device->opt[option].name);
	  print_options (test_device);
	  break;
	case opt_button:
	  DBG (0, "Yes! You pressed me!\n");
	  DBG (4, "sane_control_option: set option %d (%s)\n",
	       option, test_device->opt[option].name);
	  break;
	case opt_mode:
	  if (strcmp (test_device->val[option].s, value) == 0)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  strcpy (test_device->val[option].s, (SANE_String) value);
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  if (strcmp (test_device->val[option].s, SANE_VALUE_SCAN_MODE_COLOR) == 0)
	    {
	      test_device->opt[opt_three_pass].cap &= ~SANE_CAP_INACTIVE;
	      if (test_device->val[opt_three_pass].w == SANE_TRUE)
		test_device->opt[opt_three_pass_order].cap
		  &= ~SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      test_device->opt[opt_three_pass].cap |= SANE_CAP_INACTIVE;
	      test_device->opt[opt_three_pass_order].cap |= SANE_CAP_INACTIVE;
	    }
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name, (SANE_String) value);
	  break;
	case opt_three_pass:
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  if (test_device->val[option].w == SANE_TRUE)
	    test_device->opt[opt_three_pass_order].cap &= ~SANE_CAP_INACTIVE;
	  else
	    test_device->opt[opt_three_pass_order].cap |= SANE_CAP_INACTIVE;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_hand_scanner:
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  myinfo |= SANE_INFO_RELOAD_PARAMS;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  if (test_device->val[option].w == SANE_TRUE)
	    {
	      test_device->opt[opt_tl_x].cap |= SANE_CAP_INACTIVE;
	      test_device->opt[opt_tl_y].cap |= SANE_CAP_INACTIVE;
	      test_device->opt[opt_br_x].cap |= SANE_CAP_INACTIVE;
	      test_device->opt[opt_br_y].cap |= SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      test_device->opt[opt_tl_x].cap &= ~SANE_CAP_INACTIVE;
	      test_device->opt[opt_tl_y].cap &= ~SANE_CAP_INACTIVE;
	      test_device->opt[opt_br_x].cap &= ~SANE_CAP_INACTIVE;
	      test_device->opt[opt_br_y].cap &= ~SANE_CAP_INACTIVE;
	    }
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_read_limit:
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  if (test_device->val[option].w == SANE_TRUE)
	    test_device->opt[opt_read_limit_size].cap &= ~SANE_CAP_INACTIVE;
	  else
	    test_device->opt[opt_read_limit_size].cap |= SANE_CAP_INACTIVE;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_read_delay:
	  if (test_device->val[option].w == *(SANE_Bool *) value)
	    {
	      DBG (4, "sane_control_option: option %d (%s) not changed\n",
		   option, test_device->opt[option].name);
	      break;
	    }
	  test_device->val[option].w = *(SANE_Bool *) value;
	  myinfo |= SANE_INFO_RELOAD_OPTIONS;
	  if (test_device->val[option].w == SANE_TRUE)
	    test_device->opt[opt_read_delay_duration].cap
	      &= ~SANE_CAP_INACTIVE;
	  else
	    test_device->opt[opt_read_delay_duration].cap |=
	      SANE_CAP_INACTIVE;
	  DBG (4, "sane_control_option: set option %d (%s) to %s\n", option,
	       test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_enable_test_options:
	  {
	    int option_number;
	    if (test_device->val[option].w == *(SANE_Bool *) value)
	      {
		DBG (4, "sane_control_option: option %d (%s) not changed\n",
		     option, test_device->opt[option].name);
		break;
	      }
	    test_device->val[option].w = *(SANE_Bool *) value;
	    myinfo |= SANE_INFO_RELOAD_OPTIONS;
	    for (option_number = opt_bool_soft_select_soft_detect;
		 option_number < num_options; option_number++)
	      {
		if (test_device->opt[option_number].type == SANE_TYPE_GROUP)
		  continue;
		if (test_device->val[option].w == SANE_TRUE)
		  test_device->opt[option_number].cap &= ~SANE_CAP_INACTIVE;
		else
		  test_device->opt[option_number].cap |= SANE_CAP_INACTIVE;
	      }
	    DBG (4, "sane_control_option: set option %d (%s) to %s\n",
		 option, test_device->opt[option].name,
		 *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	    break;
	  }
	default:
	  DBG (1, "sane_control_option: trying to set unexpected option\n");
	  return SANE_STATUS_INVAL;
	}
      break;

    case SANE_ACTION_GET_VALUE:
      switch (option)
	{
	case opt_num_opts:
	  *(SANE_Word *) value = num_options;
	  DBG (4, "sane_control_option: get option 0, value = %d\n",
	       num_options);
	  break;
	case opt_tl_x:		/* Fixed options */
	case opt_tl_y:
	case opt_br_x:
	case opt_br_y:
	case opt_resolution:
	case opt_fixed:
	case opt_fixed_constraint_range:
	case opt_fixed_constraint_word_list:
	  {
	    *(SANE_Fixed *) value = test_device->val[option].w;
	    DBG (4,
		 "sane_control_option: get option %d (%s), value=%.1f %s\n",
		 option, test_device->opt[option].name,
		 SANE_UNFIX (*(SANE_Fixed *) value),
		 test_device->opt[option].unit ==
		 SANE_UNIT_MM ? "mm" : SANE_UNIT_DPI ? "dpi" : "");
	    break;
	  }
	case opt_hand_scanner:	/* Bool options */
	case opt_three_pass:
	case opt_invert_endianess:
	case opt_read_limit:
	case opt_read_delay:
	case opt_fuzzy_parameters:
	case opt_non_blocking:
	case opt_select_fd:
	case opt_bool_soft_select_soft_detect:
	case opt_bool_hard_select_soft_detect:
	case opt_bool_soft_detect:
	case opt_enable_test_options:
	case opt_bool_soft_select_soft_detect_emulated:
	case opt_bool_soft_select_soft_detect_auto:
	  *(SANE_Bool *) value = test_device->val[option].w;
	  DBG (4,
	       "sane_control_option: get option %d (%s), value=%s\n",
	       option, test_device->opt[option].name,
	       *(SANE_Bool *) value == SANE_TRUE ? "true" : "false");
	  break;
	case opt_mode:		/* String (list) options */
	case opt_three_pass_order:
	case opt_read_status_code:
	case opt_test_picture:
	case opt_string:
	case opt_string_constraint_string_list:
	case opt_string_constraint_long_string_list:
	case opt_scan_source:
	  strcpy (value, test_device->val[option].s);
	  DBG (4, "sane_control_option: get option %d (%s), value=`%s'\n",
	       option, test_device->opt[option].name, (SANE_String) value);
	  break;
	case opt_depth:	/* Int + word list options */
	case opt_read_limit_size:
	case opt_ppl_loss:
	case opt_read_delay_duration:
	case opt_int:
	case opt_int_constraint_range:
	case opt_int_constraint_word_list:
	  *(SANE_Int *) value = test_device->val[option].w;
	  DBG (4, "sane_control_option: get option %d (%s), value=%d\n",
	       option, test_device->opt[option].name, *(SANE_Int *) value);
	  break;
	case opt_int_array:	/* Int array */
	case opt_int_array_constraint_range:
	case opt_gamma_red:
	case opt_gamma_green:
	case opt_gamma_blue:
	case opt_gamma_all:
	case opt_int_array_constraint_word_list:
	  memcpy (value, test_device->val[option].wa,
		  test_device->opt[option].size);
	  DBG (4, "sane_control_option: get option %d (%s), value=%p\n",
	       option, test_device->opt[option].name, (void *) value);
	  break;
	default:
	  DBG (1, "sane_control_option: trying to get unexpected option\n");
	  return SANE_STATUS_INVAL;
	}
      break;
    default:
      DBG (1, "sane_control_option: trying unexpected action %d\n", action);
      return SANE_STATUS_INVAL;
    }

  if (info)
    *info = myinfo;

  if(myinfo & SANE_INFO_RELOAD_OPTIONS){
    SANE_Int i = 0;
    for(i=1;i<num_options;i++){
      test_device->loaded[i] = 0;
    }
  }

  DBG (4, "sane_control_option: finished, info=%s %s %s \n",
       myinfo & SANE_INFO_INEXACT ? "inexact" : "",
       myinfo & SANE_INFO_RELOAD_PARAMS ? "reload_parameters" : "",
       myinfo & SANE_INFO_RELOAD_OPTIONS ? "reload_options" : "");

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  Test_Device *test_device = handle;
  SANE_Parameters *p;
  double res, tl_x = 0, tl_y = 0, br_x = 0, br_y = 0;
  SANE_String text_format, mode;
  SANE_Int channels = 1;

  DBG (2, "sane_get_parameters: handle=%p, params=%p\n",
       (void *) handle, (void *) params);
  if (!inited)
    {
      DBG (1, "sane_get_parameters: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_get_parameters: handle %p unknown\n", (void *) handle);
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_get_parameters: handle %p not open\n", (void *) handle);
      return SANE_STATUS_INVAL;
    }

  res = SANE_UNFIX (test_device->val[opt_resolution].w);
  mode = test_device->val[opt_mode].s;
  p = &test_device->params;
  p->depth = test_device->val[opt_depth].w;

  if (test_device->val[opt_hand_scanner].w == SANE_TRUE)
    {
      tl_x = 0.0;
      br_x = 110.0;
      tl_y = 0.0;
      br_y = 170.0;
      p->lines = -1;
      test_device->lines = (SANE_Word) (res * (br_y - tl_y) / MM_PER_INCH);
    }
  else
    {
      tl_x = SANE_UNFIX (test_device->val[opt_tl_x].w);
      tl_y = SANE_UNFIX (test_device->val[opt_tl_y].w);
      br_x = SANE_UNFIX (test_device->val[opt_br_x].w);
      br_y = SANE_UNFIX (test_device->val[opt_br_y].w);
      if (tl_x > br_x)
	swap_double (&tl_x, &br_x);
      if (tl_y > br_y)
	swap_double (&tl_y, &br_y);
      test_device->lines = (SANE_Word) (res * (br_y - tl_y) / MM_PER_INCH);
      if (test_device->lines < 1)
	test_device->lines = 1;
      p->lines = test_device->lines;
      if (test_device->val[opt_fuzzy_parameters].w == SANE_TRUE
	  && test_device->scanning == SANE_FALSE)
	p->lines *= random_factor;
    }

  if (strcmp (mode, SANE_VALUE_SCAN_MODE_GRAY) == 0)
    {
      p->format = SANE_FRAME_GRAY;
      p->last_frame = SANE_TRUE;
    }
  else				/* Color */
    {
      if (test_device->val[opt_three_pass].w == SANE_TRUE)
	{
	  if (test_device->val[opt_three_pass_order].s[test_device->pass]
	      == 'R')
	    p->format = SANE_FRAME_RED;
	  else if (test_device->val[opt_three_pass_order].s[test_device->pass]
		   == 'G')
	    p->format = SANE_FRAME_GREEN;
	  else
	    p->format = SANE_FRAME_BLUE;
	  if (test_device->pass > 1)
	    p->last_frame = SANE_TRUE;
	  else
	    p->last_frame = SANE_FALSE;
	}
      else
	{
	  p->format = SANE_FRAME_RGB;
	  p->last_frame = SANE_TRUE;
	}
    }

  p->pixels_per_line = (SANE_Int) (res * (br_x - tl_x) / MM_PER_INCH);
  if (test_device->val[opt_fuzzy_parameters].w == SANE_TRUE
      && test_device->scanning == SANE_FALSE)
    p->pixels_per_line *= random_factor;
  if (p->pixels_per_line < 1)
    p->pixels_per_line = 1;

  if (p->format == SANE_FRAME_RGB)
    channels = 3;

  if (p->depth == 1)
    p->bytes_per_line = channels * (int) ((p->pixels_per_line + 7) / 8);
  else				/* depth == 8 || depth == 16 */
    p->bytes_per_line = channels * p->pixels_per_line * ((p->depth + 7) / 8);

  test_device->bytes_per_line = p->bytes_per_line;

  p->pixels_per_line -= test_device->val[opt_ppl_loss].w;
  if (p->pixels_per_line < 1)
    p->pixels_per_line = 1;
  test_device->pixels_per_line = p->pixels_per_line;

  switch (p->format)
    {
    case SANE_FRAME_GRAY:
      text_format = "gray";
      break;
    case SANE_FRAME_RGB:
      text_format = "rgb";
      break;
    case SANE_FRAME_RED:
      text_format = "red";
      break;
    case SANE_FRAME_GREEN:
      text_format = "green";
      break;
    case SANE_FRAME_BLUE:
      text_format = "blue";
      break;
    default:
      text_format = "unknown";
      break;
    }

  DBG (3, "sane_get_parameters: format=%s\n", text_format);
  DBG (3, "sane_get_parameters: last_frame=%s\n",
       p->last_frame ? "true" : "false");
  DBG (3, "sane_get_parameters: lines=%d\n", p->lines);
  DBG (3, "sane_get_parameters: depth=%d\n", p->depth);
  DBG (3, "sane_get_parameters: pixels_per_line=%d\n", p->pixels_per_line);
  DBG (3, "sane_get_parameters: bytes_per_line=%d\n", p->bytes_per_line);

  if (params)
    *params = *p;

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle handle)
{
  Test_Device *test_device = handle;
  int pipe_descriptor[2];

  DBG (2, "sane_start: handle=%p\n", handle);
  if (!inited)
    {
      DBG (1, "sane_start: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_start: handle %p unknown\n", handle);
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_start: not open\n");
      return SANE_STATUS_INVAL;
    }
  if (test_device->scanning
      && (test_device->val[opt_three_pass].w == SANE_FALSE
	  && strcmp (test_device->val[opt_mode].s, SANE_VALUE_SCAN_MODE_COLOR) == 0))
    {
      DBG (1, "sane_start: already scanning\n");
      return SANE_STATUS_INVAL;
    }
  if (strcmp (test_device->val[opt_mode].s, SANE_VALUE_SCAN_MODE_COLOR) == 0
      && test_device->val[opt_three_pass].w == SANE_TRUE
      && test_device->pass > 2)
    {
      DBG (1, "sane_start: already in last pass of three\n");
      return SANE_STATUS_INVAL;
    }

  if (test_device->pass == 0)
    {
      test_device->number_of_scans++;
      DBG (3, "sane_start: scanning page %d\n", test_device->number_of_scans);

      if ((strcmp (test_device->val[opt_scan_source].s, "Automatic Document Feeder") == 0) &&
	  (((test_device->number_of_scans) % 11) == 0))
	{
	  DBG (1, "sane_start: Document feeder is out of documents!\n");
	  return SANE_STATUS_NO_DOCS;
	}
    }

  test_device->scanning = SANE_TRUE;
  test_device->cancelled = SANE_FALSE;
  test_device->eof = SANE_FALSE;
  test_device->bytes_total = 0;

  sane_get_parameters (handle, 0);

  if (test_device->params.lines == 0)
    {
      DBG (1, "sane_start: lines == 0\n");
      test_device->scanning = SANE_FALSE;
      return SANE_STATUS_INVAL;
    }
  if (test_device->params.pixels_per_line == 0)
    {
      DBG (1, "sane_start: pixels_per_line == 0\n");
      test_device->scanning = SANE_FALSE;
      return SANE_STATUS_INVAL;
    }
  if (test_device->params.bytes_per_line == 0)
    {
      DBG (1, "sane_start: bytes_per_line == 0\n");
      test_device->scanning = SANE_FALSE;
      return SANE_STATUS_INVAL;
    }

  if (pipe (pipe_descriptor) < 0)
    {
      DBG (1, "sane_start: pipe failed (%s)\n", strerror (errno));
      return SANE_STATUS_IO_ERROR;
    }

  /* create reader routine as new process or thread */
  test_device->pipe = pipe_descriptor[0];
  test_device->reader_fds = pipe_descriptor[1];
  test_device->reader_pid =
    sanei_thread_begin (reader_task, (void *) test_device);

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

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

  return SANE_STATUS_GOOD;
}


SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * data,
	   SANE_Int max_length, SANE_Int * length)
{
  Test_Device *test_device = handle;
  SANE_Int max_scan_length;
  ssize_t bytes_read;
  size_t read_count;
  SANE_Int bytes_total = test_device->lines * test_device->bytes_per_line;


  DBG (4, "sane_read: handle=%p, data=%p, max_length = %d, length=%p\n",
       handle, data, max_length, (void *) length);
  if (!inited)
    {
      DBG (1, "sane_read: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_read: handle %p unknown\n", handle);
      return SANE_STATUS_INVAL;
    }
  if (!length)
    {
      DBG (1, "sane_read: length == NULL\n");
      return SANE_STATUS_INVAL;
    }

  if (strcmp (test_device->val[opt_read_status_code].s, "Default") != 0)
    {
      SANE_String_Const sc = test_device->val[opt_read_status_code].s;
      DBG (3, "sane_read: setting return status to %s\n", sc);
      if (strcmp (sc, "SANE_STATUS_UNSUPPORTED") == 0)
	return SANE_STATUS_UNSUPPORTED;
      if (strcmp (sc, "SANE_STATUS_CANCELLED") == 0)
	return SANE_STATUS_CANCELLED;
      if (strcmp (sc, "SANE_STATUS_DEVICE_BUSY") == 0)
	return SANE_STATUS_DEVICE_BUSY;
      if (strcmp (sc, "SANE_STATUS_INVAL") == 0)
	return SANE_STATUS_INVAL;
      if (strcmp (sc, "SANE_STATUS_EOF") == 0)
	return SANE_STATUS_EOF;
      if (strcmp (sc, "SANE_STATUS_JAMMED") == 0)
	return SANE_STATUS_JAMMED;
      if (strcmp (sc, "SANE_STATUS_NO_DOCS") == 0)
	return SANE_STATUS_NO_DOCS;
      if (strcmp (sc, "SANE_STATUS_COVER_OPEN") == 0)
	return SANE_STATUS_COVER_OPEN;
      if (strcmp (sc, "SANE_STATUS_IO_ERROR") == 0)
	return SANE_STATUS_IO_ERROR;
      if (strcmp (sc, "SANE_STATUS_NO_MEM") == 0)
	return SANE_STATUS_NO_MEM;
      if (strcmp (sc, "SANE_STATUS_ACCESS_DENIED") == 0)
	return SANE_STATUS_ACCESS_DENIED;
    }

  max_scan_length = max_length;
  if (test_device->val[opt_read_limit].w == SANE_TRUE
      && test_device->val[opt_read_limit_size].w < max_scan_length)
    {
      max_scan_length = test_device->val[opt_read_limit_size].w;
      DBG (3, "sane_read: limiting max_scan_length to %d bytes\n",
	   max_scan_length);
    }

  *length = 0;

  if (!data)
    {
      DBG (1, "sane_read: data == NULL\n");
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_read: not open\n");
      return SANE_STATUS_INVAL;
    }
  if (test_device->cancelled)
    {
      DBG (1, "sane_read: scan was cancelled\n");
      return SANE_STATUS_CANCELLED;
    }
  if (test_device->eof)
    {
      DBG (2, "sane_read: No more data available, sending EOF\n");
      return SANE_STATUS_EOF;
    }
  if (!test_device->scanning)
    {
      DBG (1, "sane_read: not scanning (call sane_start first)\n");
      return SANE_STATUS_INVAL;
    }
  read_count = max_scan_length;

  bytes_read = read (test_device->pipe, data, read_count);
  if (bytes_read == 0
      || (bytes_read + test_device->bytes_total >= bytes_total))
    {
      SANE_Status status;
      DBG (2, "sane_read: EOF reached\n");
      status = finish_pass (test_device);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG (1, "sane_read: finish_pass returned `%s'\n",
	       sane_strstatus (status));
	  return status;
	}
      test_device->eof = SANE_TRUE;
      if (strcmp (test_device->val[opt_mode].s, SANE_VALUE_SCAN_MODE_COLOR) == 0
	  && test_device->val[opt_three_pass].w == SANE_TRUE)
	{
	  test_device->pass++;
	  if (test_device->pass > 2)
	    test_device->pass = 0;
	}
      if (bytes_read == 0)
	return SANE_STATUS_EOF;
    }
  else if (bytes_read < 0)
    {
      if (errno == EAGAIN)
	{
	  DBG (2, "sane_read: no data available, try again\n");
	  return SANE_STATUS_GOOD;
	}
      else
	{
	  DBG (1, "sane_read: read returned error: %s\n", strerror (errno));
	  return SANE_STATUS_IO_ERROR;
	}
    }
  *length = bytes_read;
  test_device->bytes_total += bytes_read;

  DBG (2, "sane_read: read %ld bytes of %d, total %d\n", (long) bytes_read,
       max_scan_length, test_device->bytes_total);
  return SANE_STATUS_GOOD;
}

void
sane_cancel (SANE_Handle handle)
{
  Test_Device *test_device = handle;

  DBG (2, "sane_cancel: handle = %p\n", handle);
  if (!inited)
    {
      DBG (1, "sane_cancel: not inited, call sane_init() first\n");
      return;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_cancel: handle %p unknown\n", handle);
      return;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_cancel: not open\n");
      return;
    }
  if (test_device->cancelled)
    {
      DBG (1, "sane_cancel: scan already cancelled\n");
      return;
    }
  if (!test_device->scanning)
    {
      DBG (2, "sane_cancel: scan is already finished\n");
      return;
    }
  finish_pass (test_device);
  test_device->cancelled = SANE_TRUE;
  test_device->scanning = SANE_FALSE;
  test_device->eof = SANE_FALSE;
  test_device->pass = 0;
  return;
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  Test_Device *test_device = handle;

  DBG (2, "sane_set_io_mode: handle = %p, non_blocking = %d\n", handle,
       non_blocking);
  if (!inited)
    {
      DBG (1, "sane_set_io_mode: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_set_io_mode: handle %p unknown\n", handle);
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_set_io_mode: not open\n");
      return SANE_STATUS_INVAL;
    }
  if (!test_device->scanning)
    {
      DBG (1, "sane_set_io_mode: not scanning\n");
      return SANE_STATUS_INVAL;
    }
  if (test_device->val[opt_non_blocking].w == SANE_TRUE)
    {
      if (fcntl (test_device->pipe,
		 F_SETFL, non_blocking ? O_NONBLOCK : 0) < 0)
	{
	  DBG (1, "sane_set_io_mode: can't set io mode");
	  return SANE_STATUS_INVAL;
	}
    }
  else
    {
      if (non_blocking)
	return SANE_STATUS_UNSUPPORTED;
    }
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  Test_Device *test_device = handle;

  DBG (2, "sane_get_select_fd: handle = %p, fd %s 0\n", handle,
       fd ? "!=" : "=");
  if (!inited)
    {
      DBG (1, "sane_get_select_fd: not inited, call sane_init() first\n");
      return SANE_STATUS_INVAL;
    }
  if (!check_handle (handle))
    {
      DBG (1, "sane_get_select_fd: handle %p unknown\n", handle);
      return SANE_STATUS_INVAL;
    }
  if (!test_device->open)
    {
      DBG (1, "sane_get_select_fd: not open\n");
      return SANE_STATUS_INVAL;
    }
  if (!test_device->scanning)
    {
      DBG (1, "sane_get_select_fd: not scanning\n");
      return SANE_STATUS_INVAL;
    }
  if (test_device->val[opt_select_fd].w == SANE_TRUE)
    {
      *fd = test_device->pipe;
      return SANE_STATUS_GOOD;
    }
  return SANE_STATUS_UNSUPPORTED;
}
