diff options
Diffstat (limited to 'backend/pixma/pixma.c')
| -rw-r--r-- | backend/pixma/pixma.c | 2166 | 
1 files changed, 2166 insertions, 0 deletions
| diff --git a/backend/pixma/pixma.c b/backend/pixma/pixma.c new file mode 100644 index 0000000..f763496 --- /dev/null +++ b/backend/pixma/pixma.c @@ -0,0 +1,2166 @@ +/* SANE - Scanner Access Now Easy. + +   Copyright (C) 2011-2019 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, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, +   MA 02111-1307, USA. + +   As a special exception, the authors of SANE give permission for +   additional uses of the libraries contained in this release of SANE. + +   The exception is that, if you link a SANE library with other files +   to produce an executable, this does not by itself cause the +   resulting executable to be covered by the GNU General Public +   License.  Your use of that executable is in no way restricted on +   account of linking the SANE library code into it. + +   This exception does not, however, invalidate any other reasons why +   the executable file might be covered by the GNU General Public +   License. + +   If you submit changes to SANE to the maintainers to be included in +   a subsequent release, you agree by submitting the changes that +   those changes may be distributed with this exception intact. + +   If you write modifications of your own for SANE, it is your choice +   whether to permit this exception to apply to your modifications. +   If you do not wish that, delete this exception notice. + */ +#include "../include/sane/config.h" + +#include <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" + +#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_scan_resolution - 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]; + +  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 * config, const char *devname) +{ +  int i; +  UNUSED(config); +  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); +    } +  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 = 150; +  } + +  /* 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 +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; + +  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; + +  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) +{ +  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: +        switch (a) +          { +          case SANE_ACTION_SET_VALUE: +            clamp_value (ss, n, v, info); +            for (i = 0; i != 4096; i++) +              ss->gamma_table[i] = *((SANE_Int *) v + i); +            break; +          case SANE_ACTION_GET_VALUE: +            for (i = 0; i != 4096; i++) +              *((SANE_Int *) v + i) = ss->gamma_table[i]; +            break; +          case SANE_ACTION_SET_AUTO: +            pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table, +                  sizeof (ss->gamma_table)); +            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: +        /* 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; +        } +      break; +    case opt_gamma: +      if (a == SANE_ACTION_SET_VALUE || a == SANE_ACTION_SET_AUTO) +        { +          /* PDBG (pixma_dbg (4, "*control_option***** gamma = %f *\n", +                           SANE_UNFIX (OVAL (opt_gamma).w))); */ +          pixma_fill_gamma_table (SANE_UNFIX (OVAL (opt_gamma).w), +                                  ss->gamma_table, sizeof (ss->gamma_table)); +        } +      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, ... */ +          /* to avoid fatal errors, +           * select first entry of dynamic mode_list +           * identifiers are unknown here */ +          OVAL (opt_mode).w = ss->mode_map[0]; +          /* recreate dynamic lists */ +          create_mode_list (ss); +          create_dpi_list (ss); +          /* to avoid fatal errors, +           * select first entry of dynamic dpi_list +           * identifiers are unknown here */ +          OVAL (opt_resolution).w = ss->dpi_list[1]; +          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_table=%p source=%d\n", 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_table = (OVAL (opt_custom_gamma).b) ? ss->gamma_table : NULL; +  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; + +  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); + +  /* 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++; +    } + +  build_option_descriptors (ss); + +  /* Enable options that are available only in some scanners. */ +  if (cfg->cap & PIXMA_CAP_GAMMA_TABLE) +    { +      enable_option (ss, opt_gamma, SANE_TRUE); +      enable_option (ss, opt_custom_gamma, SANE_TRUE); +      sane_control_option (ss, opt_custom_gamma, SANE_ACTION_SET_AUTO, +			   NULL, NULL); +      pixma_fill_gamma_table (AUTO_GAMMA, ss->gamma_table, 4096); +    } +  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 *)ss->jpeg_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, V_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) != +       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 (); +} + +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; +  if (name[0] == '\0') +    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; +} + +/* +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_TRUE +  title @SANE_TITLE_CUSTOM_GAMMA +  desc  @SANE_DESC_CUSTOM_GAMMA +  cap soft_select soft_detect automatic inactive + +type int gamma-table[4096] +  constraint (0,255,0) +  title @SANE_TITLE_GAMMA_VECTOR +  desc  @SANE_DESC_GAMMA_VECTOR +  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 + +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 searches the waiting time in seconds for a new document inserted into the automatic document feeder. +  cap soft_select soft_detect automatic inactive + +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" | 
