/* SANE - Scanner Access Now Easy.

   Copyright (C) 2011-2020 Rolf Bensch <rolf at bensch hyphen online dot de>
   Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
   Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.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.
 */
#include "../include/sane/config.h"

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#ifdef USE_PTHREAD
# include <pthread.h>
#endif
#include <signal.h>		/* sigaction(POSIX) */
#include <unistd.h>		/* POSIX: write read close pipe */
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

#include "pixma_rename.h"
#include "pixma.h"

# define DEBUG_NOT_STATIC
# include "../include/sane/sane.h"
# include "../include/sane/sanei.h"
# include "../include/sane/saneopts.h"
# include "../include/sane/sanei_thread.h"
# include "../include/sane/sanei_backend.h"
# include "../include/sane/sanei_config.h"
# include "../include/sane/sanei_jpeg.h"
# include "../include/sane/sanei_usb.h"

#ifdef NDEBUG
# define PDBG(x)
#else
#  define PDBG(x) IF_DBG(x)
#endif /* NDEBUG */

#ifdef __GNUC__
# define UNUSED(v) (void) v
#else
# define UNUSED(v)
#endif

#define DECL_CTX pixma_sane_t *ss = check_handle(h)
#define OPT_IN_CTX ss->opt
#define SOD(opt)  OPT_IN_CTX[opt].sod
#define OVAL(opt) OPT_IN_CTX[opt].val
#define AUTO_GAMMA 2.2

/* pixma_sane_options.h generated by
 * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h
 */
#include "pixma_sane_options.h"

#define BUTTON_GROUP_SIZE ( opt_adf_orientation - opt_button_1 + 1 )
#define BUTTON_GROUP_INDEX(x) ( x - opt_button_1 )

typedef struct pixma_sane_t
{
  struct pixma_sane_t *next;
  pixma_t *s;
  pixma_scan_param_t sp;
  SANE_Bool cancel;

  /* valid states: idle, !idle && scanning, !idle && !scanning */
  SANE_Bool idle;
  SANE_Bool scanning;
  SANE_Status last_read_status;	/* valid if !idle && !scanning */

  option_descriptor_t opt[opt_last];
  char button_option_is_cached[BUTTON_GROUP_SIZE];
  SANE_Range xrange, yrange;
  SANE_Word dpi_list[9];	/* up to 9600 dpi */
  SANE_String_Const mode_list[6];
  pixma_scan_mode_t mode_map[6];
  uint8_t gamma_table[4096];
  SANE_String_Const source_list[4];
  pixma_paper_source_t source_map[4];
  SANE_String_Const calibrate_list[PIXMA_CALIBRATE_NUM_OPTS + 1];
  pixma_calibrate_option_t calibrate_map[PIXMA_CALIBRATE_NUM_OPTS + 1];

  unsigned byte_pos_in_line, output_line_size;
  uint64_t image_bytes_read;
  unsigned page_count;		/* valid for ADF */

  SANE_Pid reader_taskid;
  int wpipe, rpipe;
  SANE_Bool reader_stop;

  /* Valid for JPEG source */
  djpeg_dest_ptr jdst;
  struct jpeg_decompress_struct jpeg_cinfo;
  struct jpeg_error_mgr jpeg_err;
  SANE_Bool jpeg_header_seen;
} pixma_sane_t;

typedef struct
{
  struct jpeg_source_mgr jpeg;

  pixma_sane_t *s;
  JOCTET *buffer;

  SANE_Byte *linebuffer;
  SANE_Int linebuffer_size;
  SANE_Int linebuffer_index;
} pixma_jpeg_src_mgr;


static const char vendor_str[] = "CANON";
static const char type_str[] = "multi-function peripheral";

static pixma_sane_t *first_scanner = NULL;
static const SANE_Device **dev_list = NULL;
static const char* conf_devices[MAX_CONF_DEVICES];

static void mark_all_button_options_cached ( struct pixma_sane_t * ss )
{
  int i;
  for (i = 0; i < (opt__group_5 - opt_button_1); i++ )
      ss -> button_option_is_cached[i] = 1;
}

static SANE_Status config_attach_pixma(SANEI_Config __sane_unused__ * config,
				       const char *devname,
				       void __sane_unused__ *data)
{
  int i;
  for (i=0; i < (MAX_CONF_DEVICES -1); i++)
    {
      if(conf_devices[i] == NULL)
        {
          conf_devices[i] = strdup(devname);
          return SANE_STATUS_GOOD;
        }
    }
  return SANE_STATUS_INVAL;
}

static SANE_Status
map_error (int error)
{
  if (error >= 0)
    return SANE_STATUS_GOOD;

  switch (error)
    {
    case PIXMA_ENOMEM:
      return SANE_STATUS_NO_MEM;
    case PIXMA_ECANCELED:
      return SANE_STATUS_CANCELLED;
    case PIXMA_EBUSY:
      return SANE_STATUS_DEVICE_BUSY;
    case PIXMA_EINVAL:
      return SANE_STATUS_INVAL;
    case PIXMA_EACCES:
      return SANE_STATUS_ACCESS_DENIED;
    case PIXMA_EPAPER_JAMMED:
      return SANE_STATUS_JAMMED;
    case PIXMA_ENO_PAPER:
      return SANE_STATUS_NO_DOCS;
    case PIXMA_ECOVER_OPEN:
      return SANE_STATUS_COVER_OPEN;
    case PIXMA_ENOTSUP:
      return SANE_STATUS_UNSUPPORTED;
    case PIXMA_EPROTO:
    case PIXMA_ENODEV:
    case PIXMA_EIO:
    case PIXMA_ETIMEDOUT:
      return SANE_STATUS_IO_ERROR;
    }
  PDBG (pixma_dbg (1, "BUG: unmapped error %d\n", error));
  return SANE_STATUS_IO_ERROR;
}

static int
getenv_atoi (const char *name, int def)
{
  const char *str = getenv (name);
  return (str) ? atoi (str) : def;
}

#define CONST_CAST(t,x) (t)(x)

static void
free_block (const void * ptr)
{
  free (CONST_CAST (void *, ptr));
}

static void
cleanup_device_list (void)
{
  if (dev_list)
    {
      int i;
      for (i = 0; dev_list[i]; i++)
        {
          free_block ((const void *) dev_list[i]->name);
          free_block ((const void *) dev_list[i]->model);
          free_block ((const void *) dev_list[i]);
        }
    }
  free (dev_list);
  dev_list = NULL;
}

static void
find_scanners (SANE_Bool local_only)
{
  unsigned i, nscanners;

  cleanup_device_list ();
  nscanners = pixma_find_scanners (conf_devices, local_only);
  PDBG (pixma_dbg (3, "pixma_find_scanners() found %u devices\n", nscanners));
  dev_list =
    (const SANE_Device **) calloc (nscanners + 1, sizeof (*dev_list));
  if (!dev_list)
    return;
  for (i = 0; i != nscanners; i++)
    {
      SANE_Device *sdev = (SANE_Device *) calloc (1, sizeof (*sdev));
      char *name, *model;
      if (!sdev)
        goto nomem;
      name = strdup (pixma_get_device_id (i));
      model = strdup (pixma_get_device_model (i));
      if (!name || !model)
        {
          free (name);
          free (model);
          free (sdev);
          goto nomem;
        }
      sdev->name = name;
      sdev->model = model;
      sdev->vendor = vendor_str;
      sdev->type = type_str;
      dev_list[i] = sdev;
    }
  /* dev_list is already NULL terminated by calloc(). */
  return;

nomem:
  PDBG (pixma_dbg (1, "WARNING:not enough memory for device list\n"));
  return;
}

static pixma_sane_t *
check_handle (SANE_Handle h)
{
  pixma_sane_t *p;

  for (p = first_scanner; p && (SANE_Handle) p != h; p = p->next)
    {
    }
  return p;
}

static void
update_button_state (pixma_sane_t * ss, SANE_Int * info)
{
  SANE_Int b1 = OVAL (opt_button_1).w;
  SANE_Int b2 = OVAL (opt_button_2).w;
  uint32_t ev = pixma_wait_event (ss->s, 300);
  switch (ev & ~PIXMA_EV_ACTION_MASK)
    {
    case PIXMA_EV_BUTTON1:
      b1 = 1;
      break;
    case PIXMA_EV_BUTTON2:
      b2 = 1;
      break;
    }

  if (b1 != OVAL (opt_button_1).w || b2 != OVAL (opt_button_2).w)
    {
    *info |= SANE_INFO_RELOAD_OPTIONS;
    OVAL (opt_button_1).w = b1;
    OVAL (opt_button_2).w = b2;
    OVAL (opt_original).w = GET_EV_ORIGINAL(ev);
    OVAL (opt_target).w = GET_EV_TARGET(ev);
    OVAL (opt_scan_resolution).w = GET_EV_DPI(ev);
    OVAL (opt_document_type).w = GET_EV_DOC(ev);
    OVAL (opt_adf_status).w = GET_EV_STAT(ev);
    OVAL (opt_adf_orientation).w = GET_EV_ORIENT(ev);
    }
  mark_all_button_options_cached(ss);
}

static SANE_Bool
enable_option (pixma_sane_t * ss, SANE_Int o, SANE_Bool enable)
{
  SANE_Word save = SOD (o).cap;
  if (enable)
    SOD (o).cap &= ~SANE_CAP_INACTIVE;
  else
    SOD (o).cap |= SANE_CAP_INACTIVE;
  return (save != SOD (o).cap);
}

static void
clamp_value (pixma_sane_t * ss, SANE_Int n, void *v, SANE_Int * info)
{
  SANE_Option_Descriptor *sod = &SOD (n);
  SANE_Word *va = (SANE_Word *) v;
  const SANE_Range *range = sod->constraint.range;
  int i, nmemb;

  nmemb = sod->size / sizeof (SANE_Word);
  for (i = 0; i < nmemb; i++)
    {
      SANE_Word value = va[i];
      if (value < range->min)
        {
          value = range->min;
        }
      else if (value > range->max)
        {
          value = range->max;
        }
      if (range->quant != 0)
        {
          value = (value - range->min + range->quant / 2) /
            range->quant * range->quant;
        }
      if (value != va[i])
        {
          va[i] = value;
          *info |= SANE_INFO_INEXACT;
        }
    }
}

/* create dynamic mode_list
 * ss:      scanner device
 * tpu = 0: flatbed or ADF mode
 *          1 bit lineart, 8 bit grayscale and 24 bit color scans
 * tpu = 1: TPU mode
 *          16 bit grayscale and 48 bit color scans */
static void
create_mode_list (pixma_sane_t * ss)
{
  SANE_Bool tpu;
  const pixma_config_t *cfg;
  int i;

  cfg = pixma_get_config (ss->s);
  tpu = (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU);

  /* setup available mode */
  i = 0;
  ss->mode_list[i] = SANE_VALUE_SCAN_MODE_COLOR;
  ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR;
  i++;
  if (cfg->cap & PIXMA_CAP_GRAY)
    {
      ss->mode_list[i] = SANE_VALUE_SCAN_MODE_GRAY;
      ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY;
      i++;
    }
  if (tpu && (cfg->cap & PIXMA_CAP_NEGATIVE))
    {
      ss->mode_list[i] = SANE_I18N ("Negative color");
      ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_COLOR;
      i++;
      if (cfg->cap & PIXMA_CAP_GRAY)
        {
          ss->mode_list[i] = SANE_I18N ("Negative gray");
          ss->mode_map[i] = PIXMA_SCAN_MODE_NEGATIVE_GRAY;
          i++;
        }
    }
  if (tpu && (cfg->cap & PIXMA_CAP_TPUIR) == PIXMA_CAP_TPUIR)
    {
      ss->mode_list[i] = SANE_I18N ("Infrared");
      ss->mode_map[i] = PIXMA_SCAN_MODE_TPUIR;
      i++;
    }
  if (!tpu && (cfg->cap & PIXMA_CAP_48BIT))
    {
      ss->mode_list[i] = SANE_I18N ("48 bits color");
      ss->mode_map[i] = PIXMA_SCAN_MODE_COLOR_48;
      i++;
      if (cfg->cap & PIXMA_CAP_GRAY)
        {
          ss->mode_list[i] = SANE_I18N ("16 bits gray");
          ss->mode_map[i] = PIXMA_SCAN_MODE_GRAY_16;
          i++;
        }
    }
  if (!tpu && (cfg->cap & PIXMA_CAP_LINEART))
    {
      ss->mode_list[i] = SANE_VALUE_SCAN_MODE_LINEART;
      ss->mode_map[i] = PIXMA_SCAN_MODE_LINEART;
      i++;
    }
  /* terminate mode_list and mode_map */
  ss->mode_list[i] = 0;
  ss->mode_map[i] = 0;
}

/* create dynamic dpi_list
 * ss: scanner device */
static void
create_dpi_list (pixma_sane_t * ss)
{
  const pixma_config_t *cfg;
  int i, j;
  int min;
  unsigned min_dpi;
  unsigned max_dpi;

  cfg = pixma_get_config (ss->s);

  /* get min/max dpi */
  max_dpi = cfg->xdpi;
  min_dpi = 75;
  if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU
      && ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_TPUIR)
  { /* IR mode */
    /*PDBG (pixma_dbg (4, "*create_dpi_list***** TPUIR mode\n"));*/
    min_dpi = (cfg->tpuir_min_dpi) ? cfg->tpuir_min_dpi : 75;
    max_dpi = (cfg->tpuir_max_dpi) ? cfg->tpuir_max_dpi : cfg->xdpi;
  }
  else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU
            || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADF
            || ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_ADFDUP)
  { /* ADF / TPU mode */
    /*PDBG (pixma_dbg (4, "*create_dpi_list***** ADF/TPU mode\n"));*/
    min_dpi = (cfg->adftpu_min_dpi) ? cfg->adftpu_min_dpi : 75;
    max_dpi = (cfg->adftpu_max_dpi) ? cfg->adftpu_max_dpi : cfg->xdpi;
  }
  else if (ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED
            && (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_COLOR_48
                || ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_GRAY_16))
  { /* 48 bits flatbed */
    /*PDBG (pixma_dbg (4, "*create_dpi_list***** 48 bits flatbed mode\n"));*/
    min_dpi = (cfg->min_xdpi_16) ? cfg->min_xdpi_16 : 75;
  }

  /* set j for min. dpi
   *  75 dpi: j = 0
   * 150 dpi: j = 1 \
   * 300 dpi: j = 2 |--> from cfg->adftpu_min_dpi or cfg->tpuir_min_dpi
   * 600 dpi: j = 3 /
   * */
  j = -1;
  min = min_dpi / 75;
  do
  {
    j++;
    min >>= 1;
  }
  while (min > 0);

  /* create dpi_list
   * use j for min. dpi */
  i = 0;
  do
    {
      i++; j++;
      ss->dpi_list[i] = 75 * (1 << (j - 1));    /* 75 x 2^(j-1) */
    }
  while ((unsigned) ss->dpi_list[i] < max_dpi);
  ss->dpi_list[0] = i;
  /*PDBG (pixma_dbg (4, "*create_dpi_list***** min_dpi = %d, max_dpi = %d\n", min_dpi, max_dpi));*/
}


static void
create_calibrate_list (pixma_sane_t * ss)
{
  int i = 0;
  ss->calibrate_list[i] = SANE_I18N ("Once");
  ss->calibrate_map[i] = PIXMA_CALIBRATE_ONCE;
  i++;
  ss->calibrate_list[i] = SANE_I18N ("Always");
  ss->calibrate_map[i] = PIXMA_CALIBRATE_ALWAYS;
  i++;
  ss->calibrate_list[i] = SANE_I18N ("Never");
  ss->calibrate_map[i] = PIXMA_CALIBRATE_NEVER;
  i++;
}

static void
select_value_from_list (pixma_sane_t * ss, SANE_Int n, void *v,
			SANE_Int * info)
{
  SANE_Option_Descriptor *sod = &SOD (n);
  SANE_Word *va = (SANE_Word *) v;
  const SANE_Word *list = sod->constraint.word_list;
  int i, j, nmemb;

  nmemb = sod->size / sizeof (SANE_Word);
  for (i = 0; i < nmemb; i++)
    {
      SANE_Word value = va[i];
      SANE_Word mindelta = abs (value - list[1]);
      SANE_Word nearest = list[1];
      for (j = 2; j <= list[0]; j++)
        {
          SANE_Word delta = abs (value - list[j]);
          if (delta < mindelta)
            {
              mindelta = delta;
              nearest = list[j];
            }
          if (mindelta == 0)
            break;
        }
      if (va[i] != nearest)
        {
          va[i] = nearest;
          *info |= SANE_INFO_INEXACT;
        }
    }
}

static SANE_Status
control_scalar_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v,
		       SANE_Int * info)
{
  option_descriptor_t *opt = &(OPT_IN_CTX[n]);
  SANE_Word val;

  /* PDBG (pixma_dbg (4, "*control_scalar_option***** n = %u, a = %u\n", n, a)); */

  switch (a)
    {
    case SANE_ACTION_GET_VALUE:
      switch (opt->sod.type)
        {
        case SANE_TYPE_BOOL:
        case SANE_TYPE_INT:
        case SANE_TYPE_FIXED:
          *(SANE_Word *) v = opt->val.w;
          break;
        default:
          return SANE_STATUS_UNSUPPORTED;
        }
      return SANE_STATUS_GOOD;

    case SANE_ACTION_SET_VALUE:
      switch (opt->sod.type)
        {
        case SANE_TYPE_BOOL:
          val = *(SANE_Word *) v;
          if (val != SANE_TRUE && val != SANE_FALSE)
            return SANE_STATUS_INVAL;
          opt->val.w = val;
          break;
        case SANE_TYPE_INT:
        case SANE_TYPE_FIXED:
          if (opt->sod.constraint_type == SANE_CONSTRAINT_RANGE)
            clamp_value (ss, n, v, info);
          else if (opt->sod.constraint_type == SANE_CONSTRAINT_WORD_LIST)
            select_value_from_list (ss, n, v, info);
          opt->val.w = *(SANE_Word *) v;
          break;
        default:
          return SANE_STATUS_UNSUPPORTED;
        }
      *info |= opt->info;
      return SANE_STATUS_GOOD;

    case SANE_ACTION_SET_AUTO:
      switch (opt->sod.type)
        {
        case SANE_TYPE_BOOL:
        case SANE_TYPE_INT:
        case SANE_TYPE_FIXED:
          opt->val.w = opt->def.w;
          break;
        default:
          return SANE_STATUS_UNSUPPORTED;
        }
      *info |= opt->info;
      return SANE_STATUS_GOOD;
    }
  return SANE_STATUS_UNSUPPORTED;
}

static SANE_Status
control_string_option (pixma_sane_t * ss, SANE_Int n, SANE_Action a, void *v,
		       SANE_Int * info)
{
  option_descriptor_t *opt = &(OPT_IN_CTX[n]);
  const SANE_String_Const *slist = opt->sod.constraint.string_list;
  SANE_String str = (SANE_String) v;

  /* PDBG (pixma_dbg (4, "*control_string_option***** n = %u, a = %u\n", n, a)); */

  if (opt->sod.constraint_type == SANE_CONSTRAINT_NONE)
    {
      switch (a)
        {
        case SANE_ACTION_GET_VALUE:
          strcpy (str, opt->val.s);
          break;
        case SANE_ACTION_SET_AUTO:
          str = opt->def.s;
          /* fall through */
        case SANE_ACTION_SET_VALUE:
          strncpy (opt->val.s, str, opt->sod.size - 1);
          *info |= opt->info;
          break;
        }
      return SANE_STATUS_GOOD;
    }
  else
    {
      int i;

      switch (a)
        {
        case SANE_ACTION_GET_VALUE:
          strcpy (str, slist[opt->val.w]);
          break;
        case SANE_ACTION_SET_AUTO:
          str = opt->def.ptr;
          /* fall through */
        case SANE_ACTION_SET_VALUE:
          i = 0;
          while (slist[i] && strcasecmp (str, slist[i]) != 0)
            i++;
          if (!slist[i])
            return SANE_STATUS_INVAL;
          if (strcmp (slist[i], str) != 0)
            {
              strcpy (str, slist[i]);
              *info |= SANE_INFO_INEXACT;
            }
          opt->val.w = i;
          *info |= opt->info;
          break;
        }
      return SANE_STATUS_GOOD;
    }
}

static SANE_Status
control_option (pixma_sane_t * ss, SANE_Int n,
		SANE_Action a, void *v, SANE_Int * info)
{
  SANE_Option_Descriptor *sod = &SOD (n);
  int result, i;
  const pixma_config_t *cfg;
  SANE_Int dummy;

  /* info may be null, better to set a dummy here then test everywhere */
  if (info == NULL)
    info = &dummy;

  cfg = pixma_get_config (ss->s);

  /* PDBG (pixma_dbg (4, "*control_option***** n = %u, a = %u\n", n, a)); */

  /* first deal with options that require special treatment */
  result = SANE_STATUS_UNSUPPORTED;
  switch (n)
    {
      case opt_gamma_table:
        {
          int table_size = sod->size / sizeof (SANE_Word);
          int byte_cnt = table_size == 1024 ? 2 : 1;

          switch (a)
            {
            case SANE_ACTION_SET_VALUE:
              PDBG (pixma_dbg (4, "*control_option***** opt_gamma_table: SANE_ACTION_SET_VALUE with %d values ***** \n", table_size));
              clamp_value (ss, n, v, info);
              if (byte_cnt == 1)
                {
                  for (i = 0; i < table_size; i++)
                    ss->gamma_table[i] = *((SANE_Int *) v + i);
                }
              else
                {
                  for (i = 0; i < table_size; i++)
                    {
                      ss->gamma_table[i * 2] = *((SANE_Int *) v + i);
                      ss->gamma_table[i * 2 + 1] = *((uint8_t *)((SANE_Int *) v + i) + 1);
                    }
                }
              /* PDBG (pixma_hexdump (4, (uint8_t *)v, table_size * 4)); */
              /* PDBG (pixma_hexdump (4, ss->gamma_table, table_size * byte_cnt)); */
              break;
            case SANE_ACTION_GET_VALUE:
              PDBG (pixma_dbg (4, "*control_option***** opt_gamma_table: SANE_ACTION_GET_VALUE ***** \n"));
              if (byte_cnt == 1)
                {
                  for (i = 0; i < table_size; i++)
                    *((SANE_Int *) v + i) = ss->gamma_table[i];
                }
              else
                {
                  for (i = 0; i < table_size; i++)
                    {
                      *((SANE_Int *) v + i) = ss->gamma_table[i * 2];
                      *((uint8_t *)((SANE_Int *) v + i) + 1) = ss->gamma_table[i * 2 + 1];
                    }
                }
              break;
            case SANE_ACTION_SET_AUTO:
              PDBG (pixma_dbg (4, "*control_option***** opt_gamma_table: SANE_ACTION_SET_AUTO with gamma=%f ***** \n",
                               SANE_UNFIX (OVAL (opt_gamma).w)));
              pixma_fill_gamma_table (SANE_UNFIX (OVAL (opt_gamma).w),
                                      ss->gamma_table, table_size);
              /* PDBG (pixma_hexdump (4, ss->gamma_table, table_size * byte_cnt)); */
              break;
            default:
              return SANE_STATUS_UNSUPPORTED;
            }
          return SANE_STATUS_GOOD;
        }

      case opt_button_update:
        if (a == SANE_ACTION_SET_VALUE)
          {
            update_button_state (ss, info);
            return SANE_STATUS_GOOD;
          }
        else
          {
            return SANE_STATUS_INVAL;
          }
        break;
      case opt_button_1:
      case opt_button_2:
      case opt_original:
      case opt_target:
      case opt_scan_resolution:
      case opt_document_type:
      case opt_adf_status:
      case opt_adf_orientation:
        /* poll scanner if option is not cached */
        if (! ss->button_option_is_cached[ BUTTON_GROUP_INDEX(n) ] )
          update_button_state (ss, info);
        /* mark this option as read */
        ss->button_option_is_cached[  BUTTON_GROUP_INDEX(n) ] = 0;
    }

  /* now deal with getting and setting of options */
  switch (SOD (n).type)
    {
    case SANE_TYPE_BOOL:
    case SANE_TYPE_INT:
    case SANE_TYPE_FIXED:
      result = control_scalar_option (ss, n, a, v, info);
      break;
    case SANE_TYPE_STRING:
      result = control_string_option (ss, n, a, v, info);
      break;
    case SANE_TYPE_BUTTON:
    case SANE_TYPE_GROUP:
      PDBG (pixma_dbg (1, "BUG:control_option():Unhandled option\n"));
      result = SANE_STATUS_INVAL;
      break;
    }
  if (result != SANE_STATUS_GOOD)
    return result;

  /* deal with dependencies between options */
  switch (n)
    {
    case opt_custom_gamma:
      if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)
        {
          if (enable_option (ss, opt_gamma_table, OVAL (opt_custom_gamma).b))
            *info |= SANE_INFO_RELOAD_OPTIONS;
          if (OVAL (opt_custom_gamma).b)
            sane_control_option (ss, opt_gamma_table, SANE_ACTION_SET_AUTO,
                                 NULL, NULL);

        }
      break;
    case opt_gamma:
      if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO)
        {
          int table_size = SOD (opt_gamma_table).size / sizeof(SANE_Word);
          PDBG (pixma_dbg (4, "*control_option***** gamma = %f *\n",
                           SANE_UNFIX (OVAL (opt_gamma).w)));
          PDBG (pixma_dbg (4, "*control_option***** table size = %d *\n",
                           (int)(SOD (opt_gamma_table).size / sizeof (SANE_Word))));
          pixma_fill_gamma_table (SANE_UNFIX (OVAL (opt_gamma).w),
                                  ss->gamma_table, table_size);
          /* PDBG (pixma_hexdump (4, ss->gamma_table,
                               table_size == 1024 ? 2048 : table_size)); */
        }
      break;
    case opt_mode:
      if (cfg->cap & (PIXMA_CAP_48BIT|PIXMA_CAP_LINEART|PIXMA_CAP_TPUIR)
          && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO))
        { /* new mode selected: Color, Gray, ... */
          /* PDBG (pixma_dbg (4, "*control_option***** mode = %u *\n",
                           ss->mode_map[OVAL (opt_mode).w])); */
          /* recreate dynamic lists */
          create_dpi_list (ss);
          if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART)
            { /* lineart */
              enable_option (ss, opt_threshold, SANE_TRUE);
              enable_option (ss, opt_threshold_curve, SANE_TRUE);
            }
          else
            { /* all other modes */
              enable_option (ss, opt_threshold, SANE_FALSE);
              enable_option (ss, opt_threshold_curve, SANE_FALSE);
            }
          *info |= SANE_INFO_RELOAD_OPTIONS;
        }
      break;
    case opt_source:
      if ((cfg->cap & (PIXMA_CAP_ADF|PIXMA_CAP_ADFDUP|PIXMA_CAP_TPU))
          && (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO))
        {
          /* new source selected: flatbed, ADF, TPU, ... */
          pixma_scan_mode_t curr_mode = ss->mode_map[OVAL (opt_mode).w];
          SANE_Word curr_res = OVAL (opt_resolution).w;

          /* recreate dynamic lists */
          create_mode_list (ss);
          create_dpi_list (ss);

          /*
           * Check to see if the mode and res are still valid.
           * Replace with default mode or closest res if not.
           *
           */
          for (SANE_Int mode_idx = 0;; mode_idx++)
            {
              if (!ss->mode_list[mode_idx])
                {
                  OVAL (opt_mode).w = 0;
                  break;
                }
              if (curr_mode == ss->mode_map[mode_idx])
                {
                  OVAL (opt_mode).w = mode_idx;
                  break;
                }
            }

          for (SANE_Int res_idx = 1;; res_idx++)
            {
              if (res_idx > ss->dpi_list[0])
                {
                  OVAL (opt_resolution).w = ss->dpi_list[1];
                  break;
                }
              if (ss->dpi_list[res_idx] >= curr_res)
                {
                  OVAL (opt_resolution).w = ss->dpi_list[res_idx];
                  break;
                }
            }

          if (ss->mode_map[OVAL (opt_mode).w] == PIXMA_SCAN_MODE_LINEART)
            { /* lineart */
              enable_option (ss, opt_threshold, SANE_TRUE);
              enable_option (ss, opt_threshold_curve, SANE_TRUE);
            }
          else
            { /* all other modes */
              enable_option (ss, opt_threshold, SANE_FALSE);
              enable_option (ss, opt_threshold_curve, SANE_FALSE);
            }
          if (cfg->cap & (PIXMA_CAP_ADF_WAIT))
            { /* adf-wait */
              enable_option (ss, opt_adf_wait, SANE_TRUE);
            }
          else
            { /* disable adf-wait */
              enable_option (ss, opt_adf_wait, SANE_FALSE);
            }
          *info |= SANE_INFO_RELOAD_OPTIONS;
        }
      break;
    }

  return result;
}

#ifndef NDEBUG
static void
print_scan_param (int level, const pixma_scan_param_t * sp)
{
  pixma_dbg (level, "Scan parameters\n");
  pixma_dbg (level, "  line_size=%"PRIu64" image_size=%"PRIu64" channels=%u depth=%u\n",
	     sp->line_size, sp->image_size, sp->channels, sp->depth);
  pixma_dbg (level, "  dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n",
	     sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h);
  pixma_dbg (level, "  gamma=%f gamma_table=%p source=%d\n", sp->gamma,
             (void *) sp->gamma_table, sp->source);
  pixma_dbg (level, "  adf-wait=%d\n", sp->adf_wait);
}
#endif

static int
calc_scan_param (pixma_sane_t * ss, pixma_scan_param_t * sp)
{
  int x1, y1, x2, y2;
  int error;

  memset (sp, 0, sizeof (*sp));

  sp->channels = (OVAL (opt_mode).w == 0) ? 3 : 1;
  sp->depth = (OVAL (opt_mode).w == 2) ? 1 : 8;
  sp->xdpi = sp->ydpi = OVAL (opt_resolution).w;

#define PIXEL(x,dpi) (int)((SANE_UNFIX(x) / 25.4 * (dpi)) + 0.5)
  x1 = PIXEL (OVAL (opt_tl_x).w, sp->xdpi);
  x2 = PIXEL (OVAL (opt_br_x).w, sp->xdpi);
  if (x2 < x1)
    {
      int temp = x1;
      x1 = x2;
      x2 = temp;
    }
  y1 = PIXEL (OVAL (opt_tl_y).w, sp->ydpi);
  y2 = PIXEL (OVAL (opt_br_y).w, sp->ydpi);
  if (y2 < y1)
    {
      int temp = y1;
      y1 = y2;
      y2 = temp;
    }
#undef PIXEL
  sp->x = x1;
  sp->y = y1;
  sp->w = x2 - x1;
  sp->h = y2 - y1;
  if (sp->w == 0)
    sp->w = 1;
  if (sp->h == 0)
    sp->h = 1;
  sp->tpu_offset_added = 0;

  sp->gamma = SANE_UNFIX (OVAL (opt_gamma).w);
  sp->gamma_table = ss->gamma_table;
  sp->source = ss->source_map[OVAL (opt_source).w];
  sp->mode = ss->mode_map[OVAL (opt_mode).w];
  sp->adf_pageid = ss->page_count;
  sp->threshold = 2.55 * OVAL (opt_threshold).w;
  sp->threshold_curve = OVAL (opt_threshold_curve).w;
  sp->adf_wait = OVAL (opt_adf_wait).w;
  sp->calibrate = ss->calibrate_map[OVAL (opt_calibrate).w];

  error = pixma_check_scan_param (ss->s, sp);
  if (error < 0)
    {
      PDBG (pixma_dbg (1, "BUG:calc_scan_param() failed %d\n", error));
      PDBG (print_scan_param (1, sp));
    }
  return error;
}

static void
init_option_descriptors (pixma_sane_t * ss)
{
  const pixma_config_t *cfg;
  int i;

  cfg = pixma_get_config (ss->s);

  /* PDBG (pixma_dbg (4, "*init_option_descriptors*****\n")); */

  /* setup range for the scan area. */
  ss->xrange.min = SANE_FIX (0);
  ss->xrange.max = SANE_FIX (cfg->width / 75.0 * 25.4);
  ss->xrange.quant = SANE_FIX (0);

  ss->yrange.min = SANE_FIX (0);
  ss->yrange.max = SANE_FIX (cfg->height / 75.0 * 25.4);
  ss->yrange.quant = SANE_FIX (0);

  /* mode_list and source_list were already NULL-terminated,
   * because the whole pixma_sane_t was cleared during allocation. */

  /* setup available mode. */
  create_mode_list (ss);

  /* setup dpi up to the value supported by the scanner. */
  create_dpi_list (ss);

  /* setup paper source */
  i = 0;
  ss->source_list[i] = SANE_I18N ("Flatbed");
  ss->source_map[i] = PIXMA_SOURCE_FLATBED;
  i++;
  if (cfg->cap & PIXMA_CAP_ADF)
    {
      ss->source_list[i] = SANE_I18N ("Automatic Document Feeder");
      ss->source_map[i] = PIXMA_SOURCE_ADF;
      i++;
    }
  if ((cfg->cap & PIXMA_CAP_ADFDUP) == PIXMA_CAP_ADFDUP)
    {
      ss->source_list[i] = SANE_I18N ("ADF Duplex");
      ss->source_map[i] = PIXMA_SOURCE_ADFDUP;
      i++;
    }
  if (cfg->cap & PIXMA_CAP_TPU)
    {
      ss->source_list[i] = SANE_I18N ("Transparency Unit");
      ss->source_map[i] = PIXMA_SOURCE_TPU;
      i++;
    }

  create_calibrate_list (ss);

  build_option_descriptors (ss);

  /* Enable options that are available only in some scanners. */
  if (cfg->cap & PIXMA_CAP_GAMMA_TABLE)
    {
      SANE_Option_Descriptor *sod = &SOD (opt_gamma_table);

      /* some scanners have a large gamma table with 4096 entries */
      if (cfg->cap & PIXMA_CAP_GT_4096)
        {
          static const SANE_Range constraint_gamma_table_4096 = { 0,0xff,0 };
          sod->desc = SANE_I18N("Gamma-correction table with 4096 entries. In color mode this option equally affects the red, green, and blue channels simultaneously (i.e., it is an intensity gamma table).");
          sod->size = 4096 * sizeof(SANE_Word);
          sod->constraint.range = &constraint_gamma_table_4096;
        }

      /* PDBG (pixma_dbg (4, "*%s***** PIXMA_CAP_GAMMA_TABLE ***** \n",
                       __func__)); */
      /* PDBG (pixma_dbg (4, "%s: gamma_table_contraint.max = %d\n",
                       __func__,  sod->constraint.range->max)); */
      /* PDBG (pixma_dbg (4, "%s: gamma_table_size = %d\n",
                       __func__,  sod->size / sizeof(SANE_Word))); */

      /* activate option gamma */
      enable_option (ss, opt_gamma, SANE_TRUE);
      sane_control_option (ss, opt_gamma, SANE_ACTION_SET_AUTO,
                           NULL, NULL);
      /* activate option custom gamma table */
      enable_option (ss, opt_custom_gamma, SANE_TRUE);
      sane_control_option (ss, opt_custom_gamma, SANE_ACTION_SET_AUTO,
                           NULL, NULL);
    }
  enable_option (ss, opt_button_controlled,
		 ((cfg->cap & PIXMA_CAP_EVENTS) != 0));
}

/* Writing to reader_ss outside reader_process() is a BUG! */
static pixma_sane_t *reader_ss = NULL;

static void
reader_signal_handler (int sig)
{
  if (reader_ss)
    {
      reader_ss->reader_stop = SANE_TRUE;
      /* reader process is ended by SIGTERM, so no cancel in this case */
      if (sig != SIGTERM)
        pixma_cancel (reader_ss->s);
    }
}

static int
write_all (pixma_sane_t * ss, void *buf_, size_t size)
{
  uint8_t *buf = (uint8_t *) buf_;
  int count;

  while (size != 0 && !ss->reader_stop)
    {
      count = write (ss->wpipe, buf, size);
      if (count == -1 && errno != EINTR)
	break;
      if (count == -1 && errno == EINTR)
	continue;
      buf += count;
      size -= count;
    }
  return buf - (uint8_t *) buf_;
}

/* NOTE: reader_loop() runs either in a separate thread or process. */
static SANE_Status
reader_loop (pixma_sane_t * ss)
{
  void *buf;
  unsigned bufsize;
  int count = 0;

  PDBG (pixma_dbg (3, "Reader task started\n"));
  /*bufsize = ss->sp.line_size + 1;*/	/* XXX: "odd" bufsize for testing pixma_read_image() */
  bufsize = ss->sp.line_size;   /* bufsize EVEN needed by Xsane for 48 bits depth */
  buf = malloc (bufsize);
  if (!buf)
    {
      count = PIXMA_ENOMEM;
      goto done;
    }

  count = pixma_activate_connection (ss->s);
  if (count < 0)
    goto done;

  pixma_enable_background (ss->s, 1);
  if (OVAL (opt_button_controlled).b && ss->page_count == 0)
    {
      int start = 0;
#ifndef NDEBUG
      pixma_dbg (1, "==== Button-controlled scan mode is enabled.\n");
      pixma_dbg (1, "==== To proceed, press 'SCAN' or 'COLOR' button. "
		 "To cancel, press 'GRAY' or 'END' button.\n");
#endif
      while (pixma_wait_event (ss->s, 10) != 0)
        {
        }
      while (!start)
        {
          uint32_t events;
          if (ss->reader_stop)
            {
              count = PIXMA_ECANCELED;
              goto done;
            }
          events = pixma_wait_event (ss->s, 1000);
          switch (events & ~PIXMA_EV_ACTION_MASK)
            {
            case PIXMA_EV_BUTTON1:
              start = 1;
              break;
            case PIXMA_EV_BUTTON2:
              count = PIXMA_ECANCELED;
              goto done;
            }
        }
    }
  count = pixma_scan (ss->s, &ss->sp);
  if (count >= 0)
    {
      while ((count = pixma_read_image (ss->s, buf, bufsize)) > 0)
        {
          if (write_all (ss, buf, count) != count)
            pixma_cancel (ss->s);
        }
    }

done:
  pixma_enable_background (ss->s, 0);
  pixma_deactivate_connection (ss->s);
  free (buf);
  close (ss->wpipe);
  ss->wpipe = -1;
  if (count >= 0)
    {
      PDBG (pixma_dbg (3, "Reader task terminated\n"));
    }
  else
    {
      PDBG (pixma_dbg
	    (2, "Reader task terminated: %s\n", pixma_strerror (count)));
    }
  return map_error (count);
}

static int
reader_process (void *arg)
{
  pixma_sane_t *ss = (pixma_sane_t *) arg;
  struct SIGACTION sa;

  reader_ss = ss;
  memset (&sa, 0, sizeof (sa));
  sigemptyset (&sa.sa_mask);
  sa.sa_handler = reader_signal_handler;
  /* FIXME: which signal else? */
  sigaction (SIGHUP, &sa, NULL);
  sigaction (SIGINT, &sa, NULL);
  sigaction (SIGPIPE, &sa, NULL);
  sigaction (SIGTERM, &sa, NULL);
  close (ss->rpipe);
  ss->rpipe = -1;
  return reader_loop (ss);
}

static int
reader_thread (void *arg)
{
  pixma_sane_t *ss = (pixma_sane_t *) arg;
#ifdef USE_PTHREAD
  /* Block SIGPIPE. We will handle this in reader_loop() by checking
     ss->reader_stop and the return value from write(). */
  sigset_t sigs;
  sigemptyset (&sigs);
  sigaddset (&sigs, SIGPIPE);
  pthread_sigmask (SIG_BLOCK, &sigs, NULL);
#endif /* USE_PTHREAD */
  return reader_loop (ss);
}

static SANE_Pid
terminate_reader_task (pixma_sane_t * ss, int *exit_code)
{
  SANE_Pid result, pid;
  int status = 0;

  pid = ss->reader_taskid;
  if (!sanei_thread_is_valid (pid))
    return pid;
  if (sanei_thread_is_forked ())
    {
      sanei_thread_kill (pid);
    }
  else
    {
      ss->reader_stop = SANE_TRUE;
/*      pixma_cancel (ss->s);   What is this for ? Makes end-of-scan buggy => removing */
    }
  result = sanei_thread_waitpid (pid, &status);
  sanei_thread_invalidate (ss->reader_taskid);

  if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
    ss->idle = SANE_TRUE;

  if (result == pid)
    {
      if (exit_code)
	      *exit_code = status;
      return pid;
    }
  else
    {
      PDBG (pixma_dbg (1, "WARNING:waitpid() failed %s\n", strerror (errno)));
      sanei_thread_invalidate (pid);
      return pid;
    }
}

static int
start_reader_task (pixma_sane_t * ss)
{
  int fds[2];
  SANE_Pid pid;
  int is_forked;

  if (ss->rpipe != -1 || ss->wpipe != -1)
    {
      PDBG (pixma_dbg
	    (1, "BUG:rpipe = %d, wpipe = %d\n", ss->rpipe, ss->wpipe));
      close (ss->rpipe);
      close (ss->wpipe);
      ss->rpipe = -1;
      ss->wpipe = -1;
    }
  if (sanei_thread_is_valid (ss->reader_taskid))
    {
      PDBG (pixma_dbg
	    (1, "BUG:reader_taskid(%ld) != -1\n", (long) ss->reader_taskid));
      terminate_reader_task (ss, NULL);
    }
  if (pipe (fds) == -1)
    {
      PDBG (pixma_dbg (1, "ERROR:start_reader_task():pipe() failed %s\n",
		       strerror (errno)));
      return PIXMA_ENOMEM;
    }
  ss->rpipe = fds[0];
  ss->wpipe = fds[1];
  ss->reader_stop = SANE_FALSE;

  is_forked = sanei_thread_is_forked ();
  if (is_forked)
    {
      pid = sanei_thread_begin (reader_process, ss);
      if (sanei_thread_is_valid (pid))
        {
          close (ss->wpipe);
          ss->wpipe = -1;
        }
    }
  else
    {
      pid = sanei_thread_begin (reader_thread, ss);
    }
  if (!sanei_thread_is_valid (pid))
    {
      close (ss->wpipe);
      close (ss->rpipe);
      ss->wpipe = -1;
      ss->rpipe = -1;
      PDBG (pixma_dbg (1, "ERROR:unable to start reader task\n"));
      return PIXMA_ENOMEM;
    }
  PDBG (pixma_dbg (3, "Reader task id=%ld (%s)\n", (long) pid,
		   (is_forked) ? "forked" : "threaded"));
  ss->reader_taskid = pid;
  return 0;
}

/* libJPEG API callbacks */
static void
jpeg_init_source(j_decompress_ptr __sane_unused__ cinfo)
{
  /* No-op */
}

static void
jpeg_term_source(j_decompress_ptr __sane_unused__ cinfo)
{
  /* No-op */
}

static boolean
jpeg_fill_input_buffer(j_decompress_ptr cinfo)
{
  pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src;
  int size;
  int retry;

  for (retry = 0; retry < 30; retry ++ )
    {
      size = read (mgr->s->rpipe, mgr->buffer, 1024);
      if (size == 0)
        {
          return FALSE;
        }
      else if (size < 0)
        {
          sleep (1);
        }
      else
        {
          mgr->jpeg.next_input_byte = mgr->buffer;
          mgr->jpeg.bytes_in_buffer = size;
          return TRUE;
        }
    }

  return FALSE;
}

static void
jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
  pixma_jpeg_src_mgr *mgr = (pixma_jpeg_src_mgr *)cinfo->src;

  if (num_bytes > 0)
    {
      /* Read and throw away extra */
      while (num_bytes > (long)mgr->jpeg.bytes_in_buffer)
        {
           num_bytes -= (long)mgr->jpeg.bytes_in_buffer;
           jpeg_fill_input_buffer(cinfo);
        }

      /* Update jpeg info structure with leftover */
      mgr->jpeg.next_input_byte += (size_t) num_bytes;
      mgr->jpeg.bytes_in_buffer -= (size_t) num_bytes;
    }
}

/* Pixma JPEG reader helpers */
static SANE_Status
pixma_jpeg_start(pixma_sane_t *s)
{
  pixma_jpeg_src_mgr *mgr;

  s->jpeg_cinfo.err = jpeg_std_error(&s->jpeg_err);

  jpeg_create_decompress(&s->jpeg_cinfo);

  s->jpeg_cinfo.src = (struct jpeg_source_mgr *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo,
                              JPOOL_PERMANENT, sizeof(pixma_jpeg_src_mgr));

  memset(s->jpeg_cinfo.src, 0, sizeof(pixma_jpeg_src_mgr));

  mgr = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src;
  mgr->s = s;

  mgr->buffer = (JOCTET *)(*s->jpeg_cinfo.mem->alloc_small)((j_common_ptr)&s->jpeg_cinfo,
                                                  JPOOL_PERMANENT,
                                                  1024 * sizeof(JOCTET));

  mgr->jpeg.init_source = jpeg_init_source;
  mgr->jpeg.fill_input_buffer = jpeg_fill_input_buffer;
  mgr->jpeg.skip_input_data = jpeg_skip_input_data;
  mgr->jpeg.resync_to_restart = jpeg_resync_to_restart;
  mgr->jpeg.term_source = jpeg_term_source;
  mgr->jpeg.bytes_in_buffer = 0;
  mgr->jpeg.next_input_byte = NULL;

  s->jpeg_header_seen = 0;

  return SANE_STATUS_GOOD;
}

static SANE_Status
pixma_jpeg_read_header(pixma_sane_t *s)
{
  pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)s->jpeg_cinfo.src;

  if (jpeg_read_header(&s->jpeg_cinfo, TRUE))
    {
      s->jdst = sanei_jpeg_jinit_write_ppm(&s->jpeg_cinfo);

      if (jpeg_start_decompress(&s->jpeg_cinfo))
        {
          int size;

          DBG(3, "%s: w: %d, h: %d, components: %d\n",
                  __func__,
                  s->jpeg_cinfo.output_width, s->jpeg_cinfo.output_height,
                  s->jpeg_cinfo.output_components);

          size = s->jpeg_cinfo.output_width * s->jpeg_cinfo.output_components * 1;

          src->linebuffer = (*s->jpeg_cinfo.mem->alloc_large)((j_common_ptr)&s->jpeg_cinfo,
                  JPOOL_PERMANENT, size);

          src->linebuffer_size = 0;
          src->linebuffer_index = 0;

          s->jpeg_header_seen = 1;

          return SANE_STATUS_GOOD;
        }
      else
        {
          DBG(0, "%s: decompression failed\n", __func__);
          return SANE_STATUS_IO_ERROR;
        }
    }
  else
    {
      DBG(0, "%s: cannot read JPEG header\n", __func__);
      return SANE_STATUS_IO_ERROR;
    }
}

static void
pixma_jpeg_finish(pixma_sane_t *ss)
{
  jpeg_destroy_decompress(&ss->jpeg_cinfo);
}

static void
pixma_jpeg_read(pixma_sane_t *ss, SANE_Byte *data,
           SANE_Int max_length, SANE_Int *length)
{
  struct jpeg_decompress_struct *cinfo = &ss->jpeg_cinfo;
  pixma_jpeg_src_mgr *src = (pixma_jpeg_src_mgr *)cinfo->src;

  int l;

  *length = 0;

  /* copy from line buffer if available */
  if (src->linebuffer_size && src->linebuffer_index < src->linebuffer_size)
    {
      *length = src->linebuffer_size - src->linebuffer_index;

      if (*length > max_length)
        *length = max_length;

      memcpy(data, src->linebuffer + src->linebuffer_index, *length);
             src->linebuffer_index += *length;

      return;
    }

  if (cinfo->output_scanline >= cinfo->output_height)
    {
      *length = 0;
      return;
    }

  /* scanlines of decompressed data will be in ss->jdst->buffer
   * only one line at time is supported
   */

  l = jpeg_read_scanlines(cinfo, ss->jdst->buffer, 1);
  if (l == 0)
    return;

  /* from ss->jdst->buffer to linebuffer
   * linebuffer holds width * bytesperpixel
   */

  (*ss->jdst->put_pixel_rows)(cinfo, ss->jdst, 1, (char *)src->linebuffer);

  *length = ss->sp.w * ss->sp.channels;
  /* Convert RGB into grayscale */
  if (ss->sp.channels == 1)
    {
      unsigned int i;
      unsigned char *d = (unsigned char *)src->linebuffer;
      unsigned char *s = (unsigned char *)src->linebuffer;
      for (i = 0; i < ss->sp.w; i++)
        {
          /* Using BT.709 luma formula, fixed-point */
          int sum = ( s[0]*2126 + s[1]*7152 + s[2]*722 );
          *d = sum / 10000;
          d ++;
          s += 3;
        }
    }

  /* Maybe pack into lineary binary image */
  if (ss->sp.depth == 1)
    {
      *length /= 8;
      unsigned int i;
      unsigned char *d = (unsigned char *)src->linebuffer;
      unsigned char *s = (unsigned char *)src->linebuffer;
      unsigned char b = 0;
      for (i = 1; i < ss->sp.w + 1; i++)
        {
          if (*(s++) > 127)
            b = (b << 1) | 0;
         else
            b = (b << 1) | 1;
          if ((i % 8) == 0)
            *(d++) = b;
        }
    }

  src->linebuffer_size = *length;
  src->linebuffer_index = 0;

  if (*length > max_length)
    *length = max_length;

  memcpy(data, src->linebuffer + src->linebuffer_index, *length);
        src->linebuffer_index += *length;
}



static SANE_Status
read_image (pixma_sane_t * ss, void *buf, unsigned size, int *readlen)
{
  int count, status;

  if (readlen)
    *readlen = 0;
  if (ss->image_bytes_read >= ss->sp.image_size)
    return SANE_STATUS_EOF;

  do
    {
      if (ss->cancel)
        /* ss->rpipe has already been closed by sane_cancel(). */
        return SANE_STATUS_CANCELLED;
      if (ss->sp.mode_jpeg && !ss->jpeg_header_seen)
        {
          status = pixma_jpeg_read_header(ss);
          if (status != SANE_STATUS_GOOD)
            {
              close (ss->rpipe);
              pixma_jpeg_finish(ss);
              ss->rpipe = -1;
              if (sanei_thread_is_valid (terminate_reader_task (ss, &status))
                && status != SANE_STATUS_GOOD)
                {
                  return status;
                }
              else
                {
                  /* either terminate_reader_task failed or
                     rpipe was closed but we expect more data */
                  return SANE_STATUS_IO_ERROR;
                }
            }
        }

      if (ss->sp.mode_jpeg)
        {
          count = -1;
          pixma_jpeg_read(ss, buf, size, &count);
        }
      else
        count = read (ss->rpipe, buf, size);
    }
  while (count == -1 && errno == EINTR);

  if (count == -1)
    {
      if (errno == EAGAIN)
        return SANE_STATUS_GOOD;
      if (!ss->cancel)
        {
          PDBG (pixma_dbg (1, "WARNING:read_image():read() failed %s\n",
               strerror (errno)));
        }
      close (ss->rpipe);
      ss->rpipe = -1;
      terminate_reader_task (ss, NULL);
      if (ss->sp.mode_jpeg)
        pixma_jpeg_finish(ss);
      return SANE_STATUS_IO_ERROR;
    }

  /* here count >= 0 */
  ss->image_bytes_read += count;
  if (ss->image_bytes_read > ss->sp.image_size)
    {
      PDBG (pixma_dbg (1, "BUG:ss->image_bytes_read > ss->sp.image_size\n"));
    }
  if (ss->image_bytes_read >= ss->sp.image_size)
    {
      close (ss->rpipe);
      ss->rpipe = -1;
      terminate_reader_task (ss, NULL);
      if (ss->sp.mode_jpeg)
        pixma_jpeg_finish(ss);
    }
  else if (count == 0)
    {
      PDBG (pixma_dbg (3, "read_image():reader task closed the pipe:%"
		       PRIu64" bytes received, %"PRIu64" bytes expected\n",
		       ss->image_bytes_read, ss->sp.image_size));
      close (ss->rpipe);
      if (ss->sp.mode_jpeg)
        pixma_jpeg_finish(ss);
      ss->rpipe = -1;
      if (sanei_thread_is_valid (terminate_reader_task (ss, &status))
      	  && status != SANE_STATUS_GOOD)
        {
          return status;
        }
      else
        {
          /* either terminate_reader_task failed or
             rpipe was closed but we expect more data */
          return SANE_STATUS_IO_ERROR;
        }
    }
  if (readlen)
    *readlen = count;
  return SANE_STATUS_GOOD;
}


/*******************************************************************
 ** SANE API
 *******************************************************************/
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  int status, myversion, i;
  SANEI_Config config;

  UNUSED (authorize);

  if (!version_code)
    return SANE_STATUS_INVAL;
  myversion = 100 * PIXMA_VERSION_MAJOR + PIXMA_VERSION_MINOR;
  *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, myversion);
  DBG_INIT ();
  sanei_thread_init ();
  pixma_set_debug_level (DBG_LEVEL);

  PDBG(pixma_dbg(2, "pixma is compiled %s pthread support.\n",
                   (sanei_thread_is_forked () ? "without" : "with")));

  for (i = 0; i < MAX_CONF_DEVICES; i++)
    conf_devices[i] = NULL;

  config.count = 0;
  config.descriptors = NULL;
  config.values = NULL;

  if (sanei_configure_attach(PIXMA_CONFIG_FILE, &config,
                             config_attach_pixma, NULL) != SANE_STATUS_GOOD)
    PDBG(pixma_dbg(2, "Could not read pixma configuration file: %s\n",
                   PIXMA_CONFIG_FILE));

  status = pixma_init ();
  if (status < 0)
    {
      PDBG (pixma_dbg (2, "pixma_init() failed %s\n", pixma_strerror (status)));
    }
  return map_error (status);
}

void
sane_exit (void)
{
  while (first_scanner)
    sane_close (first_scanner);
  cleanup_device_list ();
  pixma_cleanup ();
  sanei_usb_exit ();
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  if (!device_list)
    return SANE_STATUS_INVAL;
  find_scanners (local_only);
  *device_list = dev_list;
  return (dev_list) ? SANE_STATUS_GOOD : SANE_STATUS_NO_MEM;
}

SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * h)
{
  unsigned i, j, nscanners;
  int error = 0;
  pixma_sane_t *ss = NULL;
  const pixma_config_t *cfg;

  if (!name || !h)
    return SANE_STATUS_INVAL;

  *h = NULL;
  nscanners = pixma_find_scanners (conf_devices, SANE_FALSE);
  if (nscanners == 0)
    return SANE_STATUS_INVAL;

  /* also get device id if we replay a xml file
   * otherwise name contains the xml filename
   * and further replay will fail  */
  if (name[0] == '\0' || strstr (name, ".xml"))
    name = pixma_get_device_id (0);

  /* Have we already opened the scanner? */
  for (ss = first_scanner; ss; ss = ss->next)
    {
      if (strcmp (pixma_get_string (ss->s, PIXMA_STRING_ID), name) == 0)
        {
          /* We have already opened it! */
          return SANE_STATUS_DEVICE_BUSY;
        }
    }

  i = 0;
  while (strcmp (pixma_get_device_id (i), name) != 0)
    {
      if (++i >= nscanners)
	      return SANE_STATUS_INVAL;
    }
  cfg = pixma_get_device_config (i);
  if ((cfg->cap & PIXMA_CAP_EXPERIMENT) != 0)
    {
#ifndef NDEBUG
      pixma_dbg (1, "WARNING:"
		 "Experimental backend CAN DAMAGE your hardware!\n");
      if (getenv_atoi ("PIXMA_EXPERIMENT", 0) == 0)
        {
          pixma_dbg (1, "Experimental SANE backend for %s is disabled "
               "by default.\n", pixma_get_device_model (i));
          pixma_dbg (1, "To enable it, set the environment variable "
               "PIXMA_EXPERIMENT to non-zero.\n");
          return SANE_STATUS_UNSUPPORTED;
        }
#else
      return SANE_STATUS_UNSUPPORTED;
#endif
    }

  ss = (pixma_sane_t *) calloc (1, sizeof (*ss));
  if (!ss)
    return SANE_STATUS_NO_MEM;
  ss->next = first_scanner;
  first_scanner = ss;
  sanei_thread_initialize (ss->reader_taskid);
  ss->wpipe = -1;
  ss->rpipe = -1;
  ss->idle = SANE_TRUE;
  ss->scanning = SANE_FALSE;
  ss->sp.frontend_cancel = SANE_FALSE;
  for (j=0; j < BUTTON_GROUP_SIZE; j++)
    ss->button_option_is_cached[j] = 0;
  error = pixma_open (i, &ss->s);
  if (error < 0)
    {
      sane_close (ss);
      return map_error (error);
    }
  pixma_enable_background (ss->s, 0);
  init_option_descriptors (ss);
  *h = ss;
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle h)
{
  pixma_sane_t **p, *ss;

  for (p = &first_scanner; *p && *p != (pixma_sane_t *) h; p = &((*p)->next))
    {
    }
  if (!(*p))
    return;
  ss = *p;
  sane_cancel (ss);
  pixma_close (ss->s);
  *p = ss->next;
  free (ss);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle h, SANE_Int n)
{
  DECL_CTX;

  if (ss && 0 <= n && n < opt_last)
    return &SOD (n);
  return NULL;
}

SANE_Status
sane_control_option (SANE_Handle h, SANE_Int n,
		     SANE_Action a, void *v, SANE_Int * i)
{
  DECL_CTX;
  SANE_Int info = 0;
  int error;
  option_descriptor_t *opt;

  if (i)
    *i = 0;
  if (!ss)
    return SANE_STATUS_INVAL;
  if (n < 0 || n >= opt_last)
    return SANE_STATUS_UNSUPPORTED;
  if (!ss->idle && a != SANE_ACTION_GET_VALUE)
    {
      PDBG (pixma_dbg (3, "Warning: !idle && !SANE_ACTION_GET_VALUE\n"));
      if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
        return SANE_STATUS_INVAL;
    }

  opt = &(OPT_IN_CTX[n]);
  if (!SANE_OPTION_IS_ACTIVE (opt->sod.cap))
    return SANE_STATUS_INVAL;
  switch (a)
    {
    case SANE_ACTION_SET_VALUE:
      if ((opt->sod.type != SANE_TYPE_BUTTON && !v) ||
          !SANE_OPTION_IS_SETTABLE (opt->sod.cap))
        return SANE_STATUS_INVAL;	/* or _UNSUPPORTED? */
      break;
    case SANE_ACTION_SET_AUTO:
      if (!(opt->sod.cap & SANE_CAP_AUTOMATIC) ||
          !SANE_OPTION_IS_SETTABLE (opt->sod.cap))
        return SANE_STATUS_INVAL;	/* or _UNSUPPORTED? */
      break;
    case SANE_ACTION_GET_VALUE:
      if (!v || !(opt->sod.cap & SANE_CAP_SOFT_DETECT))
        return SANE_STATUS_INVAL;	/* or _UNSUPPORTED? */
      break;
    default:
      return SANE_STATUS_UNSUPPORTED;
    }

  error = control_option (ss, n, a, v, &info);
  if (error == SANE_STATUS_GOOD && i)
    *i = info;
  return error;
}

SANE_Status
sane_get_parameters (SANE_Handle h, SANE_Parameters * p)
{
  DECL_CTX;
  pixma_scan_param_t temp, *sp;

  if (!ss || !p)
    return SANE_STATUS_INVAL;

  if (!ss->idle)
    {
      sp = &ss->sp;		/* sp is calculated in sane_start() */
    }
  else
    {
      calc_scan_param (ss, &temp);
      sp = &temp;
    }
  p->format = (sp->channels == 3) ? SANE_FRAME_RGB : SANE_FRAME_GRAY;
  p->last_frame = SANE_TRUE;
  p->lines = sp->h;
  p->depth = sp->depth;
  p->pixels_per_line = sp->w;
  /* p->bytes_per_line = sp->line_size; NOTE: It should work this way, but it doesn't. No SANE frontend can cope with this. */
  p->bytes_per_line = (sp->w * sp->channels * sp->depth) / 8;
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_start (SANE_Handle h)
{
  DECL_CTX;
  int error = 0;

  if (!ss)
    return SANE_STATUS_INVAL;
  if (!ss->idle && ss->scanning)
    {
      PDBG (pixma_dbg (3, "Warning in Sane_start: !idle && scanning. idle=%d, ss->scanning=%d\n",
                       ss->idle, ss->scanning));
      if (ss->sp.source != PIXMA_SOURCE_ADF && ss->sp.source != PIXMA_SOURCE_ADFDUP)
        return SANE_STATUS_INVAL;
    }

  ss->cancel = SANE_FALSE;
  if (ss->idle ||
      ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_FLATBED ||
      ss->source_map[OVAL (opt_source).w] == PIXMA_SOURCE_TPU)
    ss->page_count = 0;	/* start from idle state or scan from flatbed or TPU */
  else
    ss->page_count++;
  if (calc_scan_param (ss, &ss->sp) < 0)
    return SANE_STATUS_INVAL;

  /* Prepare the JPEG decompressor, if needed */
  if (ss->sp.mode_jpeg)
    {
      SANE_Status status;
      status = pixma_jpeg_start(ss);
      if (status != SANE_STATUS_GOOD)
        {
          PDBG (pixma_dbg(1, "%s: pixma_jpeg_start: %s\n", __func__, sane_strstatus(status)) );
          return status;
        }
    }

  ss->image_bytes_read = 0;
  /* TODO: Check paper here in sane_start(). A function like
     pixma_get_status() is needed. */
  error = start_reader_task (ss);
  if (error >= 0)
    {
      ss->output_line_size = (ss->sp.w * ss->sp.channels * ss->sp.depth) / 8;
      ss->byte_pos_in_line = 0;
      ss->last_read_status = SANE_STATUS_GOOD;
      ss->scanning = SANE_TRUE;
      ss->idle = SANE_FALSE;
      if (ss->sp.mode_jpeg && !ss->jpeg_header_seen)
        {
          SANE_Status status;
          status = pixma_jpeg_read_header(ss);
          if (status != SANE_STATUS_GOOD)
            {
              close (ss->rpipe);
              pixma_jpeg_finish(ss);
              ss->rpipe = -1;
              if (sanei_thread_is_valid (terminate_reader_task (ss, &error))
                && error != SANE_STATUS_GOOD)
                {
                  return error;
                }
            }
        }
    }
  return map_error (error);
}

SANE_Status
sane_read (SANE_Handle h, SANE_Byte * buf, SANE_Int maxlen, SANE_Int * len)
{
  DECL_CTX;
  int sum, n;
  /* Due to 32 pixels alignment, sizeof(temp) is to be greater than:
   * max(nchannels) * max (sp.line_size - output_line_size)
   * so currently: 3 * 32 = 96  for better end line cropping efficiency */
  SANE_Byte temp[100];
  SANE_Status status;

  if (len)
    *len = 0;
  if (!ss || !buf || !len)
    return SANE_STATUS_INVAL;
  if (ss->cancel)
    return SANE_STATUS_CANCELLED;
  if ((ss->idle)
      && (ss->sp.source == PIXMA_SOURCE_ADF || ss->sp.source == PIXMA_SOURCE_ADFDUP))
    return SANE_STATUS_INVAL;
  if (!ss->scanning)
    return ss->last_read_status;

  status = SANE_STATUS_GOOD;
  /* CCD scanners use software lineart
   * the scanner must scan 24 bit color or 8 bit grayscale for one bit lineart */
  if ((ss->sp.line_size - ((ss->sp.software_lineart == 1) ? (ss->output_line_size * 8) : ss->output_line_size)) == 0)
    {
      status = read_image (ss, buf, maxlen, &sum);
    }
  else
    {
      /* FIXME: Because there is no frontend that can cope with padding at
         the end of line, we've to remove it here in the backend! */
      PDBG (pixma_dbg (1, "*sane_read***** Warning: padding may cause incomplete scan results\n"));
      sum = 0;
      while (sum < maxlen)
        {
          if (ss->byte_pos_in_line < ss->output_line_size)
            {
              n = ss->output_line_size - ss->byte_pos_in_line;
              if ((maxlen - sum) < n)
                n = maxlen - sum;
              status = read_image (ss, buf, n, &n);
              if (n == 0)
                break;
              sum += n;
              buf += n;
              ss->byte_pos_in_line += n;
            }
          else
            {
              /* skip padding */
              n = ss->sp.line_size - ss->byte_pos_in_line;
              if (n > (int) sizeof (temp))
                {
                  PDBG (pixma_dbg (3, "Inefficient skip buffer. Should be %d\n", n));
                  n = sizeof (temp);
                }
              status = read_image (ss, temp, n, &n);
              if (n == 0)
                break;
              ss->byte_pos_in_line += n;
              if (ss->byte_pos_in_line == ss->sp.line_size)
                ss->byte_pos_in_line = 0;
             }
        }
    }
  if (ss->cancel)
    status = SANE_STATUS_CANCELLED;
  else if ((status == SANE_STATUS_GOOD || status == SANE_STATUS_EOF) &&
	   sum > 0)
    {
      *len = sum;
      status = SANE_STATUS_GOOD;
    }
  ss->scanning = (status == SANE_STATUS_GOOD);
  ss->last_read_status = status;
  return status;
}

void
sane_cancel (SANE_Handle h)
{
  DECL_CTX;

  if (!ss)
    return;
  ss->cancel = SANE_TRUE;
  ss->sp.frontend_cancel = SANE_TRUE;
  if (ss->idle)
    return;
  close (ss->rpipe);
  if (ss->sp.mode_jpeg)
    pixma_jpeg_finish(ss);
  ss->rpipe = -1;
  terminate_reader_task (ss, NULL);
  ss->idle = SANE_TRUE;
}

SANE_Status
sane_set_io_mode (SANE_Handle h, SANE_Bool m)
{
  DECL_CTX;

  if (!ss || ss->idle || ss->rpipe == -1)
    return SANE_STATUS_INVAL;
#ifdef HAVE_FCNTL_H
  PDBG (pixma_dbg (2, "Setting %sblocking mode\n", (m) ? "non-" : ""));
  if (fcntl (ss->rpipe, F_SETFL, (m) ? O_NONBLOCK : 0) == -1)
    {
      PDBG (pixma_dbg
	    (1, "WARNING:fcntl(F_SETFL) failed %s\n", strerror (errno)));
      return SANE_STATUS_UNSUPPORTED;
    }
  return SANE_STATUS_GOOD;
#else
  return (m) ? SANE_STATUS_UNSUPPORTED : SANE_STATUS_GOOD;
#endif
}

SANE_Status
sane_get_select_fd (SANE_Handle h, SANE_Int * fd)
{
  DECL_CTX;

  *fd = -1;
  if (!ss || !fd || ss->idle || ss->rpipe == -1)
    return SANE_STATUS_INVAL;
  *fd = ss->rpipe;
  return SANE_STATUS_GOOD;
}

/* CAUTION!
 * Remove generated files pixma_sane_options.[ch] after editing SANE option
 * descriptors below OR do a 'make clean' OR manually generate them as described
 * below.
 * However, make drops the circular dependency and the files won't be generated
 * again (see merge request sane-project/backends!491).

BEGIN SANE_Option_Descriptor

rem -------------------------------------------
type group
  title Scan mode

type int resolution
  unit dpi
  constraint @word_list = ss->dpi_list
  default 75
  title @SANE_TITLE_SCAN_RESOLUTION
  desc  @SANE_DESC_SCAN_RESOLUTION
  cap soft_select soft_detect automatic
  info reload_params

type string mode[30]
  constraint @string_list = ss->mode_list
  default @s = SANE_VALUE_SCAN_MODE_COLOR
  title @SANE_TITLE_SCAN_MODE
  desc  @SANE_DESC_SCAN_MODE
  cap soft_select soft_detect automatic
  info reload_params

type string source[30]
  constraint @string_list = ss->source_list
  title @SANE_TITLE_SCAN_SOURCE
  desc  Selects the scan source (such as a document-feeder). Set source before mode and resolution. Resets mode and resolution to auto values.
  default Flatbed
  cap soft_select soft_detect

type bool button-controlled
  title Button-controlled scan
  desc When enabled, scan process will not start immediately. To proceed, press \"SCAN\" button (for MP150) or \"COLOR\" button (for other models). To cancel, press \"GRAY\" button.
  default SANE_FALSE
  cap soft_select soft_detect inactive

rem -------------------------------------------
type group
  title Gamma

type bool custom-gamma
  default SANE_FALSE
  title @SANE_TITLE_CUSTOM_GAMMA
  desc  @SANE_DESC_CUSTOM_GAMMA
  cap soft_select soft_detect automatic inactive

type int gamma-table[1024]
  constraint (0,0xffff,0)
  title @SANE_TITLE_GAMMA_VECTOR
  desc  Gamma-correction table with 1024 entries. In color mode this option equally affects the red, green, and blue channels simultaneously (i.e., it is an intensity gamma table).
  cap soft_select soft_detect automatic inactive

type fixed gamma
  default AUTO_GAMMA
  constraint (0.3,5,0)
  title Gamma function exponent
  desc  Changes intensity of midtones
  cap soft_select soft_detect automatic inactive

rem -------------------------------------------
type group
  title Geometry

type fixed tl-x
  unit mm
  default 0
  constraint @range = &ss->xrange
  title @SANE_TITLE_SCAN_TL_X
  desc  @SANE_DESC_SCAN_TL_X
  cap soft_select soft_detect automatic
  info reload_params

type fixed tl-y
  unit mm
  default 0
  constraint @range = &ss->yrange
  title @SANE_TITLE_SCAN_TL_Y
  desc  @SANE_DESC_SCAN_TL_Y
  cap soft_select soft_detect automatic
  info reload_params

type fixed br-x
  unit mm
  default _MAX
  constraint @range = &ss->xrange
  title @SANE_TITLE_SCAN_BR_X
  desc  @SANE_DESC_SCAN_BR_X
  cap soft_select soft_detect automatic
  info reload_params

type fixed br-y
  unit mm
  default _MAX
  constraint @range = &ss->yrange
  title @SANE_TITLE_SCAN_BR_Y
  desc  @SANE_DESC_SCAN_BR_Y
  cap soft_select soft_detect automatic
  info reload_params

rem -------------------------------------------
type group
  title Buttons

type button button-update
  title Update button state
  cap soft_select soft_detect advanced

type int button-1
  default 0
  title Button 1
  cap soft_detect advanced

type int button-2
  default 0
  title Button 2
  cap soft_detect advanced

type int original
  default 0
  title Type of original to scan
  cap soft_detect advanced

type int target
  default 0
  title Target operation type
  cap soft_detect advanced

type int scan-resolution
  default 0
  title Scan resolution
  cap soft_detect advanced

type int document-type
  default 0
  title Document type
  cap soft_detect advanced

type int adf-status
  default 0
  title ADF status
  cap soft_detect advanced

type int adf-orientation
  default 0
  title ADF orientation
  cap soft_detect advanced

rem -------------------------------------------
type group
  title Extras

type int threshold
  unit PERCENT
  default 50
  constraint (0,100,1)
  title @SANE_TITLE_THRESHOLD
  desc  @SANE_DESC_THRESHOLD
  cap soft_select soft_detect automatic inactive

type int threshold-curve
  constraint (0,127,1)
  title Threshold curve
  desc  Dynamic threshold curve, from light to dark, normally 50-65
  cap soft_select soft_detect automatic inactive

type int adf-wait
  default 0
  constraint (0,3600,1)
  title ADF Waiting Time
  desc  When set, the scanner waits up to the specified time in seconds for a new document inserted into the automatic document feeder.
  cap soft_select soft_detect automatic inactive

type string calibrate[30]
  constraint @string_list = ss->calibrate_list
  title Calibration
  desc When to perform scanner calibration. If you choose \"Once\" it will be performed a single time per driver init for single page scans, and for the first page for each ADF scan.
  default Once
  cap soft_select soft_detect automatic

rem -------------------------------------------
END SANE_Option_Descriptor
*/

/* pixma_sane_options.c generated by
 * scripts/pixma_gen_options.py < pixma.c > pixma_sane_options.c
 *
 * pixma_sane_options.h generated by
 * scripts/pixma_gen_options.py h < pixma.c > pixma_sane_options.h
 */
#include "pixma_sane_options.c"
